主题
在途管理与打包发布(三)
本页对应 在途管理与打包发布,共 7 段视频,约 1 小时 10 分。
学习目标
- 能在回车登记页面补齐运输ID、出车时间、回车时间,并写入Pinia store,提交前验证三项数据完整。
- 能使用storeToRefs解构Pinia状态,在组件中正确读写recordData并保持响应式。
- 能定位车辆事故、车辆故障、交通违章三个单选组件,将选中状态同步写回对应布尔字段。
- 能封装并调用回车登记提交接口,校验响应码,成功跳转首页,失败给出提示。
- 能修复异常上报类型选择问题:改造选项数据结构,实现样式与数据同步、再次打开回显已选项、确定时过滤并拼接文本。
- 能完成H5、微信小程序、App三端发布配置与打包:H5上传部署,小程序填写AppID导入,App配置图标、启动图、版本、摄像头模块并云打包。
知识点
范围说明
本节笔记标题为“2.2 一键登录”,但提供的转写稿实际讲的是神领物流项目中“回车登记数据补齐、异常上报回显修复、H5/小程序/App 打包发布”。转写稿中没有出现一键登录的实现、接口或调试过程,因此下面不补写一键登录,只按转写稿整理正文;转写稿也未提供图片,故不插入图片。
回车登记:补齐 ID、出车时间、回车时间
为什么需要:表单提交前,数据里还差运输 ID、出车时间、回车时间。缺这些字段,提交就不完整。
怎么想:ID 不在 store 里,需要从地址参数中取;出车时间、回车时间虽然能从地址拿到,但还没放进 recordDate。同时,recordDate 在 store 中,直接解构会丢失响应式,所以要用 storeToRefs。
怎么做:引入 store 和 storeToRefs,把地址中的 ID 写入 recordDate.id;出车时间、回车时间通过 v-model 或赋值写入 recordDate。示意代码:
js
import { useTaskStore } from '@/store/task'
import { storeToRefs } from 'pinia'
const taskStore = useTaskStore()
const { recordDate } = storeToRefs(taskStore)
recordDate.value.id = query.id
recordDate.value.startTime = query.startTime
recordDate.value.endTime = query.endTime易错点:storeToRefs 后 recordDate 是 ref,取值要 .value;字段名不要写错;不要直接 const { recordDate } = taskStore,否则响应式会丢失。
三个“是否”字段:事故、故障、违章
为什么需要:页面默认显示“否”,用户选择“是”后,数据也要变成 true,不能只改样式。
怎么想:单选框的 change 事件会给出 true/false,把它同步到 recordDate 中对应的 isAccident、车辆故障、违章字段即可。
怎么做:在三个组件里分别监听 change,把事件值写入 store。示意:
js
taskStore.recordDate.isAccident = e.detail.value易错点:三个组件容易写串字段;只改页面显示、不改 store 数据,提交时仍是默认值;默认值本身不能变化,必须通过事件修改。
提交回车登记
为什么需要:所有字段齐了以后,要调用接口提交,成功后离开当前页,避免重复提交。
怎么想:先封装 task 相关 API,再在“提交登记”按钮上绑定点击事件,传 recordDate.value,最后判断业务 code。
怎么做:
js
const res = await recordAPI(recordDate.value)
if (res.code !== 200) {
uni.showToast({ title: '回车登记失败' })
return
}
uni.reLaunch({ url: '/' })易错点:recordDate 是 ref,传参要加 .value;不要只看 HTTP 200,还要看业务 code;成功后不要停留在原页反复点击。
异常上报:数据与选中状态同步
为什么需要:原来异常类型数组只是字符串,只能显示文字,不能控制选中;再次打开弹层也无法保持已选项。
怎么想:把数组单元改成对象,例如 { text, checked };渲染时用 text,选中状态绑定 checked;change 时拿 index 切换对应单元的 checked;确定时先 filter 出 checked === true,再 map 取 text,最后 join 成回显字符串。
怎么做:
js
taskStore.recordDate.exceptionType = list
.filter(item => item.checked)
.map(item => item.text)
.join('、')易错点:const 定义的数组不能整体重新赋值;切换选中时要同时改数据和样式;filter 后别忘了 map 取文字;再次打开弹层要依赖 checked 保持选中。
打包发布:H5、小程序、App
为什么需要:开发时用“运行”,交付时要“发行”生成可部署代码。
怎么做:H5 选择“发行—网站 H5”,生成到 unpackage/dist/build/h5,可上传阿里云前端网页托管;小程序发行时填写 AppID,生成到 dist/build/mp-weixin,自动打开失败时手动导入;App 端先配置 logo、启动图、minSdkVersion 21、相机模块,再云打包,测试阶段可用公共证书。
易错点:H5 免费托管每天仅前 10 个 IP 可访问;小程序开发工具端口未开或未自动打开时要手动导入;App 包名要两段点分三段并尽量唯一;公共证书只适合测试,企业正式发布需自有证书。
关于本节标题的说明
你给出的笔记标题是“2.3 实人认证”,但转写稿实际讲的是“神领物流—回车登记、上报异常 bug 修复、H5/小程序/App 打包发布”,没有出现实人认证的接口、页面或流程。为避免编造,下面按转写稿整理教程正文;转写稿未提供图片,因此不补图,只保留其中出现的代码思路与关键片段。
回车登记:补齐 ID、出车时间、回车时间
为什么需要:回车登记表单提交时,后端需要运输 ID、出车时间、回车时间等字段。前面页面虽然已经能显示部分数据,但还没有完整写入 store,直接提交会缺字段。
怎么想:先从地址参数或接口数据中拿到 ID,再把它存进 store.recordData;出车时间、回车时间同理。这里用 storeToRefs 把 store 中的响应式数据解构出来,避免直接解构丢失响应式。
怎么做:转写稿中的核心写法是:
js
import { storeToRefs } from 'pinia'
const { recordData } = storeToRefs(taskStore)
recordData.value.id = query.id
recordData.value.startTime = 地址中的出车时间
recordData.value.endTime = 选择的回车时间展示时也要改成 recordData.value.xxx,不能再直接写旧字段。
易错点:storeToRefs 忘记引入会报未定义;recordData 是 ref,赋值要写 .value;ID 不要重复定义,而是写入已有 recordData;时间字段名要区分 startTime 和 endTime。
事故、故障、违章:让“是/否”真正可切换
为什么需要:页面默认显示“否”,但用户点“是”后,数据必须同步变成 true,否则提交的仍是默认值。
怎么想:三个单选项分别对应 isAccident、isBreakRules 等字段。选中事件里拿到 value,把对应字段设为 value === true 或 false。
怎么做:
js
taskStore.recordData.isAccident = value
taskStore.recordData.isBreakRules = value三个组件分别在各自的 change 事件中改对应字段。
易错点:不要写串字段;事故、车辆故障、交通违章是三处组件;value 是布尔值,不要写成字符串 "true"。
提交回车登记与结果验证
为什么需要:表单数据齐了以后要调用提交接口,成功后离开当前页,避免重复提交。
怎么想:在“提交登记”按钮上绑定 onFormSubmit,调用封装好的 recordAPI,传 recordData.value。请求成功后判断业务码,再 uni.reLaunch 回首页。
怎么做:
js
const res = await recordAPI(recordData.value)
if (res.code !== 200) {
uni.showToast({ title: '回车登记失败' })
return
}
uni.reLaunch({ url: '/' })易错点:传参别忘 .value;不要只看 HTTP 200,还要看业务码和响应数据;提交成功后不要在旧页面反复点击;首页待提货列表应减少一条,已完成列表应增加一条。
上报异常:选中样式与数据同步
为什么需要:原来异常类型只是字符串数组,只能显示文字,不能保存“是否选中”。用户反复选择时还会出现多个选中,回显也不准。
怎么想:把数组项改成对象:{ text: '发动机启动困难', checked: false }。text 用于显示,checked 用于控制选中。点击时根据 index 切换 checked;点确定时过滤 checked === true 的项,再取出 text 拼接。
怎么做:
js
list[index].checked = !list[index].checked
const selected = list
.filter(item => item.checked)
.map(item => item.text)
.join('|')易错点:用 const 定义后又重新赋值会报错,需要改 let;filter 后还要 map 取文字;回显字段不要带错;再次打开弹层时应保留已选项。
打包发布:H5、小程序、App
为什么需要:开发时用“运行”,交付时要“发行”。H5 打包后可上传服务器;小程序打包后导入微信开发者工具;App 云打包后下载安装包。
怎么想:H5 选“发行—网站”,生成 unpackage/dist/build/h5,可上传到阿里云前端网页托管;小程序选“发行—微信小程序”,填 AppID,生成 mp-weixin 后导入工具;App 先配 manifest.json 的图标、启动图、最低版本、摄像头模块,再云打包。
易错点:H5 免费托管每天前 10 个 IP 可访问,适合测试;小程序若未自动打开,可手动导入 unpackage/dist/build/mp-weixin;App 包名要两段点分三段且尽量唯一,测试可用公共证书,企业正式包应使用自有证书;启动图不同尺寸要选对。
代码示例
示例一:回车登记 —— 把运输ID / 出车时间写入 Pinia store(Vue3 + Pinia,可直接运行)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>回车登记 - 数据写入 store</title>
</head>
<body>
<div id="app">
<p>运输ID:{{ recordData.id }}</p>
<p>出车时间:{{ recordData.startTime }}</p>
<p>回车时间:{{ recordData.endTime }}</p>
<input v-model="recordData.endTime" placeholder="请选择回车时间" />
</div>
<!-- 用 importmap 让浏览器认识 vue / pinia -->
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"pinia": "https://unpkg.com/pinia@2/dist/pinia.esm-browser.js"
}
}
</script>
<script type="module">
import { createApp, onMounted } from 'vue'
// 注意:storeToRefs 必须从 pinia 引入,忘了引入就是 “storeToRefs is not defined”
import { createPinia, defineStore, storeToRefs } from 'pinia'
const useTaskStore = defineStore('task', {
state: () => ({
// 回车登记表单的“主数据”,三个缺失字段都往这里塞
recordData: {
id: '', // 运输ID
startTime: '', // 出车时间
endTime: '', // 回车时间
isAccident: false, // 是否有事故
isBreakRules: false, // 是否有故障
isViolation: false // 是否有违章
}
})
})
const App = {
setup() {
const store = useTaskStore()
// 关键:直接 const { recordData } = store 会丢失响应式
// 必须用 storeToRefs 解构,解构出来的是 ref
const { recordData } = storeToRefs(store)
onMounted(() => {
// 模拟 uni-app 中 onLoad(options) 从地址栏拿到的参数
const query = { id: '317', startTime: '2024-05-20 08:30' }
// 因为 recordData 是 ref,所以要 .value
recordData.value.id = query.id
recordData.value.startTime = query.startTime
})
return { recordData }
},
template: `
<p>运输ID:{{ recordData.id }}</p>
<p>出车时间:{{ recordData.startTime }}</p>
<p>回车时间:{{ recordData.endTime }}</p>
<input v-model="recordData.endTime" placeholder="请选择回车时间" />
`
}
createApp(App).use(createPinia()).mount('#app')
</script>
</body>
</html>要点解释 / 易错点
storeToRefs(store)是本节的核心:直接const { recordData } = store会让解构出来的数据丢失响应式,页面就不再跟着变;用storeToRefs解构出来是ref,模板里可以直接写recordData.id,但 JS 里必须写recordData.value.id。- 转写稿里“
storeToRefs未定义”的报错,就是漏了import { storeToRefs } from 'pinia'。 id不需要在 store 里重新定义,recordData里本来就有id字段,直接赋值即可。endTime用v-model="recordData.endTime",因为v-model可以直接修改 store 里的数据,选完时间就自动存进去了,不用再手写@change。- 三个布尔字段(事故 / 故障 / 违章)在 store 里先给默认值
false,后面由 radio 的change事件去翻转。
示例二:上报异常 —— 数组对象化 + 选中状态同步 + 确定时过滤回显
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>异常类型选择</title>
</head>
<body>
<div id="app">
<div class="list">
<label v-for="(item, index) in typeList" :key="index" style="display:block">
<!-- 用 :checked 绑定数据,change 里把索引传出去 -->
<input type="checkbox" :checked="item.checked" @change="onChange(index)" />
{{ item.text }}
</label>
</div>
<button @click="onConfirm">确定</button>
<p>异常类型回显:{{ abnormalType }}</p>
</div>
<script type="importmap">
{ "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } }
</script>
<script type="module">
import { createApp, reactive, ref } from 'vue'
const App = {
setup() {
// 数据由「字符串数组」改造成「对象数组」,才能同时承载文字和选中状态
const typeList = reactive([
{ text: '发动机启动困难', checked: true },
{ text: '不着车', checked: false },
{ text: '水温高', checked: false }
])
// 回显用的数据,注意用 let/ref,用 const 之后重新赋值会报错
const abnormalType = ref('')
// 点击某一项:拿到索引,翻转它的 checked,保证“样式”和“数据”同步
const onChange = (index) => {
typeList[index].checked = !typeList[index].checked
}
// 点确定:只把 checked 为 true 的取出来,再取 text,再用竖线拼起来
const onConfirm = () => {
abnormalType.value = typeList
.filter(item => item.checked === true)
.map(item => item.text)
.join('|')
}
return { typeList, abnormalType, onChange, onConfirm }
},
template: `
<div class="list">
<label v-for="(item, index) in typeList" :key="index" style="display:block">
<input type="checkbox" :checked="item.checked" @change="onChange(index)" />
{{ item.text }}
</label>
</div>
<button @click="onConfirm">确定</button>
<p>异常类型回显:{{ abnormalType }}</p>
`
}
createApp(App).mount('#app')
</script>
</body>
</html>要点解释 / 易错点
- 数组单元从“纯字符串”变成“对象”,所以
v-for里展示文字要写item.text,绑定选中要写item.checked;直接写item会把整个对象渲染出来。 - 复选框这里用
:checked而不是v-model,因为选中状态是手动维护在item.checked上的,change事件里必须把索引index传出去,v-for里记得写(item, index)。 onChange里做的是取反:原来true变false,原来false变true,这样页面上勾选的样式和typeList里的数据就永远一致。- 默认选中:只要把某个单元的
checked直接写成true即可,再次打开弹层也会保持上次的选中状态。 - 过滤时写
filter(item => item.checked === true),只保留选中项;再用map(item => item.text)去掉多余的checked属性;最后join('|')拼成字符串回显。 - 转写稿里出现的报错是:用
const定义了接收结果的数据后又给它重新赋值 —— 改成let或ref即可。
示例三:提交登记 —— 调用接口 + 判断 code + 跳转首页
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>回车登记 - 提交</title>
</head>
<body>
<div id="app">
<p>运输ID:{{ recordData.id }}</p>
<p>出车时间:{{ recordData.startTime }}</p>
<p>回车时间:{{ recordData.endTime }}</p>
<button @click="onSubmit">提交登记</button>
</div>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"pinia": "https://unpkg.com/pinia@2/dist/pinia.esm-browser.js"
}
}
</script>
<script type="module">
import { createApp } from 'vue'
import { createPinia, defineStore, storeToRefs } from 'pinia'
const useTaskStore = defineStore('task', {
state: () => ({
recordData: {
id: '317',
startTime: '2024-05-20 08:30',
endTime: '2024-05-20 18:00',
isAccident: false,
isBreakRules: false,
isViolation: false
}
})
})
// 模拟 api/task.js 里封装好的接口
const taskAPI = {
record: (data) => new Promise(resolve => {
setTimeout(() => resolve({ code: 200, data }), 500)
})
}
const App = {
setup() {
const store = useTaskStore()
const { recordData } = storeToRefs(store)
const onSubmit = async () => {
// 注意:storeToRefs 解构出来是 ref,传参必须加 .value
const res = await taskAPI.record(recordData.value)
if (res.code !== 200) {
alert('回车登记失败')
return
}
// 成功之后不能停留在当前页,否则用户可以重复提交
// uni-app 中写:uni.redirectTo({ url: '/pages/index/index' })
location.href = '/pages/index/index'
}
return { recordData, onSubmit }
},
template: `
<p>运输ID:{{ recordData.id }}</p>
<p>出车时间:{{ recordData.startTime }}</p>
<p>回车时间:{{ recordData.endTime }}</p>
<button @click="onSubmit">提交登记</button>
`
}
createApp(App).use(createPinia()).mount('#app')
</script>
</body>
</html>要点解释 / 易错点
- 接口封装时建议把
id一起带上并在注释里标明,调试时一旦发现参数丢了,能一眼定位是参数问题。 - 调用时传
recordData.value而不是recordData:storeToRefs解构出来的是ref,漏了.value传过去的就是一个 ref 对象,后端会收到一堆{_value: ...}的怪数据。 - 判断成功不要只看 HTTP 200,要看业务
code;转写稿里强调“两处都看”,即网络面板的响应体和服务端返回结果都要扫一眼,确认字段名和值都传上去了。 - 失败时
return掉,不能再往下走;成功时用redirectTo(重定向)跳首页,不能用普通跳转,否则当前页还能返回、能重复提交。 - 验证方式:提交成功后再回到首页,待提货里应该少一条、已完成里多一条,说明数据流转正确。
常见错误
页面能选中异常类型,但提交/回显数据不变
现象→勾选后样式变化,checked或回显内容仍为旧值。
原因→只改了视图状态,没有在change回调中按index同步源数据。
修法→回调接收索引,切换对应项的checked,提交前再filter出选中项。使用
storeToRefs报“未定义”
现象→控制台提示storeToRefs is not defined。
原因→未从 Pinia 正确引入该方法。
修法→补充import { storeToRefs } from 'pinia',再解构 store 中的响应式数据。回车登记提交参数为空或字段缺失
现象→请求发出,但载荷中id/startTime/endTime等缺失。
原因→storeToRefs解构后得到的是ref,提交时直接传对象未取.value;或字段未写入 store。
修法→提交前检查载荷,传recordData.value,并确认id/startTime/endTime已回填 store。用
const定义临时数组后又重新赋值报错
现象→过滤/映射后给临时变量重新赋值,控制台报赋值错误。
原因→const声明的基本引用不能重新赋值。
修法→需要重新赋值时改用let,或直接链式处理并返回新数组。H5 打包上传后页面可开但登录异常
现象→云托管域名能访问,点击登录报“不是函数”等问题。
原因→线上环境与本地运行环境差异、相关配置/代码未适配。
修法→先看控制台与网络请求定位,再补线上环境配置;不要只看打包成功。小程序发行后开发者工具未自动打开
现象→发行完成但微信开发者工具无项目。
原因→工具服务端口未开启或自动导入失败。
修法→检查开发者工具安全设置端口,手动导入unpackage/dist/build/mp-weixin。App 云打包后无法调用相机/上传照片
现象→安装包可运行,但拍照上传功能异常。
原因→打包前未勾选相机等对应模块。
修法→在manifest.json模块配置中勾选所需模块后重新云打包。App 包名随意填写导致打包失败或冲突
现象→云打包提示包名格式/重复问题。
原因→包名未按“两段点分三段”格式,或与他人重复。
修法→使用倒置域名风格并保证唯一,如com.姓名.项目名。
练习
修复异常上报选择状态:选择多个异常类型后点确定,要求页面回显、源数据
checked、再次打开弹层默认选中三者一致;验收时检查控制台过滤结果与回显文本。完成回车登记三字段回填与提交:从路由参数取
id写入 store,选择出车/回车时间后写入startTime/endTime,点击提交检查请求载荷字段是否完整。改造异常类型数据结构:把纯字符串数组改为包含
text/checked的对象数组,渲染标题与选中状态;提交前过滤checked === true并映射出文本,再拼接回显。H5 打包发布验证:发行 H5,上传到云托管,打开免费域名;检查页面加载、登录、接口请求与控制台报错,记录问题现象。
小程序与 App 打包检查:小程序发行后手动导入
mp-weixin目录并验证登录/列表;App 打包前配置 logo、启动图、最低版本、相机模块,使用唯一包名云打包并安装验证。
课堂画面






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 神领物流-回车登记-12 | 09:04 |
| 2 | 神领物流-回车登记-13 | 06:36 |
| 3 | 神领物流-回车登记-13 | 08:50 |
| 4 | 上报异常bug修复-1 | 20:52 |
| 5 | 打包发布-H5端 | 08:13 |
| 6 | 打包发布-小程序端 | 07:07 |
| 7 | 打包发布-App端 | 10:14 |
小结
- 回车登记类表单先确认字段来源,再写入 store,最后提交前核对载荷。
storeToRefs解构出的数据是ref,提交和赋值时注意.value。- 异常类型选择必须做到“样式状态”和“数据状态”同步。
- 回显内容建议先过滤选中项,再映射文本,最后拼接展示。
- 打包发布分端验证:H5 看线上控制台与请求,小程序看导入目录,App 看模块与包名。
- 云托管免费域名有访问限制,适合测试验证,不等同企业正式环境。
- App 云打包前至少检查 logo、启动图、版本、模块、包名和证书。
- 提交/发布后不要只看“成功”提示,要检查请求载荷、页面流转和实际功能。