Skip to content

用户中心与房屋信息(三) ​

本页对应 用户中心与房屋信息,共 6 段视频,约 53 分钟。

学习目标 ​

  • 能够排查并解决腾讯位置服务调用失败的常见问题,包括拒绝授权、域名未配置、额度未分配、Key 错误及 app.json 权限未声明,并能通过重新调用接口验证配置生效。
  • 能够调用地点搜索接口并配置 pageSize 等参数,将返回的小区数据通过循环渲染为列表,并使用唯一标识设置 key。
  • 能够调用 getLocation 获取当前经纬度,再调用逆地址解析接口将经纬度转换为中文地址并渲染到页面指定位置。
  • 能够调用 chooseLocation 打开地图选点,解构返回的位置名称、详细地址和经纬度,更新页面地址并重新搜索附近小区。
  • 能够在分步表单多页面间通过 URL 查询参数传递 point、building、room 等数据,在目标页 onLoad 中接收、校验并渲染,保证参数传递链路完整。
  • 能够完成房屋列表、详情、编辑、删除的页面级操作:正确渲染列表、展示详情、编辑保存后更新数据、删除后移除或刷新列表。

知识点 ​

先明确目标:登录态下查询房屋列表 ​

为什么需要:添加房屋成功后,用户需要回到列表页看到自己刚录入的房屋;同时接口文档明确要求“查询所有房屋数据时用户必须是登录状态”,所以列表页不是简单展示静态数据,而是一次带鉴权的数据请求。

怎么想:列表页要解决三件事——什么时候请求、请求失败怎么处理、请求成功后如何渲染。请求时机不能放在 onLoad,因为从添加页返回时页面实例还在,onLoad 不会再次执行;渲染时还要区分“有数据”和“空列表”两种界面。

怎么做:在 onShow 中调用 getHouseList,用 wx.http.get('/room') 发请求,判断 code === 10000 后再 setData。接口文档地址见笔记:接口文档地址。

易错点:不要写成 onLoad;接口失败时不要继续 setData;data 解构后建议命名为 houseList,避免和页面 data 混淆。

生命周期选择:为什么是 onShow ​

为什么需要:添加房屋流程结束后,代码里是通过 wx.navigateBack 返回列表页的。navigateBack 不会创建新页面,只会让栈内页面重新显示,因此触发的是 onShow,不是 onLoad。

怎么想:把“获取列表”理解为“页面每次可见时都要刷新”。这样无论用户是第一次进入,还是从添加页、详情页返回,列表都能拿到最新数据。

怎么做:

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,
    })
  },
	// ...
})

易错点:onShow 里不要重复绑定事件;async 函数中 await 失败时要有兜底提示;如果项目封装了登录失效跳转,要确认 wx.http 已处理鉴权,否则列表会一直为空。

数据渲染:卡片、状态标签与滑动单元格 ​

为什么需要:房屋数据包含小区点位、审核状态、房间号、业主等信息,需要用列表卡片清晰展示;同时要支持左滑删除,所以使用 van-swipe-cell 包裹 van-cell-group。

怎么想:外层用 scroll-view 保证列表可滚动;每条房屋用 wx:for 渲染,wx:key="id" 保证复用稳定;状态用 item.status 分别显示“正在审核 / 审核通过 / 审核失败”;点击卡片进入详情,左滑出现“删除”。

怎么做:

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>

易错点:wx:key 不要用 index,否则删除后渲染可能错位;mark:id 是给事件传参用的,点击和左滑删除时要能从 event.currentTarget.dataset 或 mark 中取到房屋 id;status 的判断要用 ===,避免字符串和数字混用。

空状态与“最多 5 条”的添加按钮 ​

为什么需要:新用户没有房屋时不能展示空白页,要给“去添加”的入口;同时业务限制列表最多展示 5 条,超过 5 条就不再显示“添加新房屋”按钮,避免用户无限添加。

怎么想:用 isEmpty 控制空状态,空状态里放 navigator 跳到定位/添加页;用 houseList.length < 5 控制底部工具栏是否出现。

怎么做:getHouseList 中同时设置 houseList 和 isEmpty;空状态分支用 wx:else;添加按钮绑定 addHouse,跳转到 /house_pkg/pages/locate/index 所在流程。

