Skip to content

异常上报(二) ​

本页对应 异常上报,共 8 段视频,约 58 分钟。

学习目标 ​

  1. 能够解释任务通知页“查看详情”跳转后无数据的原因,并区分消息详情 ID 与任务 ID,说明通过任务列表进入详情才应获取数据。
  2. 能够独立完成在途任务列表的数据获取与渲染:在组件加载时调用任务列表 API,传入状态 2,使用 ref 数组接收数据,并渲染编号、地址、时间等字段及为详情地址拼接任务 ID。
  3. 能够根据 inable 字段对待提货入口进行条件渲染和禁用样式处理,并验证在途任务无法再次进入提货流程。
  4. 能够根据任务状态控制任务详情展示:状态大于等于 2 时显示提货凭证图片,状态大于等于 1 时显示提货对接人,状态大于等于 2 时同时显示交付对接人并添加分隔样式。
  5. 能够使用 uni-datetime-picker 的 v-model 获取异常时间,并按接口字段名整理异常上报提交数据。
  6. 能够将腾讯地图 Key 配置到 manifest.json 的微信小程序配置中,调用 chooseLocation 完成异常地点选择,并说明跨端地图方案的差异。

知识点 ​

回车登记的业务位置 ​

为什么需要:司机完成运输交付后,还要对整段运输过程做补充说明,例如是否发生交通违章、车辆故障、交通事故。这个最后操作就是回车登记。

怎么想:先明确入口。笔记中“在途列表”和“任务详情”都可能跳到回车登记页,因此跳转时必须把任务标识和出车时间带过去,否则登记页拿不到上下文。转写稿里也反复强调:状态值要记清楚——待提货是 1、在途是 2、回车登记是 4、完成是 6;地址参数漏传,下一页逻辑就做不下去。

怎么做:两个入口的 navigator 都拼上 transportTaskId 与 actualDepartureTime。

vue
<navigator
  v-if="delivery.status === 4"
  :url="`/subpkg_task/record/index?transportTaskId=${delivery.transportTaskId}&actualDepartureTime=${delivery.actualDepartureTime}`"
  class="action"
>
  回车登记
</navigator>

易错点:只写路径不传参;把 subpkg 写成 sbupkg;详情页按钮忘记加 status === 4 条件。

出车时间:从 URL 参数接收 ​

为什么需要:出车时间不是用户在本页重新选的,而是上一个页面已经确定的真实发车时间,应该直接展示并随表单提交。

怎么想:onLoad 是接收页面参数最直接的时机。先打印 query,确认参数名和值,再赋值到 Pinia。转写稿里调试接口时也强调“先看数据有没有、字段名对不对”,不要凭感觉写。

怎么做:

js
onLoad((query) => {
  console.log(query) // 先确认参数
  recordData.value.id = query.transportTaskId
  recordData.value.startTime = query.actualDepartureTime
})

易错点:笔记里 consolel.log 是笔误,应为 console.log;参数名要和跳转处完全一致;不要只渲染出车时间却忘了把 id 存进表单。

回车时间:使用 uni-datetime-picker ​

为什么需要:回车时间要精确到用户选择,普通输入框体验差且格式不可控。扩展组件 uni-datetime-picker 提供 v-model,可直接拿到选择结果。

怎么想:先用一个临时 ref 验证组件能否拿到值,再把它接到 recordData.endTime。转写稿中调试时间组件时,就是通过选择时间后观察绑定变量是否变化来确认的。

怎么做:

vue
<uni-datetime-picker v-model="recordData.endTime">
  <view class="picker-value">{{ recordData.endTime || '请选择' }}</view>
</uni-datetime-picker>

易错点:把 v-model 绑到普通变量后又忘了同步到 Pinia;提交时字段名与接口不一致。接口要什么字段名,就以接口文档为准。

三个异常面板:显示/隐藏与选择 ​

为什么需要:交通违章、车辆故障、交通事故都是“先选是/否,选是才展开详情”的交互。若不控制展开,页面会一直堆满选项。

