主题
异常上报(二)
本页对应 异常上报,共 8 段视频,约 58 分钟。
学习目标
- 能够解释任务通知页“查看详情”跳转后无数据的原因,并区分消息详情 ID 与任务 ID,说明通过任务列表进入详情才应获取数据。
- 能够独立完成在途任务列表的数据获取与渲染:在组件加载时调用任务列表 API,传入状态 2,使用
ref数组接收数据,并渲染编号、地址、时间等字段及为详情地址拼接任务 ID。 - 能够根据
inable字段对待提货入口进行条件渲染和禁用样式处理,并验证在途任务无法再次进入提货流程。 - 能够根据任务状态控制任务详情展示:状态大于等于 2 时显示提货凭证图片,状态大于等于 1 时显示提货对接人,状态大于等于 2 时同时显示交付对接人并添加分隔样式。
- 能够使用
uni-datetime-picker的v-model获取异常时间,并按接口字段名整理异常上报提交数据。 - 能够将腾讯地图 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(待提货)时还没有图片,渲染了也没有意义。
常见错误
现象:从消息列表点“查看详情”进入任务详情后页面空白。
原因:消息列表里的id不是任务 ID,按该 ID 查不到运输任务。
修法:明确区分消息 ID 与transportTaskId;任务详情只接受任务 ID,消息跳转需后端补任务 ID,或改为从任务列表进入。现象:在途列表做上拉加载、下拉刷新后出现无意义请求或重复数据。
原因:在途业务同时最多一条数据,误按普通列表处理分页。
修法:在途列表只在组件加载时请求状态2的数据,不传页码、不做分页和刷新逻辑。现象:司机已在途,仍能从待提货列表点进提货页。
原因:未使用接口返回的enable字段控制提货入口。
修法:用v-if="pickUp.enable"显示可点击入口,否则显示禁用样式或空链接;详情页若缺该字段,要求后端补齐。现象:回车登记页出车时间为空,提交时报任务 ID 缺失。
原因:跳转 URL 未携带transportTaskId、actualDepartureTime,或onLoad未接收 query。
修法:在途列表和任务详情跳转统一拼接两个参数;onLoad中写入 store,提交前校验id。现象:回车登记提交图片字段被接口拒绝或返回 400。
原因:uni-file-picker绑定的是对象数组,包含name、extname等多余字段,未按接口要求清洗。
修法:提交前对faultImagesList、accidentImagesList做map(({ url }) => ({ url })),只保留接口需要字段。现象:已完成列表滚动加载后出现重复项,或无法继续加载。
原因:刷新第 1 页未清空原数组;下一页页码未按返回page + 1更新;未用pages判断hasMore。
修法:page === 1时先清空数组;nextPage = ++data.page;hasMore = nextPage <= data.pages;scrolltolower先判断hasMore。现象:下拉刷新动画一直转圈不结束。
原因:未绑定refresher-triggered,或请求结束后未把触发状态置为false。
修法:用isTriggered控制refresher-triggered,刷新方法中先置true,await请求完成后再置false。
练习
- 检查在途列表跳转回车登记的
navigatorURL,指出至少两个必须携带的参数,并说明缺失后分别会导致什么问题。 - 已完成列表出现“第 2 页数据重复追加”的现象,请检查
getCompleteList中清空数组、页码更新、hasMore判断三处逻辑并写出修正方案。 - 任务详情在待提货状态出现空白“提货凭证”区域,请给出条件渲染思路,并说明状态
1、2、4、6下图片区域应如何处理。 - 回车登记提交时,后端只接收图片
url。请写出提交前清洗故障图片和事故图片字段的处理代码。 - 异常上报页点击“上报位置”地图不显示。请列出排查顺序:
manifest配置、地图 key、chooseLocation调用、平台差异。
课堂画面






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 任务通知页面跳到任务详情会有错误说明 | 02:01 |
| 2 | 神领物流-在途任务 | 17:27 |
| 3 | 待提货任务必须在完成交付后才可以再次提货 | 04:38 |
| 4 | 神领物流-任务详情-1.mp4 | 09:55 |
| 5 | 神领物流-任务详情-2 | 05:17 |
| 6 | 神领物流-上报异常介绍 | 03:05 |
| 7 | 神领物流-上报异常-1 | 05:19 |
| 8 | 神领物流-上报异常-2 | 11:00 |
小结
- 业务状态决定页面入口和详情区块,状态
1、2、4、6要作为 UI 条件判断的核心依据。 - 跳转参数是页面数据链路的第一环,任务 ID 和出车时间必须在列表或详情跳转时完整传递。
- 共享详情页不能只按“当前页面”写死结构,要按任务状态控制图片、联系人和操作按钮。
- 列表分页要同时处理三件事:第 1 页清空、下一页页码更新、是否还有更多数据。
- 表单提交前必须做字段映射和数据清洗,尤其是文件选择器结果和接口字段命名不一致时。
- 跨组件表单数据应统一收口到 Pinia,用
dataKey区分选择类型,避免状态散落。 - 跨端地图、扩展组件和平台 key 要先完成配置与兼容验证,再排查业务代码。