易错点:isEmpty 不要只写 false 初始值就忘记更新;接口返回空数组时 houseList.length === 0 为 true,能正确进入空状态;navigator 的 url 要写绝对路径,否则容易跳错分包页面。

调试与排查顺序 ​

转写稿里反复强调“先确认配置和文档,再定位代码”。列表页调试也按这个顺序:先看接口文档的请求方式、返回字段和登录要求;再看 Network 里 code 是否为 10000;最后看 setData 后 houseList 是否有值。若从添加页返回后列表没更新,优先检查是否误用了 onLoad;若空状态不出现,检查 isEmpty 是否在请求成功分支里被赋值。删除逻辑示例只保留了 bind:close="swipeClose" 的结构,实际删除接口和删除后刷新列表要在 swipeClose 中补齐,删除成功后重新调用 getHouseList,保证列表与后端一致。

从列表跳转到详情:把房屋 ID 带过去 ​

为什么需要:详情页并不知道用户点了哪一套房,必须由列表页把房屋 ID 传到 URL 上,详情页才能按 ID 请求对应数据。
怎么想:列表项在 mark:id 里已经带了 ID,点击时用 wx.navigateTo 跳转,并把 ID 拼到详情页路径后面。
怎么做:在列表页写 goDetail,通过 ev.mark.id 取 ID,拼成 ?id= 参数。
易错点:不要从 dataset 里乱猜字段;mark:id 对应的是 ev.mark.id。路径要写完整,参数名要和详情页解构的 id 一致。转写稿里反复强调“页面传参要一步一步检查”,这里同理:先看列表页有没有把 ID 拼进 URL,再看详情页有没有接到。

javascript
// house_pkg/pages/list/index.js
Page({
  // ...
  goDetail(ev) {
    // 通过 mark:id 来传递数据
    wx.navigateTo({
      url: '/house_pkg/pages/detail/index?id=' + ev.mark.id,
    })
  },
})

接口文档地址:https://apifox.com/apidoc/shared-8d66c345-7a9a-4844-9a5a-1201852f6faa/api-41400751

详情页接收 ID 并请求房屋详情 ​

为什么需要:URL 上的 id 只是入口参数,真正展示的数据要调用 /room/:id 接口获取。
怎么想:在 onLoad 里解构 { id },先判空,再请求接口;接口成功后再 setData。
怎么做:用 async/await 调 wx.http.get('/room/' + id),判断 code === 10000 后再渲染。
易错点:id 可能为空,不能直接拼接口;接口失败要提示;houseDetail 建议先给空对象,避免模板访问未定义字段。转写稿里提到“参数没接到页面就是空的”,调试时先看页面参数里有没有 id,再看接口返回。

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 })
  },
})

渲染详情:状态与字段映射 ​

为什么需要:接口数据不能直接“扔”到页面,模板要按字段取值,状态还要转成不同样式。
怎么想:房屋状态有 3 种:1 正在审核对应 info,2 审核通过对应 success,3 审核失败对应 fail。用 wx:if 分别渲染。
怎么做:用 van-cell-group 分区展示房屋信息、业主信息、身份证照片,字段按接口返回取。
易错点: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>

联调排查:按链路定位问题 ​

为什么需要:详情页出问题时,可能是传参、接口、渲染任一环出错。
怎么想:按“列表拼 URL → 详情 onLoad 接参 → 请求接口 → setData → 模板取值”顺序排查。转写稿里提到没配域名请求会失败、页面参数要主动确认、调试工具偶发显示问题可重新操作验证;这些排查思路同样适用于本节。
怎么做:先看页面参数有没有 id,再看接口返回 code 和 data,最后看 houseDetail 是否进入 AppData。
易错点:多个参数拼接时用 &,本节只有一个 id 也要注意 ?;不要滥用可选链,转写稿里提到过 ES 版本不支持 ?. 会报错,按项目编译版本处理。

编辑房屋的需求与共用表单思路 ​

为什么需要:编辑房屋不是重新录入一套房屋信息,而是对原有房屋信息做修改。用户进入表单时,需要先看到原来的数据,再在原有基础上改动;如果页面是空的,用户就得重复填写,体验差,也容易改错。

怎么想:编辑房屋和添加房屋的字段基本一致,所以不必单独再做一个“编辑页”,而是让同一个表单页面承担两种模式:地址参数里带 id 就是编辑,不带 id 就是新增。这样表单结构、校验逻辑、提交接口都可以复用。接口文档地址保留如下:接口文档地址。