怎么想:用 radio-group 的 change 事件拿到值,用 show 控制 v-show;用户选“是”才展开。三个组件结构相似,可抽出公共选项组件。

怎么做:

js
const show = ref(false)
function onRadioChange(ev) {
  show.value = !!parseInt(ev.detail.value)
  taskStore.recordData.isBreakRules = show.value
}
vue
<view v-show="show" class="vehicle-panel-body">...</view>

易错点:ev.detail.value 是字符串,"0" 直接转布尔会变真,必须 !!parseInt(...);三个面板分别写入 isBreakRules、isFault、isAccident,不要写串。

公共选项组件与 dataKey ​

为什么需要:违章类型、罚款金额、扣分、故障类型、事故类型都要“点选高亮并记录值”,重复写交互很啰嗦。

怎么想:公共组件只负责高亮和抛出“哪个类型、哪个值”。用 dataKey 区分当前点的是哪个字段,用选项 id 作为提交值。

怎么做:

js
const props = defineProps({ types: Array, dataKey: String })
function onOptionSelect(index, id, text) {
  tabIndex.value = index
  console.log(props.dataKey, text)
  taskStore.recordData[props.dataKey] = id
}
vue
<vehicle-options :data-key="item.key" :types="item.types" />

易错点:只记录文字不记录 id;data-key 与 Pinia 字段名不一致;vehicle-violation 中 types 重复定义要保留正确的一份。

用 Pinia 汇总表单数据 ​

为什么需要:回车登记涉及父页面、三个子组件、图片和描述,靠 emit 层层传很乱。转写稿中处理详情共享数据时也体现了“数据要集中管理”的思路。

怎么想:把接口需要的字段统一定义在 recordData,组件直接写对应字段,父页面提交时取一份完整数据。

怎么做:

js
const recordData = ref({
  id: '', startTime: '', endTime: '',
  isBreakRules: false, breakRulesType: null, penaltyAmount: null, deductPoints: null,
  isFault: false, faultType: null, faultDescription: '', faultImagesList: [],
  isAccident: false, accidentType: null, accidentDescription: '', accidentImagesList: []
})

易错点:字段名与接口文档不一致;描述和图片忘了 v-model 到 Pinia;在子组件里新建一份数据,提交时父页面拿不到。

提交数据与图片裁剪 ​

为什么需要:uni-file-picker 返回的图片对象包含较多信息,而接口通常只接收 url。不裁剪可能提交多余字段。

怎么想:提交前先映射图片数组,只保留 { url },再调用封装好的 record 接口;成功后再跳任务列表。

怎么做:

js
const { accidentImagesList, faultImagesList } = recordData.value
recordData.value.accidentImagesList = accidentImagesList.map(({ url }) => ({ url }))
recordData.value.faultImagesList = faultImagesList.map(({ url }) => ({ url }))

const { code } = await taskApi.record(recordData.value)
if (code !== 200) return uni.utils.toast('回车登记失败!')
uni.reLaunch({ url: '/pages/task/index' })

易错点:record 中 if (!data.id) return 说明没有任务 ID 不会发请求;提交前用 Vue Devtools 检查 Pinia 中 endTime、各 dataKey、图片数组是否齐全;接口字段名以文档为准,不要照抄临时变量名。

1.3 已完成任务列表:先明确状态值 ​

为什么需要:司机的运输任务完成回车登记后,任务状态会变成 6,也就是“已完成”。已完成页面不能再去查待提货、在途的数据,否则列表和业务状态会对不上。

怎么想:任务列表接口是复用的,区别只在状态值。待提货是 1,在途是 2,回车登记是 4,已完成是 6。所以这里调用 taskApi.list(6, page, pageSize) 即可。

怎么做:组件加载时请求第一页,渲染 completeList,同时维护空列表状态。

vue
<!-- pages/task/components/complete.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import taskApi from '@/apis/task'

const completeList = ref([])
const isEmpty = ref(false)

