主题
房屋管理与报修(三)
本页对应 房屋管理与报修,共 7 段视频,约 1 小时 8 分。
学习目标
- 能使用
van-swipe-cell配置左右插槽与right-width,实现房屋列表侧滑删除,并通过异步关闭在用户确认后再关闭单元格。 - 能根据业务复杂度选择原生
wx.showModal或组件库van-dialog完成二次确认,并依据返回值正确调用删除接口。 - 能完成房屋删除后的列表同步:采用前端乐观更新移除对应数据,并在列表为空时渲染空状态,同时用首次加载标记避免初始化时闪现空状态。
- 能修复房屋新增与编辑共用表单的提交问题:利用
undefined字段被请求自动过滤的机制处理 ID,剔除审核状态等多余字段,并按是否存在 ID 区分提交后的返回层级。 - 能使用
van-action-sheet实现报修房屋/维修项目选择,配置actions、title、显示隐藏及select事件,并将选中项回显到页面。 - 能调用“查询审核通过房屋”接口获取报修可选房屋,验证返回数据与组件
actions格式匹配,必要时通过map映射字段后完成渲染。
知识点
报修列表要解决什么问题
报修列表页的目标很明确:用户进入页面后,能看到自己提交过的报修记录,并能从列表进入详情;没有记录时给出引导;底部还能继续“在线报修”。转写稿在进入报修管理时也说明,报修模块整体包含列表查询、详情、编辑和在线报修表单,其中列表查询与前面房屋管理类似,重点是把接口数据查回来并正确渲染。
为什么需要先强调登录?因为报修记录属于用户私有数据,接口要求用户保持登录状态。怎么想?页面加载时调用列表接口,由封装的 wx.http 携带登录态;接口成功后判断业务码,再取 data.rows。怎么做?在 onLoad 中调用 getRepairList:
javascript
// repqir_pkg/pages/list/index.js
Page({
data: {
repairList: []
},
onLoad() {
// 获取报修列表数据
this.getRepairList()
},
// 报修列表接口
async getRepairList() {
// 调用接口
const {
code,
data: { rows: repairList },
} = await wx.http.get('/repair', { current: 1, pageSize: 10 })
// 检测接口是否用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({
repairList,
isEmpty: repairList.length === 0,
})
},
})易错点:不要写死列表数据;接口路径是 /repair,分页参数按文档传;code !== 10000 时要 return,避免继续渲染错误数据;解构 data.rows 前要确认接口返回结构。接口文档地址保留如下:接口文档地址。
列表渲染与状态标签
为什么需要状态标签?报修单有“已取消、受理中、上门中、已完成”等状态,直接显示数字用户看不懂。怎么想?用 wx:for 渲染 repairList,用 wx:key="id" 稳定复用;每个列表项用 van-cell-group 组织信息;状态用 item.status === 数字 判断,并套对应类名。怎么做?模板如下:
xml
<authorization>
<block wx:if="{{!isEmpty}}">
<scroll-view show-scrollbar="{{false}}" enhanced scroll-y>
<view class="repairs">
<view class="repairs-title">我的报修</view>
<view class="repairs-list">
<van-cell-group
wx:for="{{repairList}}"
wx:key="id"
border="{{false}}"
mark:id="{{item.id}}"
bind:tap="goDetail"
>
<van-cell size="large" title="{{item.houseInfo}}">
<text wx:if="{{item.status === 0}}" class="tag cancel">已取消</text>
<text wx:if="{{item.status === 1}}" class="tag info">受理中</text>
<text wx:if="{{item.status === 2}}" class="tag success">上门中</text>
<text wx:if="{{item.status === 3}}" class="tag complete">已完成</text>
</van-cell>
<van-cell title="报修项目" border="{{false}}" value="{{item.repairItemName}}" />
<van-cell title="预约时间" border="{{false}}" value="{{item.appointment}}" />
<van-cell title="电话号码" border="{{false}}" value="{{item.mobile}}" />
</van-cell-group>
</view>
</view>
</scroll-view>
<view class="toolbar" bind:tap="addRepair">
<text class="enjoy-icon icon-repair"></text>
<text class="button-text">在线报修</text>
</view>
</block>
<view wx:else class="blank">
您还没有报修记录,请点击
<navigator hover-class="none" class="link" url="/repair_pkg/pages/form/index">添加</navigator>
</view>
</authorization>易错点:状态判断用 ===,不要用 =;四个状态与类名要一一对应:0 cancel、1 info、2 success、3 complete;wx:key 用唯一 id;点击列表进入详情时,mark:id="{{item.id}}" 是为后续取参准备,不要在模板里写死 id。转写稿里也提到组件数据格式要匹配,若后端字段与组件要求不同,可在前端用 map 做映射,但本节字段已能直接使用。
空状态、入口与调试
为什么需要空状态?用户没有报修记录时,如果只渲染空列表,会不知道下一步做什么。怎么想?接口成功后用 isEmpty 判断;模板中用 wx:if="{{!isEmpty}}" 显示列表,用 wx:else 显示提示,并提供“添加”入口。怎么做?空状态里用 navigator 跳到 /repair_pkg/pages/form/index;底部工具栏 bind:tap="addRepair" 也进入在线报修。易错点:wx:else 必须紧跟在 wx:if 的兄弟节点上;navigator 的 hover-class="none" 可去掉默认点击态;空状态和列表都要放在 authorization 登录包裹内。
调试时建议按顺序看:登录态是否有效;接口是否返回 code: 10000;data.rows 是否为数组;repairList.length 是否正确;点击列表是否能把 id 带到详情页。转写稿中讲房屋列表时提到首次加载容易先显示空状态再闪成列表,解决思路是增加“首次加载”标记,接口成功后再切换空/非空状态。这个经验同样适用于报修列表:isEmpty 的初始值和设置时机要明确,避免页面首次渲染时误判。最后测一遍“有数据”和“无数据”两种场景,再点一次在线报修入口,确认登录和跳转都正常。
先明确详情页要解决什么
报修详情页不是写死数据的页面,它要根据上一条页面传过来的报修 ID,去接口取回这一条报修单,再把房屋信息、报修信息、问题附件和状态展示出来。转写稿在讲报修管理时也提到,报修模块里列表、详情、编辑属于同一类业务,通常都是“页面加载时取数,再把数据渲染到模板”。所以这一节的推导顺序应该是:先拿到 ID,再请求详情,再核对字段,最后按状态控制局部结构。
为什么需要先拿 ID?因为详情页可能从列表进入,也可能从其他入口进入,页面本身不知道用户要看哪一条报修。怎么想?把 ID 放在地址参数里,在 onLoad 中解构出来。怎么做?先判空,再调用详情接口。易错点是参数名要和跳转时保持一致,不能一边传 id,一边在 onLoad 里解构别的名字。
javascript
// repair_pkg/pages/detail/index.js
Page({
data: {
repairDetail: {}
},
onLoad({ id }) {
// 获取报修详情的数据
this.getRepairDetail(id)
},
// 报修详情接口
async getRepairDetail(id) {
if (!id) return wx.utils.toast('参数有误!')
// 调用接口
const { code, data: repairDetail } = await wx.http.get('/repair/' + id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({ repairDetail })
},
})接口调用后要判断业务状态
为什么不能拿到 data 就直接渲染?因为接口请求成功不等于业务成功,笔记里明确判断了 code !== 10000。怎么想?先解构 code 和 data,把 data 重命名为 repairDetail,只有业务码正确才 setData。怎么做?就是上面代码中的 if (code !== 10000) return wx.utils.toast()。易错点是只看网络请求有没有报错,却忽略业务码;或者接口失败后仍然把空对象渲染进去,导致页面出现空白字段。调试时可以按转写稿里常用的顺序:先看请求地址是否为 /repair/具体ID,再看控制台返回的 code 和 data,最后看 setData 后模板字段是否对得上。
用单元格分组渲染详情
为什么用 van-cell-group 和 van-cell?详情字段多,分组后房屋信息、报修信息更清楚。怎么想?房屋信息放一组,报修信息放一组,附件单独一块。怎么做?把 repairDetail 中的字段直接绑定到模板。易错点是模板字段名必须和接口返回字段一致,比如 houseInfo、repairItemName、mobile、appointment、description、attachment,少一个字母就会显示空。转写稿在房屋列表里提到过,后端字段和组件要求不一致时可以用映射方法处理;详情页虽然直接绑定,但同样要先打印数据核对字段,不能凭感觉写。
xml
<scroll-view scroll-y enhanced show-scrollbar="{{false}}">
<view class="repair-detail">
<view wx:if="{{repairDetail.status === 2}}" class="polyline">
<map
style="width: 100%; height: 100%;"
scale="15"
latitude="{{latitude}}"
longitude="{{longitude}}"
></map>
</view>
<van-cell-group border="{{false}}" title="房屋信息">
<van-cell title="{{repairDetail.houseInfo}}">
<text wx:if="{{repairDetail.status === 0}}" class="tag cancel">已取消</text>
<text wx:if="{{repairDetail.status === 1}}" class="tag info">受理中</text>
<text wx:if="{{repairDetail.status === 2}}" class="tag success">上门中</text>
<text wx:if="{{repairDetail.status === 3}}" class="tag complete">已完成</text>
</van-cell>
</van-cell-group>
<van-cell-group title="报修信息" border="{{false}}">
<van-cell title-width="200rpx" title="维修项目" value="{{repairDetail.repairItemName}}" />
<van-cell title-width="200rpx" title="手机号码" value="{{repairDetail.mobile}}" />
<van-cell title-width="200rpx" title="预约日期" value="{{repairDetail.appointment}}" />
<van-cell title="问题描述" label="{{repairDetail.description}}" />
</van-cell-group>
<view class="attachment">
<view class="title">问题附件</view>
<scroll-view scroll-x>
<image wx:for="{{repairDetail.attachment}}" wx:key="id" src="{{item.url}}"></image>
</scroll-view>
</view>
</view>
</scroll-view>
<view class="toolbar">
<view bind:tap="editRepair" class="button-text active">修改信息</view>
<view wx:if="{{repairDetail.status === 1}}" class="button-text">取消报修</view>
</view>状态标签、地图和取消按钮要按条件显示
状态标签为什么需要?不同报修单有已取消、受理中、上门中、已完成等状态,用户要一眼看懂。怎么想?用 status 的数值控制文案和样式:0 已取消、1 受理中、2 上门中、3 已完成。怎么做?用 wx:if="{{repairDetail.status === 0}}" 这类判断。易错点是用 = 做比较、状态值和文案对应错、数字写成字符串。
地图为什么只在 status === 2 时显示?笔记明确说“当报修状态为上门中即 status 值为2时才会展示地图组件”。怎么想?只有上门中才需要定位相关展示,其他状态不渲染。怎么做?把 map 包在 wx:if="{{repairDetail.status === 2}}" 的 view 里。易错点是所有状态都渲染地图,或者 latitude、longitude 没有数据时仍然显示空地图。
取消报修按钮为什么只在 status === 1 时显示?笔记说“在线报修状态为受理中即 status 值为1时才允许取消报修”。怎么想?这是业务限制,不是样式问题。怎么做?给取消按钮加 wx:if="{{repairDetail.status === 1}}"。易错点是只隐藏按钮但接口层不校验;本节笔记只负责详情渲染和按钮显示,取消报修的事件与接口要按后续业务补,不能在这里凭空编造。
附件用横向滚动承接多张图
为什么附件用 scroll-view scroll-x?报修问题附件可能有多张图,横向滚动比纵向堆叠更省空间。怎么想?循环 repairDetail.attachment,用 item.url 作为图片地址,wx:key="id" 保持列表稳定。怎么做?就是模板中的 image 循环。易错点是附件为空时仍渲染空白区域、wx:key 用错、图片地址字段不是 url。调试时先确认接口返回的 attachment 是数组,再检查每一项是否有 id 和 url。整体排查顺序可以归纳为:地址参数有没有传进来 → 接口有没有请求成功 → setData 后数据有没有进来 → 模板字段和状态判断是否一致。
路线规划的整体思路:地图组件 + 位置服务
路线规划要解决的问题是:在地图上展示两个点之间的行进路线。它不是一个组件就能完成的事情,而是两部分知识配合:小程序 map 组件负责“把地图和路线画出来”,腾讯位置服务负责“计算两点之间到底经过哪些经纬度坐标点”。
为什么需要这样分工?因为地图组件本身擅长渲染,但不负责路径计算;位置服务擅长算路线,但不负责页面展示。怎么想?先把地图渲染出来,再把位置服务返回的坐标点交给 polyline。怎么做?页面里使用 map,同时调用 qqMap.direction。易错点是把“渲染”和“计算”混在一起,误以为 map 组件会自动算出路线。
map 组件的基础属性
map 组件用于在页面渲染地图。笔记中列出几个核心属性:
latitude:指定地图中心点的纬度longitude:指定地图中心点的经度(笔记中“经功”为同音错字)scale:地图初始缩放比例,取值范围 3–20markers:地图上的标记点polyline:地图上的路线
为什么需要这些属性?因为地图必须先确定“看哪里、放多大”,再决定“标什么、画什么”。怎么想?latitude、longitude、scale 相对容易理解,先让地图中心落在路线附近,再处理标记点和路线。怎么做?在 data 中准备中心点和缩放级别,绑定到 map 组件。易错点是经纬度顺序写反,或者 scale 超出 3–20 的范围。
markers 标记点:把起点和终点标出来
markers 用来在地图上显示标记点。为什么需要?因为只有路线还不够,用户需要一眼看出起点、终点或房屋位置。怎么想?每个标记点是一个对象,至少要有位置信息和唯一 id。怎么做?笔记中的代码如下:
javascript
// repair_pkg/pages/detail/index.js
Page({
data: {
markers: [
{
id: 1,
latitude: 40.22077,
longitude: 116.23128,
width: 24,
height: 30,
},
{
id: 2,
latitude: 40.225857999999995,
longitude: 116.23246699999999,
iconPath: '/static/images/marker.png',
width: 40,
height: 40,
},
],
}
})每个标记点必须指定 id,否则会产生错误。通过 iconPath 可以自定义标记点图片,width/height 定义标记点大小。易错点:漏写 id;iconPath 路径写错;宽高单位或数值不合理导致图标显示异常。
polyline 与路线规划:路线需要很多坐标点
polyline 用来在图上展示两点间的行进路线。它不是只给起点和终点,而是需要一条由很多坐标点组成的线。为什么需要调用位置服务?因为这些坐标点要靠路线规划计算出来。
怎么想?使用腾讯位置服务的路线规划方法 qqMap.direction,传入出行方式、起点、终点,在成功回调里拿到路线数据。怎么做?笔记代码如下:
javascript
// repair_pkg/pages/detail/index.js
Page({
data: {},
onLoad() {
// 生成路线
this.getPolyline()
},
// 调用位置服务(路线规划)
getPolyline() {
qqMap.direction({
mode: 'bicycling',
from: '40.227978,116.22998',
to: '40.22077,116.23128',
success: ({ result }) => {
const coors = result.routes[0].polyline
const points = []
// 坐标解压(返回的点串坐标,通过前向差分进行压缩)
for (let i = 2; i < coors.length; i++) {
coors[i] = Number(coors[i - 2]) + Number(coors[i]) / 1000000
}
// 获取经纬度
for (let i = 0; i < coors.length; i += 2) {
points.push({ latitude: coors[i], longitude: coors[i + 1] })
}
// 渲染数据
this.setData({
latitude: points[30].latitude,
longitude: points[30].longitude,
polyline: [
{ points, color: '#5591af', width: 4 },
],
})
},
})
},
})易错点:from、to 的格式通常是“纬度,经度”;result.routes[0].polyline 要确认存在;success 是异步回调,不能把 setData 写到回调外面。
坐标解压与二次加工
为什么还要解压?因为位置服务返回的 polyline 是压缩过的点串坐标,通过前向差分进行压缩,不能直接当作经纬度使用。怎么想?先按官方方式解压,再把一维数组两两一组转成 { latitude, longitude }。怎么做?第一个循环从 i = 2 开始,用前两个值递推;第二个循环每两个值生成一个点。最后把 points 交给 polyline,并设置颜色和线宽。
易错点:解压循环从 i = 0 开始会出错;忘记除以 1000000;points[30] 可能不存在,调试时要先确认解压后的点数量;polyline 的格式要写成 { points, color, width }。
调试检查与课堂简化说明
调试时建议按顺序检查:markers 是否能正常显示且 id 不重复;qqMap.direction 是否成功返回;result.routes[0].polyline 是否有数据;解压后 points 是否是非空数组;setData 后 polyline 是否生效。本节转写稿主要记录的是房屋删除、表单、报修管理等其他业务,没有提供路线规划的实际调试过程,因此这里不虚构调试记录,只依据笔记中的代码和接口返回结构说明检查点。
笔记还说明:真实企业项目中路线起点和终点通常由接口提供,课堂项目做了简化,自己定义起点和终点,便于观察结果,对知识点掌握没有影响。易错点是不要把课堂写死的 from、to 当成最终业务写法。
修改报修的整体思路
为什么需要:在线报修既要支持“新增”,也要支持“修改”。如果新增和修改各写一套页面,表单结构、校验逻辑、提交逻辑都要重复维护,成本高且容易不一致。因此本项目复用同一个表单页:有报修 ID 时走修改,没有 ID 时走新增。
怎么想:修改报修可以拆成两步——先把原有报修数据取回来并回显到表单,再在用户改完后把数据重新提交。关键点是:报修 ID 通过 URL 传入,页面加载时先判断有没有 id,有就调详情接口。
怎么做:在 onLoad 中解构 URL 参数 id,如果有值就调用 getRepairDetail(id)。详情接口返回成功后,用 setData 展开数据完成回显。
javascript
// repair_pkg/pages/form/index.js
Page({
data: {},
onLoad({id}) {
// ...
// 如果有id表明是修改操作
if (id) this.getRepairDetail(id)
},
// 获取待修改的报修信息
async getRepairDetail(id) {
// 调用接口
const { code, data: repairDetail } = await wx.http.get('/repair/' + id)
// 检测是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({ ...repairDetail })
},
})易错点:不要只判断 id 是否存在却不处理详情失败;code !== 10000 时要中断,不能继续 setData。setData({ ...repairDetail }) 是展开对象,不是把整个对象赋给某个字段,写错会导致表单取不到值。还要确认详情接口路径拼接正确。
先统一字段名:houseName 改为 houseInfo
为什么需要:修改报修要回显“报修房屋”的默认值。如果字段名仍用 houseName,而表单/接口使用的是 houseInfo,页面就无法正确展示原有房屋信息。笔记明确说明:不修改这个数据名称,将不会展示报修房屋的默认值。
怎么想:这不是单纯改模板文字,而是数据字段名要贯穿“详情回显—表单绑定—提交数据”等链路。笔记提示该名称修改涉及 4 个位置,说明不能只改一处。
怎么做:按视频逐个位置把 houseName 统一改成 houseInfo,改完后全局搜索 houseName 确认没有遗漏。修改后重新进入带 id 的修改页,观察房屋默认值是否出现。
易错点:只改页面展示、不改提交字段,或只改提交、不改回显字段,都会造成“看起来改了但数据对不上”。同音字里常说的“回响”应理解为“回显”;字段名不一致本质是数据契约不一致。
根据 URL 中的 id 获取报修详情并回显
为什么需要:修改页必须先拿到旧数据,否则用户看到的是空表单,无法在原有内容上修改。报修 ID 通过 URL 传递,所以页面入口参数就是详情查询的依据。
怎么想:把“取详情”和“提交表单”分开。页面加载时只负责判断新增/修改并取详情;用户点击提交时再做校验和提交。这样逻辑清晰,也方便调试。
怎么做:使用 onLoad({id}) 接收参数;if (id) this.getRepairDetail(id);详情成功后 this.setData({ ...repairDetail })。因为本页面与添加在线报修是同一个页面,数据回填后会自动展示到表单中。
易错点:onLoad 参数解构失败、URL 没有正确带 id、详情接口返回结构不是 data、code 判断写反,都会导致不回显。调试时先看 Network 中 /repair/{id} 是否发出、返回是否有数据,再看 setData 后页面数据是否变化。
提交时带上 id,让后端区分新增与修改
为什么需要:新增和修改共用同一个提交接口。后端接口会根据提交数据中是否包含 ID 来区分“添加报修”还是“修改报修”,所以修改提交时必须把 id 一并带上;漏掉就会被当成新增。
怎么想:提交前先校验表单;再从 data 中提取接口需要的字段;最后调用 wx.http.post('/repair', {...})。成功后跳转到报修列表页。
怎么做:保留笔记中的提交代码,注意解构里有 id,请求体里也传 id。
javascript
// repair_pkg/pages/form/index.js
Page({
data: {},
onLoad() {},
getRepairDetail(id) {
// ...
},
async submitForm() {
// 验证表单数据
if (!this.validate()) return
// 提取接口需要的数据
const {
id, houseId, repairItemId, mobile, appointment, description, attachment
} = this.data
// 调用接口
const { code } = await wx.http.post('/repair', {
id,
houseId,
repairItemId,
mobile,
appointment,
description,
attachment,
})
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast('在线报修失败!')
// 跳转到报修列表页面
wx.redirectTo({
url: '/repair_pkg/pages/list/index',
})
},
})易错点:解构时漏掉 id、请求体漏传 id,修改会变成新增;校验失败必须 return,否则会继续提交;接口失败要提示并停止跳转。转写稿中房屋表单调试还提到:新增时初始 id 可设为 undefined,请求时无效空值会被过滤;编辑时要检查多余字段是否被提交,例如房屋模块曾因多传 state 报错。报修修改同样要以接口字段为准,提交前核对 payload,避免多传或漏传。
先明确业务边界:什么状态能取消
为什么需要:取消报修不是任何状态都能点。本节笔记明确:当在线报修处于“受理中”时,才允许取消本次报修。如果不加状态判断,已完成的单子也可能出现“取消报修”,用户点了接口也可能失败,交互上会造成误解。
怎么想:先看详情数据里的状态字段。示例代码用 repairDetail.status === 1 控制“取消报修”按钮是否渲染,因此可以把 status === 1 理解为当前页面里“受理中”的显示条件。按钮是否显示由前端状态控制,真正能否取消仍以接口返回为准。
怎么做:在工具栏中保留“修改信息”,并给“取消报修”加上 wx:if="{{repairDetail.status === 1}}"。同时把报修单 ID 通过 mark:id 带到事件里,方便后续调接口。
易错点:不要把所有状态都显示“取消报修”;不要把 status 写死;不要只凭按钮显示就认为接口一定成功。接口文档地址保留如下:接口文档地址。
xml
<view class="toolbar">
<view
mark:id="{{repairDetail.id}}"
bind:tap="editRepair"
class="button-text active"
>修改信息</view>
<view
wx:if="{{repairDetail.status === 1}}"
mark:id="{{repairDetail.id}}"
bind:tap="cancelRepair"
class="button-text"
>取消报修</view>
</view>事件传参:用 mark:id 把报修单 ID 带到事件里
为什么需要:取消报修必须知道取消哪一条记录。只在按钮上写“取消报修”不够,事件处理函数需要拿到当前报修单 ID,才能拼出接口路径。
怎么想:示例没有在 data 里额外绕一层,而是用 mark:id="{{repairDetail.id}}" 把 ID 标记在事件源上。事件触发后,通过 ev.mark.id 读取。转写稿里删除房屋时也强调“ID 应该在我们点击的时候,直接通过事件传过来”,这个调试顺序可以迁移:先确认事件里有没有 ID,再调接口。
怎么做:在“取消报修”按钮上保留 mark:id,在 cancelRepair(ev) 中先取 ev.mark.id,再调用取消接口。
易错点:不要写成 ev.mark.id 却忘了在模板里加 mark:id;不要把 ID 写成固定值;不要混淆 bind:tap 与普通点击绑定写法。转写稿中“传参传参之后,根据这个参数进行删除接口的调用”说的是删除房屋,但“先传 ID、再调接口”的思路同样适用于本节取消报修。
调用取消接口:按文档发 PUT 请求
为什么需要:前端只是发起取消动作,真正的状态变更在后端。必须按接口文档的请求方法、路径和路径参数调用,否则会出现 404、参数错误或取消无效。
怎么想:示例代码使用 wx.http.put('/cancel/repaire/' + ev.mark.id)。这里路径中的 repaire 是笔记和示例代码里的原样拼写,应优先对照接口文档确认,不要擅自改成自己以为的单词。转写稿里删除房屋时也出现过“路径写错”导致调试失败的提醒,说明调接口前要核对路径。
怎么做:在 cancelRepair 中先调用接口,拿到返回值后判断 code。示例代码如下,保留原代码结构。
javascript
// repair_pkg/pages/detail/index.js
Page({
data: {},
onLoad() {},
// 取消报修
async cancelRepair(ev) {
// 调用接口
const { code } = await wx.http.put('/cancel/repaire/' + ev.mark.id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 跳转到报修列表页面
wx.navigateTo({
url: '/repair_pkg/pages/list/index',
})
},
})易错点:不要用 GET 代替 PUT;不要把路径参数拼错;不要漏掉 await;不要只看请求有没有发出,而不看 code。转写稿中调试删除时先看返回值、再处理页面,这个顺序可以借鉴:先确认接口成功,再做跳转。
成功判断与跳转:code 为 10000 才进列表
为什么需要:接口返回成功和业务成功不是一回事。示例用 code !== 10000 判断失败,失败时 return wx.utils.toast(),成功后才跳转报修列表页。这样用户不会在失败时被误带到列表页。
怎么想:取消成功后,当前详情页的数据已经不再适合继续展示“受理中”的报修单,因此示例选择跳转到报修列表页。转写稿里多次出现“操作成功之后回到列表页/更新界面”的处理思路,本节按笔记要求使用 wx.navigateTo 跳转到 /repair_pkg/pages/list/index。
怎么做:保持 if (code !== 10000) return wx.utils.toast(),成功分支执行 wx.navigateTo。如果项目里 wx.utils.toast() 有默认提示文案,按项目封装使用即可,不要在本节额外编造提示内容。
易错点:不要在失败分支继续跳转;不要把 code 判断写成赋值;不要跳错分包路径;不要擅自把 navigateTo 改成其他跳转方式,除非项目统一规范明确要求。转写稿里删除房屋还涉及二次确认和乐观更新,但那是删除房屋业务;本节笔记只要求“受理中允许取消、调用取消接口、成功后跳列表”,不要把这些额外流程硬套到取消报修上。
课件图示








代码示例
示例一:报修列表页(列表渲染 + 空状态)
xml
<!-- repair_pkg/pages/list/index.wxml -->
<authorization>
<block wx:if="{{!isEmpty}}">
<scroll-view show-scrollbar="{{false}}" enhanced scroll-y>
<view class="repairs">
<view class="repairs-title">我的报修</view>
<view class="repairs-list">
<van-cell-group
wx:for="{{repairList}}"
wx:key="id"
border="{{false}}"
mark:id="{{item.id}}"
bind:tap="goDetail"
>
<van-cell size="large" title="{{item.houseInfo}}">
<text wx:if="{{item.status === 0}}" class="tag cancel">已取消</text>
<text wx:if="{{item.status === 1}}" class="tag info">受理中</text>
<text wx:if="{{item.status === 2}}" class="tag success">上门中</text>
<text wx:if="{{item.status === 3}}" class="tag complete">已完成</text>
</van-cell>
<van-cell title="报修项目" border="{{false}}" value="{{item.repairItemName}}" />
<van-cell title="预约时间" border="{{false}}" value="{{item.appointment}}" />
<van-cell title="电话号码" border="{{false}}" value="{{item.mobile}}" />
</van-cell-group>
</view>
</view>
</scroll-view>
<view class="toolbar" bind:tap="addRepair">
<text class="enjoy-icon icon-repair"></text>
<text class="button-text">在线报修</text>
</view>
</block>
<view wx:else class="blank">
您还没有报修记录,请点击
<navigator hover-class="none" class="link" url="/repair_pkg/pages/form/index">添加</navigator>
</view>
</authorization>javascript
// repair_pkg/pages/list/index.js
Page({
data: {
repairList: []
},
onLoad() {
// 获取报修列表数据
this.getRepairList()
},
// 报修列表接口
async getRepairList() {
// 调用接口
const {
code,
data: { rows: repairList },
} = await wx.http.get('/repair', { current: 1, pageSize: 10 })
// 检测接口是否用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({
repairList,
isEmpty: repairList.length === 0,
})
},
})要点与易错点
data: { repairList: [] }只初始化列表,isEmpty是在setData里才写入的,所以首次渲染时!isEmpty为真,会先显示列表容器,接口回来后再切成空状态。- 解构
data: { rows: repairList }一次性把rows改名成repairList,避免再写this.setData({ repairList: data.rows })。 if (code !== 10000) return wx.utils.toast()一定要在setData之前拦截,否则失败时会把undefined塞进repairList,页面直接报错。- 状态与类名一一对应:
0 → cancel、1 → info、2 → success、3 → complete,漏写一个就会出现“没有标签”的空白格。 wx:key="id"、border="{{false}}"中的false必须用{{}}插值,写成字符串"false"会被当成真值。mark:id="{{item.id}}"是给事件传参用的,在goDetail中通过ev.mark.id取,不能写成data-id。
示例二:报修详情页(详情渲染 + 取消报修)
xml
<!-- repair_pkg/pages/detail/index.wxml -->
<scroll-view scroll-y enhanced show-scrollbar="{{false}}">
<view class="repair-detail">
<view wx:if="{{repairDetail.status === 2}}" class="polyline">
<map
style="width: 100%; height: 100%;"
scale="15"
latitude="{{latitude}}"
longitude="{{longitude}}"
></map>
</view>
<van-cell-group border="{{false}}" title="房屋信息">
<van-cell title="{{repairDetail.houseInfo}}">
<text wx:if="{{repairDetail.status === 0}}" class="tag cancel">已取消</text>
<text wx:if="{{repairDetail.status === 1}}" class="tag info">受理中</text>
<text wx:if="{{repairDetail.status === 2}}" class="tag success">上门中</text>
<text wx:if="{{repairDetail.status === 3}}" class="tag complete">已完成</text>
</van-cell>
</van-cell-group>
<van-cell-group title="报修信息" border="{{false}}">
<van-cell title-width="200rpx" title="维修项目" value="{{repairDetail.repairItemName}}" />
<van-cell title-width="200rpx" title="手机号码" value="{{repairDetail.mobile}}" />
<van-cell title-width="200rpx" title="预约日期" value="{{repairDetail.appointment}}" />
<van-cell title="问题描述" label="{{repairDetail.description}}" />
</van-cell-group>
<view class="attachment">
<view class="title">问题附件</view>
<scroll-view scroll-x>
<image wx:for="{{repairDetail.attachment}}" wx:key="id" src="{{item.url}}"></image>
</scroll-view>
</view>
</view>
</scroll-view>
<view class="toolbar">
<view
mark:id="{{repairDetail.id}}"
bind:tap="editRepair"
class="button-text active"
>修改信息</view>
<view
wx:if="{{repairDetail.status === 1}}"
mark:id="{{repairDetail.id}}"
bind:tap="cancelRepair"
class="button-text"
>取消报修</view>
</view>javascript
// repair_pkg/pages/detail/index.js
Page({
data: {
repairDetail: {}
},
onLoad({ id }) {
// 获取报修详情的数据
this.getRepairDetail(id)
},
// 报修详情接口
async getRepairDetail(id) {
if (!id) return wx.utils.toast('参数有误!')
// 调用接口
const { code, data: repairDetail } = await wx.http.get('/repair/' + id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({ repairDetail })
},
// 取消报修
async cancelRepair(ev) {
// 调用接口
const { code } = await wx.http.put('/cancel/repaire/' + ev.mark.id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 跳转到报修列表页面
wx.navigateTo({
url: '/repair_pkg/pages/list/index',
})
},
})要点与易错点
onLoad({ id })是直接解构地址参数,页面必须通过url="/repair_pkg/pages/detail/index?id=xxx"进入,否则id为undefined。if (!id) return wx.utils.toast('参数有误!')放在发请求前,避免拼出/repair/undefined这种脏地址。- 详情接口返回的是对象,直接
setData({ repairDetail }),不要套data,否则模板里repairDetail.houseInfo全空。 - 地图只在
status === 2(上门中)时渲染;取消报修按钮只在status === 1(受理中)时出现,两个wx:if条件写反就没有对应交互。 ev.mark.id依赖工具栏上的mark:id,若忘记写mark:前缀,ev.mark会是undefined,直接报错。- 取消报修的接口路径以接口文档为准(笔记中为
/cancel/repaire/),联调时先核对文档再动手。
示例三:路线规划(map + 腾讯位置服务 polyline)
javascript
// repair_pkg/pages/detail/index.js(在示例二基础上补充 markers / polyline)
Page({
data: {
// 地图上的标记点:每个标记点必须指定 id
markers: [
{
id: 1,
latitude: 40.22077,
longitude: 116.23128,
width: 24,
height: 30,
},
{
id: 2,
latitude: 40.225857999999995,
longitude: 116.23246699999999,
iconPath: '/static/images/marker.png',
width: 40,
height: 40,
},
],
},
onLoad() {
// 生成路线
this.getPolyline()
},
// 调用位置服务(路线规划)
getPolyline() {
qqMap.direction({
mode: 'bicycling',
from: '40.227978,116.22998',
to: '40.22077,116.23128',
success: ({ result }) => {
const coors = result.routes[0].polyline
const points = []
// 坐标解压(返回的点串坐标,通过前向差分进行压缩)
for (let i = 2; i < coors.length; i++) {
coors[i] = Number(coors[i - 2]) + Number(coors[i]) / 1000000
}
// 获取经纬度
for (let i = 0; i < coors.length; i += 2) {
points.push({ latitude: coors[i], longitude: coors[i + 1] })
}
// 渲染数据
this.setData({
latitude: points[30].latitude,
longitude: points[30].longitude,
polyline: [
{ points, color: '#5591af', width: 4 },
],
})
},
})
},
})xml
<!-- repair_pkg/pages/detail/index.wxml 中的地图节点 -->
<map
style="width: 100%; height: 100%;"
scale="15"
latitude="{{latitude}}"
longitude="{{longitude}}"
markers="{{markers}}"
polyline="{{polyline}}"
></map>要点与易错点
markers里的每一项都必须有id,缺了会直接抛错;iconPath不写就用默认图标,width/height控制图标尺寸。- 坐标解压的循环必须从
i = 2开始(前两个值不能参与差分计算),写成i = 0会把整条路线算歪。 - 解压后要除以
1000000,这个精度是腾讯位置服务规定的,漏掉就是几十公里外的坐标。 - 第二层循环步长是
i += 2,一个纬度一个经度成对取出,写成i++会得到一堆重复点。 polyline接收的是数组,元素形如{ points, color, width },points必须是{ latitude, longitude }对象数组,直接塞原始数字数组地图不认。scale取值范围 3–20,超出范围地图会忽略该值;中心点这里取points[30],实际项目中应根据起点/终点动态计算,避免路线跑出视野。qqMap需要在页面引入并初始化位置服务 SDK 后才可用,mode: 'bicycling'换成driving/walking即切换出行方式。
常见错误
- 现象:报修列表首次进入先闪一下“空状态”再出现列表;原因:用列表长度直接判断空态,未区分“首次加载中”和“确实无数据”;修法:增加
isFirstLoad等加载态变量,首次加载期间不展示空态,数据返回后再按长度判断。 - 现象:滑动单元格划不出删除按钮,或右侧内容布局错乱;原因:未设置右侧插槽宽度,或插槽内容未正确放入
slot="right";修法:补齐右侧宽度属性,并确认删除区域放在对应具名插槽内。 - 现象:点击删除后弹窗已出现,但滑动单元格不收回,或用户未确认就执行删除;原因:未启用异步关闭,或未在用户操作完成后主动调用实例关闭方法;修法:开启异步关闭,在确认/取消流程结束后关闭当前单元格,并且只在确认时调用删除接口。
- 现象:删除接口成功但页面列表不变,或删到最后一条仍不显示空态;原因:只调接口未更新本地列表,或空态条件未随数据变化;修法:删除成功后基于原列表过滤掉对应
id做乐观更新,并用更新后的长度控制空态展示。 - 现象:新增正常、编辑提交失败,或编辑后返回层级不对;原因:新增/编辑共用表单时未保留
id、未过滤undefined、未剔除只读字段如status,且返回层级未按是否存在id区分;修法:初始化id为undefined,提交时保留有效id,剔除只读/多余字段,并按新增或编辑分别控制返回层级。 - 现象:动作面板选择房屋无数据或回显异常;原因:查询了全部房屋而非“审核通过”房屋,或接口返回字段与组件
actions要求格式不一致;修法:改用审核通过房屋接口,必要时用map将返回数据映射为组件要求的{ name, ... }结构,并保存选中项用于回显。
练习
- 改造报修列表页:实现“首次加载不闪空态”,并保证接口返回空数组时才展示无报修提示。
- 完成房屋滑动删除:加入二次确认、异步关闭、确认后删除、删除成功本地过滤列表,并处理全部删除后的空状态。
- 完成新增/编辑共用房屋表单:分别验证新增不传有效
id、编辑保留id,提交前剔除status等只读字段,并让两种场景返回到正确页面层级。 - 完成报修表单中的房屋选择:只查询审核通过房屋,使用动作面板展示选项,选中后回显房屋名称,并保存后续提交所需字段。
- 完成报修详情取消报修:仅在允许取消的状态下展示按钮,取消成功后让列表页数据与状态保持一致。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 房屋删除-滑动单元格和提示框组件 | 10:11 |
| 2 | 原生的模态框和组件库的对话框 | 11:40 |
| 3 | 房屋删除-完成 | 07:20 |
| 4 | 房屋列表-首次加载细节 | 03:38 |
| 5 | 房屋表单-新增和编辑完成 | 12:19 |
| 6 | 报修管理-动作面板组件 | 12:12 |
| 7 | 报修管理-查询审核通过的房屋 | 11:13 |
小结
- 空态、加载态、无数据态要分开建模,不能只用数组长度判断。
- 交互型组件要同时关注“展示结构”和“关闭时机”,异步关闭必须由业务结果驱动。
- 删除类操作优先保证用户当前位置与列表稳定性,乐观更新是常用方案,但要同步处理空态。
- 新增与编辑共用表单时,
id是业务分支核心,提交数据要做空值过滤和只读字段剔除。 - 接口选择要匹配业务语义,报修/访客等场景应使用审核通过房屋数据源。
- 组件库数据格式不一定与接口返回完全一致,必要时在进入组件前做字段映射。
- 状态字段应驱动页面 UI 与操作权限,例如地图展示、取消报修按钮、标签样式等。