怎么做:在 onLoad 中接收页面参数,根据 id 是否存在分流。若存在 id,调用详情接口;否则把新增时带过来的 point、building、room 渲染到表单。

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 })
  },
})

易错点:第一,onLoad 的解构参数要和上一页传参名一致,否则 id 拿不到,编辑会被误判为新增。第二,详情接口返回后要判断 code,不能直接把失败结果 setData。第三,回填时用 ...houseDetail 展开,字段名要和表单 data 对应。转写稿里反复强调“先接收参数,再基于参数生成/传递”,这里同理:先确认 id 是否接收到,再谈详情回填。

获取详情后的回填与调试顺序 ​

为什么需要:编辑页面的表单初始值必须来自服务端详情,而不是本地猜测。只有详情回填成功,用户看到的才是“原来那套房屋信息”。

怎么想:调试时不要一上来就怀疑表单组件。可以按链路排查:页面参数有没有 id → 详情接口有没有成功 → setData 后页面 data 有没有变化 → 表单是否绑定了对应字段。转写稿中提到过类似“页面参数有了,但数据没生成,检查 setData 是否执行;有时加个打印或重新编译就好了,可能是调试工具的小问题”。这提醒我们:先验证数据流,再处理样式。

怎么做:在 getHouseDetail 中打印或断点确认 houseDetail,再执行 this.setData({ ...houseDetail })。如果页面没更新,检查是否少写 setData、是否字段名不一致、是否编译缓存未刷新。

易错点:不要把详情数据放到页面外层变量却不 setData;不要漏掉接口失败提示;不要用新增参数覆盖详情回填结果。若发现“代码改了页面没变”,先保存、重新编译,再检查调试工具状态,避免把工具问题误判成逻辑问题。

提交时区分新增与修改并剔除 status ​

为什么需要:新增和修改共用提交接口,后端需要根据参数里是否包含 id 判断操作类型。同时,编辑房屋时房屋状态不允许用户修改,因此提交数据里不能把 status 再传回去。

怎么想:提交前先校验表单;然后从 this.data 中剔除多余参数 __webviewId__ 和不允许修改的 status;最后调用与新增相同的 POST /room 接口。成功后返回房屋列表页面。

怎么做:

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__ 是页面运行时可能带出的多余字段,也不应提交;校验失败要直接 return;接口失败要提示;navigateBack({ delta: 4 }) 的层级要和实际页面栈一致。转写稿中“分步表单”应为“分步表单/多页面传值”,其核心也是每页先接收参数、再处理、再传下一层;编辑房屋则是在 onLoad 接收 id,在提交时把干净数据交回服务端。

删除房屋功能在房屋管理中的位置 ​

删除房屋是根据房屋 ID 实现的数据删除操作,也是房屋管理的最后一个功能。它看起来只是“点一下删除”,但实际包含三段交互:滑动单元格露出删除入口、弹出确认对话框、确认后调用接口并从本地列表移除数据。

为什么需要:房屋管理不能只有新增和查看,用户选错、房源下架时都需要删除;同时删除必须二次确认,避免误触。
怎么想:把删除拆成“手势触发 → 确认意图 → 请求接口 → 更新视图”四步,每一步只做一件事。
怎么做:用 Vant 的 van-swipe-cell 做左滑删除入口,用 van-dialog 做确认,用 close 事件把两步串起来。
易错点:不要在滑动时直接删;也不要把“弹窗关闭”和“用户确认”混为一谈。

用 swipe-cell 的 close 事件拿到待删除房屋标识 ​

笔记中两个组件有一个共同事件 close,监听该事件来执行相应逻辑:

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()
  },
})

为什么需要:删除接口需要房屋 ID,列表更新需要知道删哪一项,所以滑动关闭时要先把 id 和 index 保存下来。
怎么想:close 事件既能告诉我们滑动方向,也能通过 mark 拿到 WXML 上绑定的自定义数据;只有 position === 'right' 才代表用户滑出了删除区域。
怎么做:在 swipeClose 中解构 position、instance,判断方向后显示 Dialog,把 ev.mark.id、ev.mark.index 存到页面实例 this 上,最后调用 instance.close() 收起滑块。
易错点:ev.mark 来自模板上的 mark:id、mark:index,如果模板没写或写错,这里就是空值;this.cellId 不要只存在 data 里,笔记用页面实例属性保存,便于其它方法直接访问。转写稿里反复强调“先接收参数,再 setData 渲染”,这里同理:先确认 id/index 有没有拿到,再谈删除。