onMounted(() => {
  getCompleteList()
})

async function getCompleteList(page = 1, pageSize = 5) {
  const { code, data } = await taskApi.list(6, page, pageSize)
  if (code !== 200) return uni.utils.toast('已完成任务获取失败!')
  completeList.value = data.items || []
  isEmpty.value = completeList.value.length === 0
}
</script>

模板中通过 v-for 渲染任务卡片,编号、起止地址、提货时间都从 complete 中取;没有数据时展示“无完成货物”。

易错点:状态值不要传错;接口失败要先判断 code !== 200;data.items 可能为空,要用 || [] 兜底。

1.3.2 上拉分页:追加数据,不要覆盖 ​

为什么需要:已完成任务可能不止一页,用户滚动到底部时要继续加载。

怎么想:监听 scroll-view 的 scrolltolower,每次请求下一页。页码不能写死,要根据接口返回的 data.page 推进;有没有更多数据,要根据 data.pages 判断。

怎么做:声明 nextPage 和 hasMore,请求成功后把新数据追加到原数组,并更新页码和是否还有更多。

vue
<script setup>
const nextPage = ref(1)
const hasMore = ref(true)

function onScrollToLower() {
  if (!hasMore.value) return
  getCompleteList(nextPage.value)
}

async function getCompleteList(page = 1, pageSize = 10) {
  const { code, data } = await taskApi.list(6, page, pageSize)
  if (code !== 200) return uni.utils.toast('获取列表失败,稍后重试!')

  completeList.value = [...completeList.value, ...(data.items || [])]
  nextPage.value = ++data.page
  isEmpty.value = completeList.value.length === 0
  hasMore.value = nextPage.value <= data.pages
}
</script>

<template>
  <scroll-view
    @scrolltolower="onScrollToLower"
    class="scroll-view"
    refresher-enabled
    scroll-y
  >
    <!-- 任务卡片 -->
  </scroll-view>
</template>

调试过程:可以在接口返回后看 data.page、data.pages、data.items。如果第二页把第一页覆盖了,说明写成了 completeList.value = data.items;如果一直重复请求,检查 hasMore 是否在无更多数据时拦住。

易错点:下一页数据要追加,使用扩展运算符或 concat;nextPage 要用 ++data.page 后的结果;hasMore 判断的是“下一页页码是否小于等于总页码”。

1.3.3 下拉刷新:回到第一页并关闭动画 ​

为什么需要:用户下拉时希望重新拿到最新的已完成任务,而不是继续追加旧数据。

怎么想:下拉刷新本质是重新请求第 1 页。请求前要打开刷新动画,请求结束后关闭动画;当 page === 1 时要先清空原列表,避免重复数据。

怎么做:监听 refresherrefresh,用 isTriggered 控制 refresher-triggered。

vue
<script setup>
const isTriggered = ref(false)

async function onScrollViewRefresh() {
  isTriggered.value = true
  await getCompleteList()
  isTriggered.value = false
}

async function getCompleteList(page = 1, pageSize = 5) {
  const { code, data } = await taskApi.list(6, page, pageSize)
  if (code !== 200) return uni.utils.toast('获取列表失败,稍后重试!')

  if (page === 1) completeList.value = []
  completeList.value = [...completeList.value, ...(data.items || [])]
  nextPage.value = ++data.page
  isEmpty.value = completeList.value.length === 0
  hasMore.value = nextPage.value <= data.pages
}
</script>

<template>
  <scroll-view
    @scrolltolower="onScrollToLower"
    @refresherrefresh="onScrollViewRefresh"
    :refresher-triggered="isTriggered"
    scroll-y
    refresher-enabled
    class="scroll-view"
  >
    <!-- 任务卡片 -->
  </scroll-view>
</template>

易错点:刷新时忘记清空原列表,会出现重复卡片;忘记 await,动画可能提前关闭;属性名是 refresher-triggered,不是 refresher-triggiered;isTriggered 为 true 打开动画,为 false 关闭动画。

