Skip to content

在途管理与打包发布(三) ​

本页对应 在途管理与打包发布,共 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(重定向)跳首页,不能用普通跳转,否则当前页还能返回、能重复提交。
  • 验证方式:提交成功后再回到首页,待提货里应该少一条、已完成里多一条,说明数据流转正确。

常见错误 ​

  1. 页面能选中异常类型,但提交/回显数据不变
    现象→勾选后样式变化,checked 或回显内容仍为旧值。
    原因→只改了视图状态,没有在 change 回调中按 index 同步源数据。
    修法→回调接收索引,切换对应项的 checked,提交前再 filter 出选中项。

  2. 使用 storeToRefs 报“未定义”
    现象→控制台提示 storeToRefs is not defined。
    原因→未从 Pinia 正确引入该方法。
    修法→补充 import { storeToRefs } from 'pinia',再解构 store 中的响应式数据。

  3. 回车登记提交参数为空或字段缺失
    现象→请求发出,但载荷中 id/startTime/endTime 等缺失。
    原因→storeToRefs 解构后得到的是 ref,提交时直接传对象未取 .value;或字段未写入 store。
    修法→提交前检查载荷,传 recordData.value,并确认 id/startTime/endTime 已回填 store。

  4. 用 const 定义临时数组后又重新赋值报错
    现象→过滤/映射后给临时变量重新赋值,控制台报赋值错误。
    原因→const 声明的基本引用不能重新赋值。
    修法→需要重新赋值时改用 let,或直接链式处理并返回新数组。

  5. H5 打包上传后页面可开但登录异常
    现象→云托管域名能访问,点击登录报“不是函数”等问题。
    原因→线上环境与本地运行环境差异、相关配置/代码未适配。
    修法→先看控制台与网络请求定位,再补线上环境配置;不要只看打包成功。

  6. 小程序发行后开发者工具未自动打开
    现象→发行完成但微信开发者工具无项目。
    原因→工具服务端口未开启或自动导入失败。
    修法→检查开发者工具安全设置端口,手动导入 unpackage/dist/build/mp-weixin。

  7. App 云打包后无法调用相机/上传照片
    现象→安装包可运行,但拍照上传功能异常。
    原因→打包前未勾选相机等对应模块。
    修法→在 manifest.json 模块配置中勾选所需模块后重新云打包。

  8. App 包名随意填写导致打包失败或冲突
    现象→云打包提示包名格式/重复问题。
    原因→包名未按“两段点分三段”格式,或与他人重复。
    修法→使用倒置域名风格并保证唯一,如 com.姓名.项目名。

练习 ​

  1. 修复异常上报选择状态:选择多个异常类型后点确定,要求页面回显、源数据 checked、再次打开弹层默认选中三者一致;验收时检查控制台过滤结果与回显文本。

  2. 完成回车登记三字段回填与提交:从路由参数取 id 写入 store,选择出车/回车时间后写入 startTime/endTime,点击提交检查请求载荷字段是否完整。

  3. 改造异常类型数据结构:把纯字符串数组改为包含 text/checked 的对象数组,渲染标题与选中状态;提交前过滤 checked === true 并映射出文本,再拼接回显。

  4. H5 打包发布验证:发行 H5,上传到云托管,打开免费域名;检查页面加载、登录、接口请求与控制台报错,记录问题现象。

  5. 小程序与 App 打包检查:小程序发行后手动导入 mp-weixin 目录并验证登录/列表;App 打包前配置 logo、启动图、最低版本、相机模块,使用唯一包名云打包并安装验证。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1神领物流-回车登记-1209:04
2神领物流-回车登记-1306:36
3神领物流-回车登记-1308:50
4上报异常bug修复-120:52
5打包发布-H5端08:13
6打包发布-小程序端07:07
7打包发布-App端10:14

小结 ​

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