dialog 的 close 事件只处理 confirm ​

dialogClose 中判断 ev.detail === 'confirm' 才调用删除方法。

为什么需要:Dialog 关闭可能是取消、点遮罩或确认,不能一关闭就删除。
怎么想:把“关闭弹窗”和“确认删除”分开,只有 confirm 才进入业务删除。
怎么做:监听 dialogClose,用 if (ev.detail === 'confirm') this.deleteHouse()。
易错点:不要写成 if (ev.detail);也不要在 swipeClose 里直接调删除接口。

封装 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,
    })
  },
})

为什么需要:删除不是只调接口,还要让页面立刻反映结果;否则用户确认后列表还在,会以为没删成功。
怎么想:先防错(没有 cellId 直接提示),再请求,再判断业务码,最后本地 splice 并 setData。isEmpty 要一起更新,因为删到最后一条时页面要显示空状态。
怎么做:wx.http.delete('/room/' + this.cellId) 拿 code;成功后在 houseList 中按 cellIndex 删除一项,再 setData 渲染。
易错点:splice 后不要忘记 setData;cellIndex 必须和当前列表一致,若删除前列表被刷新或筛选,索引可能失效;接口失败时不要先删本地数据。调试时按转写稿的排查顺序:先看参数有没有传到,再看接口返回,最后看 setData 后视图是否更新。转写稿中“拟地址解析”应纠正为“逆地址解析”,这类同音/口误在排查时也要以文档和实际 API 为准。

课件图示 ​

课件第8页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第19页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

课件第20页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

课件第17页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

课件第18页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

课件第11页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

课件第12页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

课件第13页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 享

代码示例 ​

示例一:房屋列表页(列表渲染 + 左滑删除) ​

javascript
// house_pkg/pages/list/index.js
Page({
  data: {
    houseList: [],
    isEmpty: false,
    dialogVisible: false,
  },
  onShow() {
    // 获取房屋列表(添加成功后 wx.navigateBack 回来会触发 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,
    })
  },
  // 跳转房屋详情(通过 mark:id 传递数据)
  goDetail(ev) {
    wx.navigateTo({
      url: '/house_pkg/pages/detail/index?id=' + ev.mark.id,
    })
  },
  // 添加新房屋
  addHouse() {
    wx.navigateTo({ url: '/house_pkg/pages/locate/index' })
  },
  // 左滑单元格关闭时触发
  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()
    }
  },
  // Dialog 关闭
  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 -->
<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>

<van-dialog
  show="{{dialogVisible}}"
  show-cancel-button
  bind:close="dialogClose"
  title="提示"
>
  <view>确定要删除该房屋吗?</view>
</van-dialog>

关键行与易错点

  • 接口调用必须放在 onShow 中,而不是 onLoad:添加房屋成功后是通过 wx.navigateBack 返回的,此时只触发 onShow,放 onLoad 会导致列表不刷新。
  • isEmpty 由 houseList.length === 0 计算得出,空列表时展示提示文案,非空时才渲染 scroll-view。
  • mark:id / mark:index 是自定义标记,事件回调中通过 ev.mark.id、ev.mark.index 取值;goDetail 与 swipeClose 都依赖它。
  • swipeClose 中必须 instance.close() 关闭滑块,否则 Dialog 弹出时滑块仍停在打开状态。
  • deleteHouse 里先 splice 修改 this.data.houseList,再 setData 同一数组;删除后要同步重算 isEmpty,否则删完最后一条会一直停在空白的列表视图。
  • cellId / cellIndex 挂在页面实例 this 上而非 data 里,避免无意义的视图层渲染。
  • 列表最多 5 条,wx:if="&#123;&#123;houseList.length < 5&#125;&#125;" 控制"添加新房屋"按钮是否展示。

示例二:房屋详情页(URL 传 ID + 详情渲染) ​

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 })
  },
  // 跳转编辑(携带 id 表示修改)
  editHouse() {
    wx.navigateTo({
      url: '/house_pkg/pages/form/index?id=' + this.data.houseDetail.id,
    })
  },
})
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>