代码示例 ​

代码示例 1:回车登记的数据容器(Pinia Store + 公共选项组件) ​

js
// stores/task.js
import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useTaskStore = defineStore('task', () => {
  // 这里定义的数据全部是接口所需要的数据
  const recordData = ref({
    id: '',
    startTime: '',
    endTime: '',
    /*** 违章 ***/
    isBreakRules: false,
    breakRulesType: null,
    penaltyAmount: null,
    deductPoints: null,
    /*** 故障 ***/
    isFault: false,
    faultType: null,
    faultDescription: '',
    faultImagesList: [],
    /*** 事故 ***/
    isAccident: false,
    accidentType: null,
    accidentDescription: '',
    accidentImagesList: [],
  })

  return { recordData }
})
vue
<!-- subpkg_task/record/components/vehicle-options.vue -->
<script setup>
  import { ref } from 'vue'
  import { useTaskStore } from '@/stores/task'

  const taskStore = useTaskStore()

  // 当前被选中选项的索引值
  const tabIndex = ref(-1)

  // 接收传入组件的数据
  const props = defineProps({
    types: Array,
    dataKey: String,
  })

  // 点击选中选项
  function onOptionSelect(index, id, text) {
    // 高亮显示选中的选项
    tabIndex.value = index
    // 用户选择的是哪个类型
    console.log(props.dataKey)
    // 用户选择了哪个值
    console.log(text)
    // 将数据存入 Pinia
    taskStore.recordData[props.dataKey] = id
  }
</script>

<template>
  <view class="vehicle-options">
    <view
      class="option"
      :class="{ active: tabIndex === index }"
      v-for="(option, index) in props.types"
      :key="option.id"
      @click="onOptionSelect(index, option.id, option.text)"
    >
      {{ option.text }}
    </view>
  </view>
</template>

<style lang="scss" scoped>
  .vehicle-options {
    display: flex;
    flex-wrap: wrap;
    font-size: $uni-font-size-small;

    .option {
      width: 180rpx;
      height: 70rpx;
      text-align: center;
      line-height: 72rpx;
      margin-top: 30rpx;
      margin-right: 38rpx;
      color: $uni-secondary-color;
      border: 2rpx solid $uni-bg-color;
      background-color: $uni-bg-color;
      border-radius: 20rpx;

      &:nth-child(3n) {
        margin-right: 0;
      }

      &.active {
        color: $uni-primary;
        border: 2rpx solid $uni-primary;
        background-color: #ffe0dd;
      }
    }
  }
</style>

关键行与易错点

  • 父组件用 :data-key="item.key" 传参(kebab-case),子组件用 defineProps({ dataKey: String }) 接收,两者写法必须对应;props.dataKey 是“哪个类型”的唯一标识(如 breakRulesType / faultType / accidentType)。
  • taskStore.recordData[props.dataKey] = id 用方括号动态取属性,一次封装就能服务违章、故障、事故三个业务;存的是 option.id 而不是 option.text,接口要的是值(罚款金额、扣分是字符串 id,别擅自转数字)。
  • tabIndex 只是组件内部的高亮状态,不进入 Store,因此三个面板各自独立高亮,互不影响。
  • Store 是 setup 写法,recordData 本身是 ref:组件内用 taskStore.recordData.xxx,页面里经 storeToRefs 后用 recordData.value.xxx,两处取值方式不要混。
  • 是否有违章/故障/事故由 radio 的 @change 写入 isBreakRules / isFault / isAccident,记得用 !!parseInt(ev.detail.value) 把字符串 '0'/'1' 转成布尔值。

代码示例 2:回车登记页面(地址参数 + 时间 + 提交) ​

js
// apis/task.js
import { uniFetch } from './uni-fetch'

