主题
房屋管理(三)
本页对应 房屋管理,共 7 段视频,约 1 小时 8 分。
学习目标
- 能够调用图片上传接口完成房屋图片上传,并根据
type将返回的图片地址分别写入身份证正面或反面数据,实现图片预览。 - 能够完成房屋表单提交:先校验表单,剔除多余字段,以 POST 方式提交数据,并使用
wx.navigateBack按指定层数返回房屋列表页。 - 能够在
onShow生命周期中获取房屋列表,渲染小区、楼栋、房间号、业主姓名、审核状态和空列表提示,并根据列表长度控制“添加房屋”按钮的显示。 - 能够通过地址参数传递房屋 ID,在详情页
onLoad中接收 ID,调用详情接口并渲染房屋详情及审核状态。 - 能够使用
wd-swipe-cell与wd-dialog实现侧滑删除交互,通过mark传递房屋 ID 和索引,并依据close事件中的confirm/cancel判断用户操作。 - 能够调用删除房屋接口,将 ID 和索引挂载到页面实例以便共享,并在删除成功后使用
splice同步本地列表数据、重新渲染列表与空状态。
知识点
先明确这一节要做什么
房屋列表页要完成三件事:一是只有登录用户才能看到自己的房屋;二是从接口取出房屋数组并渲染,同时处理“一条都没有”的空状态;三是对审核状态做区分展示,并在房屋达到 5 套后隐藏“添加新房屋”的入口。
为什么需要这一节?因为上一节只是把表单数据提交成功,用户点“提交审核”后通过 wx.navigateBack 回到了列表页。如果列表页不主动去拉数据,页面上仍然是旧内容,用户会以为“我明明添加成功了,怎么没显示”。所以列表页的职责不是“记住”数据,而是每次露面都去问一次服务器。
为什么必须在 onShow 里取列表
怎么想:这里最容易踩的坑是写成 onLoad。onLoad 只在页面第一次创建时执行一次;而添加房屋是“列表页 → 选择小区 → 选择楼栋 → 选择房间号 → 填写表单 → 提交”,提交成功后用 wx.navigateBack 往回退,退回来的列表页早就存在了,只是重新显示出来,此时触发的是 onShow,onLoad 不会再跑。
怎么做:把取数据的方法放在 onShow 里调用。
易错点:写成 onLoad 后,第一次进页面有数据,添加完返回却看不到新房屋,很多人会误以为是接口没返回,其实是生命周期选错了。
用 authorization 组件守住登录状态
为什么需要:接口文档明确要求请求时携带用户登录状态,未登录时后端不会返回数据,前端也不该把空列表当成“你没有房屋”展示给用户。
怎么做:项目里 authorization 已经全局注册,直接把列表主体包进去即可;未登录时组件会自动跳转登录页。注意只包裹列表这一块,底部的弹层不需要包。
易错点:包错范围,或者忘了包,导致未登录也能看到页面骨架。
封装 getHouseList:调接口、判 code、渲染
怎么想:请求用 GET,路径 /room,不需要额外参数。返回结构是 { code, data },data 就是房屋数组,用解构重命名成 houseList 更好读。判断成功依旧看 code === 10000,不成功直接 return 并 toast 提示,避免拿脏数据去渲染。
怎么做:
javascript
Page({
data: {
houseList: [],
isEmpty: false,
dialogVisible: false,
},
onShow() {
// 获取用房屋列表
this.getHouseList()
},
// 用户房屋列表
async getHouseList() {
// 调用接口
const { code, data: houseList } = await wx.http.get('/room')
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({
houseList,
isEmpty: houseList.length === 0,
})
},
// ...
})易错点:setData 里顺手把 isEmpty 一起算好,别只更新列表,否则空状态永远不出现。
列表为空时给用户一个出口
为什么需要:新用户一条房屋都没有,页面会是一片空白,必须给一句提示和一个“去添加”的链接。
怎么做:用 wx:if="{{!isEmpty}}" 控制列表区,wx:else 走空状态,链接指向 /house_pkg/pages/locate/index。
渲染房屋信息与审核状态
怎么做:wx:for 遍历 houseList,wx:key="id"(接口返回里带 id)。小区名取 point,房间号由 building 和 room 拼起来,业主取 name。审核状态用 status 判断:1 正在审核(info)、2 审核通过(success)、3 审核失败(fail)。
xml
<authorization>
<block wx:if="{{!isEmpty}}">
<scroll-view show-scrollbar="{{false}}" enhanced scroll-y>
<view class="houses">
<view class="houses-title">房屋信息</view>
<view class="houses-list">
<van-swipe-cell
wx:for="{{houseList}}"
wx:key="id"
mark:index="{{index}}"
mark:id="{{item.id}}"
async-close
bind:close="swipeClose"
right-width="{{70}}"
>
<van-cell-group mark:id="{{item.id}}" bind:tap="goDetail" border="{{false}}">
<van-cell size="large" title="{{item.point}}">
<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 fail">审核失败</text>
</van-cell>
<van-cell
title="房间号"
border="{{false}}"
value="{{item.building}}{{item.room}}"
/>
<van-cell title="业主" border="{{false}}" value="{{item.name}}" />
</van-cell-group>
<view slot="right">删除</view>
</van-swipe-cell>
</view>
</view>
</scroll-view>
<view wx:if="{{houseList.length < 5}}" class="toolbar" bind:tap="addHouse">
<text class="enjoy-icon icon-add"></text>
<text class="button-text">添加新房屋</text>
</view>
</block>
<view wx:else class="blank">
您还没有认证房屋,请点击
<navigator hover-class="none" class="link" url="/house_pkg/pages/locate/index">添加</navigator>
</view>
</authorization>易错点:状态类名写错(比如把失败写成别的名字),页面上文字对了但没有背景色,看起来像“没生效”,实际是类名拼错,对照 app.wxss 里预置的 success / info / fail 检查。
限制最多添加 5 套房屋
为什么需要:业务上每户最多认证 5 套。怎么做:不隐藏整个按钮区,只给按钮加 wx:if="{{houseList.length < 5}}",到 5 条(或更多)时按钮消失,用户自然加不了。
调试审核状态
新添加的房屋状态都是 1,页面只会显示“正在审核”,另外两种样式验证不到。转写稿里后端提供了一个专门用于调试的接口:参数 type=room、id(对应某条房屋的 id)、status(改成 2 或 3),发一次请求后刷新列表页,就能看到“审核通过 / 审核失败”的样式是否正常。注意这只是调试手段,真实项目里状态应由管理后台修改。
易错点小结
生命周期用 onShow 不用 onLoad;isEmpty 跟着列表一起 setData;wx:key 用 id;状态判断用 === 并配对上类名;按钮的显隐条件写成 length < 5 而不是 <= 5。
1. 列表跳详情:先把房屋 ID 带过去
为什么需要:详情页要展示“某一套房屋”的信息,而详情接口必须根据房屋 ID 查询;列表页本身已经拿到了每条房屋数据的 id,所以跳转时要把这个 id 传给详情页。
怎么想:详情页不是凭空知道用户点了哪套房,最直接的方式是把 id 拼在 URL 查询参数后面。转写稿里特别说明:列表页这里没有用 navigator 组件,而是在点击事件里通过 mark 把 id 传给事件回调,再从事件对象里取出 id。
怎么做:在列表页的点击回调中调用 wx.navigateTo,把 ev.mark.id 拼到详情页地址后面:
javascript
// house_pkg/pages/list/index.js
Page({
// ...
goDetail(ev) {
// 通过 mark:id 来传递数据
wx.navigateTo({
url: '/house_pkg/pages/detail/index?id=' + ev.mark.id,
})
},
})易错点:mark:id 要和事件对象里的 ev.mark.id 对应;不要漏掉 ?id=;详情页路径要写完整。转写稿中也是先确认地址里已经带上了参数,再进入详情页接收。
2. 在 onLoad 中接收 ID,并封装详情请求
为什么需要:详情页加载时必须先拿到列表页传过来的 id,否则接口不知道查哪套房。接口要求以 GET 方式请求,并把 ID 拼在 /room/ 后面。
怎么想:onLoad 可以直接接收页面参数,所以用 onLoad({ id }) 解构出 id,再调用封装好的 getHouseDetail(id)。请求前先判断 id 是否存在;请求后根据 code 判断是否成功,成功后再 setData 渲染。
怎么做:
javascript
// house_pkg/pages/detail/index.js
Page({
data: {
houseDetail: {}
},
onLoad({id}) {
// 获取房屋详情
this.getHouseDetail(id)
},
// 房屋详情接口
async getHouseDetail(id) {
if (!id) return wx.utils.toast('参数有误!')
// 调用接口
const { code, data: houseDetail } = await wx.http.get('/room/' + id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({ houseDetail })
},
})易错点:houseDetail 最好初始化为空对象,避免页面首次渲染取字段报错;接口路径是 '/room/' + id,不要把 ID 放错位置;判断成功看 code === 10000,失败要 return,不要继续渲染。转写稿中也是先校验 ID,再调接口,再根据 code 决定是否 setData。
3. 把详情数据渲染到页面:字段要一一对应
为什么需要:接口返回的是完整房屋详情,页面要展示小区、房间号、业主、手机号、身份证正反面照片等信息。
怎么想:按接口字段对号入座:point 是小区名,房间号由 building 和 room 拼起来,业主是 name,手机号是 mobile,身份证照片是 idcardFrontUrl 和 idcardBackUrl。状态单独用 status 判断。
怎么做:保留笔记中的页面结构:
xml
<scroll-view scroll-y enhanced show-scrollbar="{{false}}">
<view class="house-detail">
<van-cell-group border="{{false}}" title="房屋信息">
<van-cell title="{{houseDetail.point}}">
<text wx:if="{{houseDetail.status === 1}}" class="tag info">正在审核</text>
<text wx:if="{{houseDetail.status === 2}}" class="tag success">审核通过</text>
<text wx:if="{{houseDetail.status === 3}}" class="tag fail">审核失败</text>
</van-cell>
</van-cell-group>
<van-cell-group title="业主信息" border="{{false}}">
<van-cell
title-width="200rpx"
title="房间号"
value="{{houseDetail.building}}{{houseDetail.room}}"
/>
<van-cell title-width="200rpx" title="业主" value="{{houseDetail.name}}" />
<van-cell
title-width="200rpx"
border="{{false}}"
title="手机号"
value="{{houseDetail.mobile}}"
/>
</van-cell-group>
<view class="id-card">
<van-cell title="本人身份证照片" />
<view class="id-card-front">
<image src="{{houseDetail.idcardFrontUrl}}" />
</view>
<view class="id-card-back">
<image src="{{houseDetail.idcardBackUrl}}" />
</view>
</view>
</view>
</scroll-view>
<view class="toolbar" bind:tap="editHouse">
<text class="enjoy-icon icon-edit"></text>
<text class="button-text">修改房屋信息</text>
</view>易错点:字段名大小写要准确,尤其是 idcardFrontUrl、idcardBackUrl;图片地址为空时页面可能没有内容,这是数据问题不是结构问题;房间号是两个字段拼接,不要只写一个。
4. 审核状态:用 status 控制文案和类名
为什么需要:房屋有 3 种状态,页面要显示不同文案和不同颜色样式。
怎么想:接口返回 status:1 正在审核,类名 info;2 审核通过,类名 success;3 审核失败,类名 fail。用 wx:if 分别渲染即可。
怎么做:上面 XML 中已经按 status === 1/2/3 写了三个 text。转写稿中还提到可以用调试接口辅助修改状态:传 type=room、房屋 id、status=2 或 3 来验证页面显示。调试时曾发现失败状态类名写错,导致没有背景色,改成 fail 后正常。
易错点:状态值是数字,不要写成字符串比较;失败类名是 fail,不要写成转写稿里口误的 “ar”;调试接口只用于辅助验证,真实项目状态应由管理后台修改。
编辑房屋的目标与共用页面思路
为什么需要: 编辑房屋不是重新添加一套数据,而是对原有房屋信息做修改。用户需要先看到原来的小区、楼栋、房间号等内容,再在表单中改完后提交,否则用户只能“盲改”,体验和正确性都无法保证。
怎么想: 添加房屋和编辑房屋的表单字段基本一致,因此不需要单独写两个页面。可以共用一个表单页,通过地址参数中是否带有 id 来区分:有 id 是编辑,没有 id 是添加。提交接口也可以共用,后端根据参数中是否包含 id 判断是新增还是修改。
怎么做: 在 onLoad 中接收 point、building、room、id。如果存在 id,调用详情接口获取原数据;否则把地址参数直接渲染到表单中。
易错点: 不要一进入页面就无条件请求详情;要先判断 id。添加和编辑共用页面时,不要把“添加”的默认渲染逻辑和“编辑”的详情回填逻辑混在一起。
通过 id 判断添加还是编辑
为什么需要: 同一个页面、同一个提交接口要承担两种业务,必须有一个稳定标识让前端和后端都知道当前操作类型。
怎么想: 地址中带 id,说明操作对象已经存在,是编辑;不带 id,说明还没有保存到服务端,是添加。转写稿中也明确提到:添加编辑房屋接口共用,id 允许为空表示添加,编辑时 id 必须传递。
怎么做: 在 onLoad 中先解构参数,再判断:
javascript
// house_pkg/pages/form/index.js
Page({
// ...
onLoad({ point, building, room, id }) {
// 根据 id 判断是否为修改房屋
if (id) return this.getHouseDetail(id)
// 添加房屋 (渲染数据)
this.setData({ point, building, room })
},
// 房屋详情接口
async getHouseDetail(id) {
// 调用接口
const { code, data: houseDetail } = await wx.http.get('/room/' + id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({ ...houseDetail })
},
})易错点: id 是地址参数,不要写成页面内固定值;详情接口路径要把 id 拼到 /room/ 后面,转写稿中特别提醒“ID 是拼凑到地址后面的,不要写错”。
获取房屋详情并回填表单
为什么需要: 编辑房屋必须基于原数据修改。如果不回填,用户看到的是空表单,提交后可能覆盖掉原有信息。
怎么想: 先根据 id 调详情接口,拿到服务端返回的 houseDetail,再用 setData 展开回填。接口返回中通常包含 code 和 data,先判断 code === 10000 再渲染。
怎么做: 使用 wx.http.get('/room/' + id),解构出 code 和 data: houseDetail;失败时 toast,成功时 this.setData({ ...houseDetail })。这样表单中的小区、楼栋、房间号、姓名、手机号、身份证照片等字段都能回填。
易错点: 不要把整个返回结果直接 setData 到页面根数据里,应取 data;详情接口失败时不要继续渲染;字段名要按接口返回写,例如 point、building、room、name、mobile、status 等,避免同音字或拼写错误。
提交修改并剔除 status
为什么需要: 用户修改的是房屋基础信息,房屋审核状态不应由用户在表单里随意改。转写稿和笔记都强调:修改房屋信息时房屋状态不允许修改,因此提交前要把 status 从数据中剔除。
怎么想: 提交前先做表单校验;校验通过后取 this.data,去掉可能多余的 __webviewId__,再去掉 status,剩余字段通过 post /room 提交。后端根据是否带 id 判断新增或修改。
怎么做:
javascript
Page({
onLoad() {
// ...
},
getHouseDetail(id) {
// ...
},
// 提交审核
async submitForm() {
// 验证数据
if (!this.validate()) return
// 获取全部的数据(剔除可能多余参数 __webviewId__)
const { __webviewId__, status, ...data } = this.data
// 调用接口
const { code } = await wx.http.post('/room', data)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast('提交数据失败!')
// 返回房屋列表页面
wx.navigateBack({ delta: 4 })
},
})易错点: 不要只剔除 status 而忘记 __webviewId__;转写稿提到旧版开发者工具可能带这个多余参数,新版可能不带,但解构剔除更稳妥。提交后要判断 code,不能默认成功。示例中 navigateBack({ delta: 4 }) 是按添加房屋流程的页面层级返回列表;如果编辑入口层级不同,应按实际页面栈调整,不能机械照抄。接口文档地址保留如下:接口文档地址。
调试与易错点
为什么需要: 编辑房屋涉及“详情回填”和“提交修改”两个接口,任何一个环节参数不对,页面看起来可能正常,但数据没有真正更新。
怎么想: 调试时先看详情请求是否带对 id,再看返回数据是否回填;提交时看 Network 中请求参数是否包含 id、是否误带 status,最后看 code 是否为 10000。
怎么做: 先进入编辑页,确认地址参数有 id;在 getHouseDetail 中检查 /room/ + id;提交时检查解构后的 data;成功后按页面栈返回列表并刷新列表数据。转写稿中通过 Network 观察 code 判断成功,这是很实用的调试方式。
易错点: 同音字和拼写要纠正:authorization、setData、onLoad、navigateBack、delta、status、__webviewId__ 不要写错;code !== 10000 时及时 return;编辑时不要修改审核状态;详情失败不要继续回填;提交失败不要跳转。
删除房屋要解决什么问题
删除房屋是根据房屋 ID 完成的数据删除操作,也是房屋管理中的最后一个功能。它看起来只是“点一下删除”,实际包含两层工作:第一层是交互,用户通过侧滑露出删除入口,再通过对话框确认,避免误删;第二层是数据一致性,接口删除数据库中的房屋后,页面里的 houseList 也要同步移除对应项,否则会出现“后端已删、页面还在”的假数据。
所以这一节的思路不是直接发请求,而是先用 Vant 的 van-swipe-cell 和 van-dialog 把交互串起来:侧滑组件负责手势和删除入口,对话框负责二次确认,两个组件都监听 close 事件,在事件里推进下一步逻辑。
用两个 close 事件串起交互
van-swipe-cell 的 close 会在侧滑区域关闭时触发,事件对象里能拿到 position 和 instance。position 表示用户操作的位置,只有 position === 'right' 时才认为用户点了右侧删除区域,这时才显示对话框,并把待删除房屋的 id 和索引保存到页面实例上。保存到 this 的原因是:后面确认删除的方法还要用这两个值,但那时已经不在当前事件回调的作用域里了。
javascript
Page({
swipeClose(ev) {
const { position, instance } = ev.detail
if (position === 'right') {
// 显示 Dialog 对话框
this.setData({
dialogVisible: true,
})
// 记录房屋的 id 和索引,在调用接口的时使用
this.cellId = ev.mark.id
this.cellIndex = ev.mark.index
// swiper-cell 滑块关闭
instance.close()
}
},
dialogClose(ev) {
// 当用户点了确认按钮时调用方法删除数据
if (ev.detail === 'confirm') this.deleteHouse()
},
})这里的关键是 mark:id 和 mark:index。模板中每条房屋记录都要把当前房屋的 ID 和索引通过 mark 传进事件回调,否则 ev.mark.id、ev.mark.index 就是空的。转写稿里也强调过:用户点的是哪一条房屋的删除,必须靠 mark 区分,不能靠猜。instance.close() 的作用是把侧滑滑块恢复关闭状态,否则对话框弹出后侧滑区域可能还开着,交互会显得很乱。
确认后调用删除接口
van-dialog 的 close 事件会在对话框关闭时触发。用户点“确认”时,ev.detail 的值是 'confirm';点“取消”时是 'cancel'。因此不能只看对话框关闭就删除,必须判断 ev.detail === 'confirm',只有确认才调用 deleteHouse()。这是防误删的核心。
javascript
Page({
swipeClose() {
// ...
},
dialogClose() {
// ...
},
// 删除房屋
async deleteHouse() {
// 检测id是否存在
if (!this.cellId) return wx.utils.toast('参数有误!')
// 调用接口
const { code } = await wx.http.delete('/room/' + this.cellId)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 从 AppData 中将删除的房屋从数组移除
this.data.houseList.splice(this.cellIndex, 1)
// 渲染数据
this.setData({
houseList: this.data.houseList,
isEmpty: this.data.houseList.length === 0,
})
},
})deleteHouse 先检查 this.cellId,防止参数异常时发出无意义请求;然后以 DELETE 方式请求 /room/房屋ID。接口返回 code === 10000 才表示成功。成功后用 splice(this.cellIndex, 1) 从 houseList 中移除一条,再 setData 更新 houseList 和 isEmpty。isEmpty 用来控制列表为空时的提示,删除最后一条后页面就能显示空状态。
调试时可以这样验证:在开发者工具 Network 中观察确认后是否发出 DELETE /room/... 请求;请求成功后页面应少一条房屋;点“取消”时不应发请求。若页面没变化,先查 mark 是否传了、this.cellId 是否有值、code 是否等于 10000、splice 后是否执行了 setData。易错点是:把 dialogClose 的 detail 当成布尔值;position 不是 right 也弹框;忘记 instance.close();只改 this.data 不 setData;以及 mark 没绑定导致删错或删不了。
课件图示








代码示例
示例 1:房屋列表(house_pkg/pages/list/index.js + index.wxml)
javascript
// house_pkg/pages/list/index.js
Page({
data: {
houseList: [],
isEmpty: false,
dialogVisible: false,
},
onShow() {
// 获取用户房屋列表
this.getHouseList()
},
// 用户房屋列表
async getHouseList() {
// 调用接口
const { code, data: houseList } = await wx.http.get('/room')
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({
houseList,
isEmpty: houseList.length === 0,
})
},
// 跳转房屋详情
goDetail(ev) {
// 通过 mark:id 来传递数据
wx.navigateTo({
url: '/house_pkg/pages/detail/index?id=' + ev.mark.id,
})
},
})xml
<!-- house_pkg/pages/list/index.wxml -->
<authorization>
<block wx:if="{{!isEmpty}}">
<scroll-view show-scrollbar="{{false}}" enhanced scroll-y>
<view class="houses">
<view class="houses-title">房屋信息</view>
<view class="houses-list">
<van-swipe-cell
wx:for="{{houseList}}"
wx:key="id"
mark:index="{{index}}"
mark:id="{{item.id}}"
async-close
bind:close="swipeClose"
right-width="{{70}}"
>
<van-cell-group mark:id="{{item.id}}" bind:tap="goDetail" border="{{false}}">
<van-cell size="large" title="{{item.point}}">
<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 fail">审核失败</text>
</van-cell>
<van-cell
title="房间号"
border="{{false}}"
value="{{item.building}}{{item.room}}"
/>
<van-cell title="业主" border="{{false}}" value="{{item.name}}" />
</van-cell-group>
<view slot="right">删除</view>
</van-swipe-cell>
</view>
</view>
</scroll-view>
<view wx:if="{{houseList.length < 5}}" class="toolbar" bind:tap="addHouse">
<text class="enjoy-icon icon-add"></text>
<text class="button-text">添加新房屋</text>
</view>
</block>
<view wx:else class="blank">
您还没有认证房屋,请点击
<navigator hover-class="none" class="link" url="/house_pkg/pages/locate/index">添加</navigator>
</view>
</authorization>要点解释与易错点:
- 接口必须在
onShow中调用:添加房屋成功后是通过wx.navigateBack返回列表页的,此时只触发onShow,不会触发onLoad,写在onLoad里会导致新增的房屋不显示。 data: houseList是解构时重命名,把接口返回的data直接命名为houseList,避免与this.data混淆。code !== 10000要return掉,防止接口失败时继续setData渲染脏数据。isEmpty用houseList.length === 0计算,配合wx:if="{{!isEmpty}}"决定显示列表还是空提示,两者是取反关系,容易写反。- 添加按钮的条件是
houseList.length < 5,等于或大于 5 条时按钮隐藏(笔记:列表数据限制最多为 5 条)。 - 状态值:
1正在审核(info)、2审核通过(success)、3审核失败(fail),类名写错会导致没有背景色。 - 整个列表区包在
<authorization>中,未登录会自动跳转登录页;弹层部分不需要包。 - 跳转详情用
mark:id传值,回调里通过ev.mark.id取,不能用dataset。
示例 2:房屋详情(house_pkg/pages/detail/index.js + index.wxml)
javascript
// house_pkg/pages/detail/index.js
Page({
data: {
houseDetail: {},
},
onLoad({ id }) {
// 获取房屋详情
this.getHouseDetail(id)
},
// 房屋详情接口
async getHouseDetail(id) {
if (!id) return wx.utils.toast('参数有误!')
// 调用接口(ID 拼在路径后面)
const { code, data: houseDetail } = await wx.http.get('/room/' + id)
// 检测接口是否调用成功
if (code !== 10000) return wx.utils.toast()
// 渲染数据
this.setData({ houseDetail })
},
})xml
<!-- house_pkg/pages/detail/index.wxml -->
<scroll-view scroll-y enhanced show-scrollbar="{{false}}">
<view class="house-detail">
<van-cell-group border="{{false}}" title="房屋信息">
<van-cell title="{{houseDetail.point}}">
<text wx:if="{{houseDetail.status === 1}}" class="tag info">正在审核</text>
<text wx:if="{{houseDetail.status === 2}}" class="tag success">审核通过</text>
<text wx:if="{{houseDetail.status === 3}}" class="tag fail">审核失败</text>
</van-cell>
</van-cell-group>
<van-cell-group title="业主信息" border="{{false}}">
<van-cell
title-width="200rpx"
title="房间号"
value="{{houseDetail.building}}{{houseDetail.room}}"
/>
<van-cell title-width="200rpx" title="业主" value="{{houseDetail.name}}" />
<van-cell
title-width="200rpx"
border="{{false}}"
title="手机号"
value="{{houseDetail.mobile}}"
/>
</van-cell-group>
<view class="id-card">
<van-cell title="本人身份证照片" />
<view class="id-card-front">
<image src="{{houseDetail.idcardFrontUrl}}" />
</view>
<view class="id-card-back">
<image src="{{houseDetail.idcardBackUrl}}" />
</view>
</view>
</view>
</scroll-view>
<view class="toolbar" bind:tap="editHouse">
<text class="enjoy-icon icon-edit"></text>
<text class="button-text">修改房屋信息</text>
</view>要点解释与易错点:
onLoad({ id })直接解构地址参数,前提是列表页跳转时 URL 上拼了?id=;漏拼则id为undefined。- ID 是拼在路径上的:
'/room/' + id,不要写成 query 形式。 if (!id) return wx.utils.toast('参数有误!')做前置校验,参数缺失时不发请求。data初始值给空对象{},避免模板访问houseDetail.point时报错。- 详情与列表一样是三种状态
1/2/3对应info/success/fail。
示例 3:编辑房屋 + 删除房屋(house_pkg/pages/form/index.js、list/index.js 中的删除逻辑)
javascript
// house_pkg/pages/form/index.js
Page({
onLoad({ point, building, room, id }) {
// 根据 id 判断是否为修改房屋
if (id) return this.getHouseDetail(id)
// 添加房屋(渲染地址参数)
this.setData({ point, building, room })
},
// 房屋详情接口
async getHouseDetail(id) {
const { code, data: houseDetail } = await wx.http.get('/room/' + id)
if (code !== 10000) return wx.utils.toast()
this.setData({ ...houseDetail })
},
// 提交审核
async submitForm() {
// 验证数据
if (!this.validate()) return
// 获取全部的数据(剔除可能多余参数 __webviewId__ 与不允许修改的 status)
const { __webviewId__, status, ...data } = this.data
// 调用接口(有 id 为修改,无 id 为添加)
const { code } = await wx.http.post('/room', data)
if (code !== 10000) return wx.utils.toast('提交数据失败!')
// 返回房屋列表页面
wx.navigateBack({ delta: 4 })
},
})javascript
// house_pkg/pages/list/index.js(删除房屋部分)
Page({
// ... 前面的 data / onShow / getHouseList / goDetail 同示例 1
swipeClose(ev) {
const { position, instance } = ev.detail
if (position === 'right') {
// 显示 Dialog 对话框
this.setData({
dialogVisible: true,
})
// 记录房屋的 id 和索引,在调用接口时使用
this.cellId = ev.mark.id
this.cellIndex = ev.mark.index
// swipe-cell 滑块关闭
instance.close()
}
},
dialogClose(ev) {
// 当用户点了确认按钮时调用方法删除数据
if (ev.detail === 'confirm') this.deleteHouse()
},
// 删除房屋
async deleteHouse() {
// 检测 id 是否存在
if (!this.cellId) return wx.utils.toast('参数有误!')
// 调用接口
const { code } = await wx.http.delete('/room/' + this.cellId)
if (code !== 10000) return wx.utils.toast()
// 从 AppData 中将删除的房屋从数组移除
this.data.houseList.splice(this.cellIndex, 1)
// 渲染数据
this.setData({
houseList: this.data.houseList,
isEmpty: this.data.houseList.length === 0,
})
},
})xml
<!-- house_pkg/pages/list/index.wxml 中弹层部分 -->
<van-dialog
use-slot
title="提示"
show-cancel-button
visible="{{dialogVisible}}"
bind:close="dialogClose"
>
<view>确定要删除该房屋吗?</view>
</van-dialog>要点解释与易错点:
- 编辑与添加共用同一页面:地址参数里有
id就调详情接口回填,没有就按添加处理;提交时同一个POST /room接口靠id区分添加/修改。 - 提交前
if (!this.validate()) return,validate()返回布尔值,任一字段不通过就不能发请求。 - 解构剔除多余字段:
__webviewId__是旧版开发者工具会自动带上的多余参数;status是房屋状态,修改房屋时不允许改,必须剔除。 wx.navigateBack({ delta: 4 }):从列表 → 添加 → 选小区 → 选楼栋 → 选房间号共 4 层,delta默认是 0(只返回上一页),层数写错会退不到列表页。swipeClose中position === 'right'表示点击了右侧删除区域;left、cell不处理。this.cellId/this.cellIndex挂在页面实例上,因为swipeClose里拿到的数据要在deleteHouse里使用,不能用data存(会造成多余渲染)。instance.close()用来把滑块收回去,van-swipe-cell上要加async-close,否则不会等逻辑执行完再关闭。dialogClose中ev.detail === 'confirm'才删除,cancel时什么都不做。- 接口删除成功后必须同步执行
this.data.houseList.splice(this.cellIndex, 1)并重新setData,否则数据库删了、页面数据还在,两者不一致;同时要重算isEmpty。 splice的第二个参数是删除个数,固定写1。
常见错误
1. 添加/编辑房屋返回列表后,列表还是旧数据
- 现象:
wx.navigateBack回到列表页,新房屋不出现,手动下拉或重启才看到。 - 原因:
getHouseList写在onLoad里;navigateBack只触发onShow,不触发onLoad。 - 修法:把拉取逻辑移到
onShow;若onLoad也需要,抽成同一方法避免重复请求。
2. 列表渲染出空白,控制台看到 data 是 Promise
- 现象:
houseList为{}或undefined,wx:for无输出。 - 原因:
wx.http.get返回 Promise,忘了await,或await了但没解构data就直接setData({ houseList: res })。 - 修法:
const { code, data: houseList } = await wx.http.get('/room'),并先判code !== 10000再渲染。
3. 图片上传成功但预览不出来,页面显示 [object Object] 或空
- 现象:
uploadFile的 success 回调拿到了结果,但setData后图片不显示。 - 原因:
uploadFile返回的res.data是 JSON 字符串,不是对象,取data.url取不到。 - 修法:
const result = JSON.parse(res.data),再判result.code,最后取result.data.url。
4. 上传接口 401/400,图片传不上去
- 现象:接口报未登录或缺少文件字段。
- 原因:
header里Authorization少了'Bearer '后的空格;name写成了img/file等;url直接写/upload没拼wx.http.baseURL。 - 修法:
'Bearer ' + getApp().token(注意空格)、name: 'file'、url: wx.http.baseURL + '/upload'。
5. 编辑房屋提交后,房屋状态被重置或接口报错
- 现象:原本"审核通过"的房屋,改完信息变回"正在审核"。
- 原因:
this.data整体提交,把status(以及__webviewId__)一起发给了服务端。 - 修法:提交前解构剔除:
const { __webviewId__, status, ...data } = this.data,只提交data。
6. 侧滑删除,删掉的不是滑动的这一条
- 现象:滑 A 删除,结果 B 消失了。
- 原因:
mark:id/mark:index绑在了内层van-cell-group上而非van-swipe-cell上,ev.mark取不到或取到错误值。 - 修法:把
mark:id、mark:index绑在van-swipe-cell上;并在swipeClose里立刻this.cellId = ev.mark.id存到页面实例,供后续方法使用。
7. 删除成功但列表没变化,或空列表提示不出现
- 现象:接口 200,页面条目仍在;或删到最后一条仍显示列表。
- 原因:只调了接口没同步
AppData;或splice后没setData;或isEmpty没跟着重算。 - 修法:
splice后一次setData({ houseList, isEmpty: houseList.length === 0 });若改用filter重写更稳妥(不依赖索引)。
8. 审核状态标签样式错乱(文字对但没背景色)
- 现象:显示"审核失败"但没有红色底。
- 原因:类名写错,用了
error/arror,而样式表里定义的是fail。 - 修法:对照全局样式表核对
info / success / fail三个类名,三处(列表、详情)同步修改。
练习
练习 1|验证生命周期 在 getHouseList 中打印一行日志,分别用「添加房屋 → 提交 → 返回列表」和「切换 tabBar 回列表」两条路径观察触发次数。回答:如果改成 onLoad,哪条路径会失效?为什么 tabBar 切换也依赖 onShow?
练习 2|消除重复的状态判断 当前列表页与详情页各有三组 wx:if 判断状态文案与类名。请抽出一份状态映射(如 const STATUS_MAP = { 1: {...}, 2: {...}, 3: {...} }),用 WXS 或页面内的转换函数统一产出 { text, className },让模板只写一次。完成后对比改动前后的模板行数。
练习 3|把删除改为不依赖索引 重写 deleteHouse,不使用 this.cellIndex,改为按 id 过滤:houseList.filter(item => item.id !== this.cellId)。然后构造一个场景验证旧写法的问题:连续快速删除两条(第一条的动画未结束就滑第二条),观察两种实现的差异。
练习 4|封装图片上传 把「选择图片 → 上传 → 按 type 写入 idcardFrontUrl / idcardBackUrl」抽成一个可复用方法 uploadImage(type),要求:失败时有 toast、成功时用动态 key 写入、并保证返回 Promise 以便 await。抽完后让表单页的代码只剩两行调用。
练习 5|边界与回归测试 不改代码,只做验证并记录结论:①房屋数量达到 5 条时"添加新房屋"按钮是否隐藏;②删到 0 条时是否出现引导文案与跳转链接;③从详情页点"修改房屋信息"提交后,navigateBack 是否能回到列表且数据已刷新。任一项不符合,定位是哪一处 setData 或 delta 的问题。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 享+生活-添加房屋(上传图片)-2 | 10:06 |
| 2 | 享+生活-添加房屋(提交表单) | 10:17 |
| 3 | 享+生活-房屋列表 | 12:52 |
| 4 | 享+生活-修改房屋的状态 | 03:57 |
| 5 | 享+生活-查询房屋详情 | 09:38 |
| 6 | 享+生活-删除房屋-1 | 11:09 |
| 7 | 享+生活-删除房屋-2 | 10:13 |
小结
- 页面数据何时刷新,由"数据可能在何处被改变"决定,而不是由"页面第一次打开"决定——凡是从别处返回的场景,都要检查是否只依赖了
onLoad。 - 所有接口调用都遵循同一骨架:解构
code与data→ 判code失败即toast并return→ 成功才setData。骨架统一,出错点就只剩参数。 - 组件库回调里拿到的数据往往不是最终形态:
uploadFile的data是字符串,swipe-cell的close需要区分position,dialog的close需要区分confirm。先打印再判断,比猜更快。 - 事件回调和后续方法之间需要"桥",这个桥就是页面实例
this;同时,跨组件传参优先用mark:与 URL query,不要依赖全局变量或隐式顺序。 - 服务端数据与页面数据是两份数据:接口成功只代表数据库变了,
AppData必须手动同步(增删改各一次),否则出现"刷新才对"的假象。 - 凡是"提交表单",都要先问一句"哪些字段不该由前端决定"——
status、__webviewId__这类字段的剔除,比多传一次更难排查。 - 调试接口(改状态)只用于验证渲染分支,不能进入业务流程;能靠它验证的,说明前端只负责展示,不负责裁决。
- 边界条件(0 条、5 条上限、删最后一条)往往比主流程更容易出错,写完主流程立刻补一轮边界验证,成本最低。