关键行与易错点

  • 列表页跳转时把 ID 拼在 URL 后面:'/house_pkg/pages/detail/index?id=' + ev.mark.id;详情页用 onLoad({ id }) 直接解构参数。
  • 拿到 id 先做空值判断再请求,避免拼出 /room/undefined 这种错误地址。
  • 状态码只有三种:1 正在审核(info)、2 审核通过(success)、3 审核失败(fail),WXML 中用三个 wx:if 对应,注意 === 严格比较,接口返回的是数字。
  • data 里给 houseDetail 一个 {} 初始值,防止渲染时报 undefined 属性错误。
  • image 的 src 直接用接口返回的图片地址字段,不要写死。

示例三:添加/编辑共用的房屋表单页 ​

javascript
// house_pkg/pages/form/index.js
Page({
  data: {},
  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 })
  },
  // 数据校验
  validate() {
    const { name, mobile, idcard } = this.data
    if (!name) {
      wx.utils.toast('请填写业主姓名!')
      return false
    }
    if (!mobile) {
      wx.utils.toast('请填写手机号!')
      return false
    }
    if (!idcard) {
      wx.utils.toast('请填写身份证号!')
      return false
    }
    return true
  },
  // 提交审核
  async submitForm() {
    // 验证数据
    if (!this.validate()) return
    // 获取全部的数据(剔除可能多余的参数 __webviewId__ 与不允许修改的 status)
    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 })
  },
})

关键行与易错点

  • 添加与修改共用同一个页面:onLoad 中判断有没有 id,有就拉详情回填,没有就把上一步分步表单传来的 point / building / room 直接 setData。
  • 编辑时房屋状态不允许修改,所以提交前用解构把 status 剔除;同时剔除小程序注入的 __webviewId__,否则会把脏数据提交给后端。
  • 后端靠参数中是否带 id 来区分新增还是修改,因此 data 中要保留 id,只剔除 status 和 __webviewId__。
  • 提交成功用 wx.navigateBack({ delta: 4 }) 一次退回列表页,delta 值要按分步表单的实际层级设置,写小了会停在中间页。
  • validate() 中任一项不通过要 return false,外层 if (!this.validate()) return 才能拦住提交。

常见错误 ​

1. 新增房屋后返回列表,看不到刚添加的数据

  • 现象:提交成功后 wx.navigateBack 回到列表,页面还是旧数据,必须手动下拉或杀掉重进才更新。
  • 原因:把 getHouseList() 写在了 onLoad 里,而 navigateBack 只触发 onShow,不触发 onLoad。
  • 修法:列表请求统一放 onShow;onLoad 只做一次性初始化(如参数解析)。

2. 列表为空时页面一片空白 / 超过 5 条仍显示“添加新房屋”

  • 现象:没有房屋时看不到引导文案;或者已有 5 条以上仍能继续添加。
  • 原因:isEmpty 只在首次 setData 时算过一次,后续增删没有同步;按钮的显隐条件写成了固定值或写反了。
  • 修法:凡是改动 houseList 的地方(请求、删除)都同时 setData 重算 isEmpty;按钮显隐严格用 houseList.length < 5。

3. 删除后列表不刷新,或删掉的不是点中的那一行

  • 现象:确认删除后接口返回成功,但界面上那条记录还在;或者点 A 删掉了 B。
  • 原因:只 splice 了 this.data.houseList 而没有 setData;mark:index 是渲染时的索引,列表变动后已失效,异步 close 回调里再取就容易错位。
  • 修法:splice 后把数组整体 setData 回去并重算 isEmpty;索引只作辅助,真正删除以 mark:id 为准,稳妥做法是删除成功后按 id 过滤或重新拉一次列表。

4. 编辑房屋提交后,审核状态被改回或后端报参数错误

  • 现象:修改房间号等无关字段,房屋状态却从“审核通过”变回“正在审核”;或接口返回失败。
  • 原因:this.data 整包提交,把 status、__webviewId__ 等不该提交的字段一起发了出去。
  • 修法:提交前解构剔除 const { __webviewId__, status, ...data } = this.data,只发业务字段;同时确认表单页是靠 onLoad 里有无 id 区分“新增/编辑”。