export default {
  /**
   * 回车登记
   * @param {Object} data - 接口数据
   */
  record(data) {
    if (!data.id) return
    return uniFetch.post('/driver/tasks/truckRegistration', data)
  },
}
vue
<!-- subpkg_task/record/index.vue -->
<script setup>
  import { onLoad } from '@dcloudio/uni-app'
  import { storeToRefs } from 'pinia'
  import { useTaskStore } from '@/stores/task'
  import taskApi from '@/apis/task'
  import vehicleViolation from './components/vehicle-violation'
  import vehicleBreakdown from './components/vehicle-breakdown'
  import vehicleAccident from './components/vehicle-accident'

  // 回车登记的全部数据
  const { recordData } = storeToRefs(useTaskStore())

  // 获取地址参数
  onLoad((query) => {
    // 任务ID
    recordData.value.id = query.transportTaskId
    // 发车时间
    recordData.value.startTime = query.actualDepartureTime
  })

  // 提交回车登记
  async function onFormSubmit() {
    // 过滤掉图片多余的数据,只保留 url
    const { accidentImagesList, faultImagesList } = recordData.value
    // 事故照片
    recordData.value.accidentImagesList = accidentImagesList.map(({ url }) => {
      return { url }
    })
    // 故障照片
    recordData.value.faultImagesList = faultImagesList.map(({ url }) => {
      return { url }
    })

    // 调用接口提交数据
    const { code } = await taskApi.record(recordData.value)
    // 检测接口是否调用成功
    if (code !== 200) return uni.utils.toast('回车登记失败!')
    // 跳转到任务列表
    uni.reLaunch({ url: '/pages/task/index' })
  }
</script>

<template>
  <view class="page-container">
    <scroll-view class="scroll-view" scroll-y>
      <view class="scroll-view-wrapper">
        <uni-list class="base-info">
          <uni-list-item
            title="出车时间"
            show-arrow
            :right-text="recordData.startTime"
          />
          <uni-list-item show-arrow title="回车时间">
            <template v-slot:footer>
              <uni-datetime-picker v-model="recordData.endTime">
                <view class="picker-value">{{
                  recordData.endTime || '请选择'
                }}</view>
              </uni-datetime-picker>
            </template>
          </uni-list-item>
        </uni-list>
        <vehicle-violation />
        <vehicle-breakdown />
        <vehicle-accident />
      </view>
    </scroll-view>
    <view class="toolbar">
      <button @click="onFormSubmit" class="button">提交登记</button>
    </view>
  </view>
</template>

关键行与易错点

  • 跳转来源必须带参数:在途列表和任务详情都是 :url="`/subpkg_task/record/index?transportTaskId=${...}&actualDepartureTime=${...}`", 少传一个参数,onLoad 里取到 undefined,提交就会失败(record() 里 if (!data.id) return 就是兜底)。
  • onLoad((query) => {...}) 的参数名是 query,取的是地址里的 key 名 transportTaskId / actualDepartureTime,不要写成 id。
  • 模板里用 recordData.startTime(storeToRefs 出来的 ref 在模板自动解包),script 里必须写 recordData.value.startTime,两者不能混,这是最常见的报错点。
  • uni-file-picker 的 v-model 拿到的是包含 name / url / size… 的对象数组,而接口只要 url,所以提交前用 map(({ url }) => ({ url })) 过滤,并且要把结果重新赋值回 recordData.value(直接改 recordData.value 里的数组,不要只改局部变量)。
  • endTime 由 uni-datetime-picker 的 v-model 直接写入 Store,不需要额外的 ref;展示用 recordData.endTime || '请选择',否则空值时界面是一片空白。
  • 提交成功后用 uni.reLaunch 回任务列表,任务状态由后端变为 6(已完成),列表页需要重新拉取。

代码示例 3:已完成任务列表(分页 + 上拉加载 + 下拉刷新) ​

vue
<!-- pages/task/components/complete.vue -->
<script setup>
  import { ref, onMounted } from 'vue'
  import taskApi from '@/apis/task'

  // 已完成任务列表
  const completeList = ref([])
  // 列表是否为空
  const isEmpty = ref(false)
  // 下一页页码
  const nextPage = ref(1)
  // 是否还有更多数据
  const hasMore = ref(true)
  // 下拉刷新动画开关
  const isTriggered = ref(false)

  // 生命周期(获取数据)
  onMounted(() => {
    getCompleteList()
  })

  // 上拉分页
  function onScrollToLower() {
    if (!hasMore.value) return
    // 获取下一页数据
    getCompleteList(nextPage.value)
  }

  // 监听用户的下拉操作
  async function onScrollViewRefresh() {
    isTriggered.value = true
    await getCompleteList()
    // 关闭动画交互
    isTriggered.value = false
  }

  // 获取任务列表(状态 6 = 已完成)
  async function getCompleteList(page = 1, pageSize = 5) {
    const { code, data } = await taskApi.list(6, page, pageSize)
    // 检测接口是否调用成功
    if (code !== 200) return uni.utils.toast('获取列表失败,稍后重试!')
    // 页面为 1 时,清空数组
    if (page === 1) completeList.value = []
    // 渲染任务列表(追加)
    completeList.value = [...completeList.value, ...(data.items || [])]
    // 计算下一页页码
    nextPage.value = ++data.page
    // 判断列表是否为空
    isEmpty.value = completeList.value.length === 0
    // 判断还有没有更多的数据
    hasMore.value = nextPage.value <= data.pages
  }
</script>

<template>
  <scroll-view
    @scrolltolower="onScrollToLower"
    @refresherrefresh="onScrollViewRefresh"
    :refresher-triggered="isTriggered"
    scroll-y
    refresher-enabled
    class="scroll-view"
  >
    <view class="scroll-view-wrapper">
      <view
        v-for="complete in completeList"
        :key="complete.id"
        class="task-card"
      >
        <navigator
          hover-class="none"
          :url="`/subpkg_task/detail/index?id=${complete.id}`"
        >
          <view class="header">
            <text class="no">任务编号: {{ complete.transportTaskId }}</text>
          </view>
          <view class="body">
            <view class="timeline">
              <view class="line">{{ complete.startAddress }}</view>
              <view class="line">{{ complete.endAddress }}</view>
            </view>
          </view>
        </navigator>
        <view class="footer flex">
          <view class="label">提货时间</view>
          <view class="time">{{ complete.created }}</view>
        </view>
      </view>
      <view v-if="isEmpty" class="task-blank">无完成货物</view>
    </view>
  </scroll-view>
</template>

关键行与易错点

  • 状态值要记牢:待提货 1、在途 2、回车登记 4、已完成 6,本页 taskApi.list(6, page, pageSize) 的 6 不能写错。
  • 下一页页码用 nextPage.value = ++data.page(以接口返回的当前页 +1 为准),不要自己 nextPage.value++,否则后端分页规则一变就对不上。
  • 追加数据必须用展开运算符 [...completeList.value, ...(data.items || [])],data.items 可能为 undefined,加 || [] 兜底。
  • 下拉刷新本质是重新请求第 1 页,所以 getCompleteList 里必须 if (page === 1) completeList.value = [],否则数据会重复叠加。
  • hasMore 由 nextPage.value <= data.pages 判断,上拉前先 if (!hasMore.value) return,避免最后一页反复请求。
  • 关闭下拉动画必须配合 refresher-triggered 与 async/await:isTriggered.value = true → await getCompleteList() → isTriggered.value = false,不 await 会出现动画“转不停”。
  • 注意在途列表(delivery 组件)只有一条数据,不做分页也不做下拉刷新;只有已完成列表才需要这套逻辑。另外任务详情页的提货凭证图片要加条件渲染 v-if="taskDetail.status >= 2",状态为 1(待提货)时还没有图片,渲染了也没有意义。