5. 详情页请求变成 /room/undefined

  • 现象:进入详情页网络请求路径带 undefined,页面空白。
  • 原因:列表跳转时没拼 ?id=,或 mark:id 没写;也可能是详情页 onLoad 解构的参数名与 URL 不一致。
  • 修法:跳转统一 '/house_pkg/pages/detail/index?id=' + ev.mark.id;onLoad({ id }) 解构后先判空 if (!id) return wx.utils.toast('参数有误!') 再发请求。

6. 分步表单(位置 → 楼栋 → 房间 → 表单)后续页面数据为空

  • 现象:选完小区进入下一步,页面没有生成楼栋;或最后一步 point/building/room 显示 undefined。
  • 原因:某一步 navigateTo 的 url 漏拼参数、参数名前后不一致、多参数少写 & 连接符。
  • 修法:每步跳转前先打印要传的对象,落地页 onLoad 里打印 options 逐项核对;参数名全流程统一(point/building/room);生成随机数据的代码要在拿到上一步参数之后再执行。

7. 位置相关接口调用失败或提示文案不对

  • 现象:getLocation/chooseLocation 报错、地图打不开;附近社区列表为空;权限弹窗文案与预期不符。
  • 原因:app.json 中未声明地理位置接口与 permission 文案;服务器域名未配置;腾讯位置服务 key 的搜索额度未分配或已耗尽。
  • 修法:补齐 app.json 声明并在“详情→项目配置”里确认域名生效;为 key 分配“搜索”额度;额度用完时换 key 或申请。

8. 代码里用了 ?. 等新语法导致编译报错

  • 现象:写 obj?.point 保存后报语法错误,加一行 console.log 又“好了”。
  • 原因:开发者工具的 ES6 转 ES5 默认编译版本较低,不支持可选链;偶发的 AppData 不刷新多为工具缓存问题。
  • 修法:把编译版本调到较新的 ES 规范(如 ES2020),或暂时改写为 obj && obj.point;怀疑缓存时重启工具再验证。

练习 ​

  1. 把列表页改成“删除成功后不本地 splice,而是重新调一次 getHouseList()”,对比两种写法在空列表、删到第 5 条以下时的表现差异,并说明各自适用场景。
  2. 给详情页加一个“刷新”能力:onShow 时若已有 id 就重新拉一次详情,验证编辑返回后详情页能自动更新(注意别重复弹 toast)。
  3. 构造 6 条房屋数据,验证列表页“添加新房屋”按钮的显隐边界(第 5 条与第 6 条),并截图说明 isEmpty 与 length 两个判断分别在什么条件下生效。
  4. 从位置页一路走到表单页,人为制造一次“漏传 building”的 bug,用打印 options 的方式定位并修复,记录定位过程。
  5. 给编辑提交写一个“只提交白名单字段”的工具函数(如 pick(data, ['point','building','room','name','mobile'])),替换现有解构写法并回归测试新增与编辑两条路径。

原视频对照 ​

#视频时长
1腾讯位置服务-常见问题06:24
2腾讯位置服务-小区列表渲染07:26
3腾讯位置服务-获取当前经纬度07:27
4腾讯位置服务-逆地址解析05:20
5腾讯位置服务-打开地图选位置08:30
6房屋信息-分步表单多页面传值18:44

小结 ​

  1. 页面“数据不刷新”的问题,九成出在生命周期选错:一次性初始化归 onLoad,需要每次可见都最新的归 onShow。
  2. 派生状态(isEmpty、按钮显隐)不能只算一次,必须与数据源在同一处、同一时刻更新,否则迟早不一致。
  3. 列表项的“身份”永远用业务 id,不用渲染索引;索引只适合“当前渲染态”的临时记录。
  4. 提交类接口要做字段白名单,this.data 整包发送是隐患的温床,尤其在新增/编辑共用一个页面的场景。
  5. 多页面分步传值的可靠性靠“两端对齐”:跳转侧参数名与落地侧解构名必须一致,调试时打印 options 比猜快得多。
  6. 涉及用户隐私与地理位置的能力,配置(app.json 声明、域名、key 额度)和代码同等重要,先查配置再查逻辑。
  7. 删除、编辑这类“写操作”之后,优先考虑重新拉取数据而不是手工维护本地数组,能规避大量索引与状态同步的坑。