常见错误 ​

  1. 现象:从消息列表点“查看详情”进入任务详情后页面空白。
    原因:消息列表里的 id 不是任务 ID,按该 ID 查不到运输任务。
    修法:明确区分消息 ID 与 transportTaskId;任务详情只接受任务 ID,消息跳转需后端补任务 ID,或改为从任务列表进入。

  2. 现象:在途列表做上拉加载、下拉刷新后出现无意义请求或重复数据。
    原因:在途业务同时最多一条数据,误按普通列表处理分页。
    修法:在途列表只在组件加载时请求状态 2 的数据,不传页码、不做分页和刷新逻辑。

  3. 现象:司机已在途,仍能从待提货列表点进提货页。
    原因:未使用接口返回的 enable 字段控制提货入口。
    修法:用 v-if="pickUp.enable" 显示可点击入口,否则显示禁用样式或空链接;详情页若缺该字段,要求后端补齐。

  4. 现象:回车登记页出车时间为空,提交时报任务 ID 缺失。
    原因:跳转 URL 未携带 transportTaskId、actualDepartureTime,或 onLoad 未接收 query。
    修法:在途列表和任务详情跳转统一拼接两个参数;onLoad 中写入 store,提交前校验 id。

  5. 现象:回车登记提交图片字段被接口拒绝或返回 400。
    原因:uni-file-picker 绑定的是对象数组,包含 name、extname 等多余字段,未按接口要求清洗。
    修法:提交前对 faultImagesList、accidentImagesList 做 map(({ url }) => ({ url })),只保留接口需要字段。

  6. 现象:已完成列表滚动加载后出现重复项,或无法继续加载。
    原因:刷新第 1 页未清空原数组;下一页页码未按返回 page + 1 更新;未用 pages 判断 hasMore。
    修法:page === 1 时先清空数组;nextPage = ++data.page;hasMore = nextPage <= data.pages;scrolltolower 先判断 hasMore。

  7. 现象:下拉刷新动画一直转圈不结束。
    原因:未绑定 refresher-triggered,或请求结束后未把触发状态置为 false。
    修法:用 isTriggered 控制 refresher-triggered,刷新方法中先置 true,await 请求完成后再置 false。

练习 ​

  1. 检查在途列表跳转回车登记的 navigator URL,指出至少两个必须携带的参数,并说明缺失后分别会导致什么问题。
  2. 已完成列表出现“第 2 页数据重复追加”的现象,请检查 getCompleteList 中清空数组、页码更新、hasMore 判断三处逻辑并写出修正方案。
  3. 任务详情在待提货状态出现空白“提货凭证”区域,请给出条件渲染思路,并说明状态 1、2、4、6 下图片区域应如何处理。
  4. 回车登记提交时,后端只接收图片 url。请写出提交前清洗故障图片和事故图片字段的处理代码。
  5. 异常上报页点击“上报位置”地图不显示。请列出排查顺序:manifest 配置、地图 key、chooseLocation 调用、平台差异。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1任务通知页面跳到任务详情会有错误说明02:01
2神领物流-在途任务17:27
3待提货任务必须在完成交付后才可以再次提货04:38
4神领物流-任务详情-1.mp409:55
5神领物流-任务详情-205:17
6神领物流-上报异常介绍03:05
7神领物流-上报异常-105:19
8神领物流-上报异常-211:00

小结 ​

  1. 业务状态决定页面入口和详情区块,状态 1、2、4、6 要作为 UI 条件判断的核心依据。
  2. 跳转参数是页面数据链路的第一环,任务 ID 和出车时间必须在列表或详情跳转时完整传递。
  3. 共享详情页不能只按“当前页面”写死结构,要按任务状态控制图片、联系人和操作按钮。
  4. 列表分页要同时处理三件事:第 1 页清空、下一页页码更新、是否还有更多数据。
  5. 表单提交前必须做字段映射和数据清洗,尤其是文件选择器结果和接口字段命名不一致时。
  6. 跨组件表单数据应统一收口到 Pinia,用 dataKey 区分选择类型,避免状态散落。
  7. 跨端地图、扩展组件和平台 key 要先完成配置与兼容验证,再排查业务代码。