主题
表单验证与任务通知(三)
本页对应 表单验证与任务通知,共 6 段视频,约 51 分钟。
学习目标
- 能够根据接口文档定位测试接口返回 500 的原因,说明缺失必传参数(如配置、分页参数、contentType 等)与 500 响应的关系,并通过补齐参数验证接口恢复 200。
- 能够独立封装用户信息查询 API,在页面加载生命周期中调用该 API,检查响应状态码后将用户信息以响应式数据渲染到页面。
- 能够说明 uni-app 中
keep-alive的跨端限制,并使用v-if与v-show配合实现消息模块组件的首次加载与后续显示/隐藏缓存。 - 能够按“先封装接口方法、再在组件中调用、最后在生命周期中触发”的流程完成任务通知列表数据获取,并正确传入
contentType、页码和每页条数等参数。 - 能够使用
v-for与唯一key渲染任务通知列表,根据isRead控制未读样式,并通过列表长度判断和展示空数据提示。
知识点
作业目标:把各小节标注的任务落到代码里
本节作业不是新增一个大功能,而是完成今天各小节标注的任务:先解释并处理测试接口返回 500 的问题,再完成“我的”用户信息接口调用与渲染,理解消息模块的通知/公告共用接口,处理 uni-app 中组件缓存问题,最后完成任务通知列表的接口封装、调用、渲染和空状态判断。下面按“为什么需要 → 怎么想 → 怎么做 → 易错点”整理。
测试接口返回 500 的排查与处理
为什么需要:登录后测试接口仍报 500,容易让人误以为登录逻辑没写对。转写中明确说“这不赖我们了,这是后端接口的问题”,但必须学会看网络面板定位原因。
怎么想:先看请求 URL 和接口文档。转写中调用的是任务列表 driver task list 一类接口,文档要求必须传 contentType、page、pageSize 等参数;缺参时后端直接返回 500。测试阶段 401、500 不影响登录功能本身,因为除登录接口外,多数接口都要登录后才能用。
怎么做:在测试请求的 data 中补齐必传参数。转写中 pageSize 给 10,page 给 1,contentType 按接口文档或已有测试值填写。补完后重新保存、刷新,状态码从 500 变为 200,并能返回 10 条数据。测试代码若不再需要可删除;若保留,就必须把参数补齐。
js
// 测试请求中补齐接口要求的参数(具体值以接口文档为准)
data: {
contentType: '按文档填写',
page: 1,
pageSize: 10
}易错点:只看控制台不看网络面板;把缺参导致的 500 误判为登录失败;参数名写成 content、pageSize 拼错;测试代码保留却不补参数。
用户信息接口封装与页面渲染
为什么需要:“我的”页面要展示头像、姓名、司机编号、手机号,这些数据不能写死,必须调用户信息接口。
怎么想:接口文档说明用户信息用 GET,不需要业务参数,但必须带 token。按“先封装接口,再在页面调用”的顺序做。转写中强调:先 console.log 看完整返回,再解构,不要上来就解构。
怎么做:在 apis/user.js 增加 profile 方法;在 my/index 页面用组合式 API 的 onLoad 调用;检测 code 是否为 200,再把对象数据赋给 ref。转写中通过 Vue 调试工具确认 userProfile 已有数据,再渲染到页面。
js
// apis/user.js
export const profile = () => {
return uni.request({
url: '按接口文档填写',
method: 'GET'
})
}
// my/index
import { onLoad } from '@dcloudio/uni-app'
import { profile } from '@/apis/user'
import { ref } from 'vue'
const userProfile = ref({})
const getUser = async () => {
const res = await profile()
console.log(res) // 先看结构,再解构
if (res.code !== 200) return
userProfile.value = res.data
}
onLoad(() => {
getUser()
})易错点:组合式 API 中不要用 this;小程序没有 Vue Router 概念,不要照搬 vue-router;先打印再解构;不判断 code 就继续执行;头像、姓名、司机编号、手机号字段名要双击复制,避免手写错。
消息模块与组件缓存
为什么需要:消息模块包含任务通知和公告,两块共用一个列表接口,通过 contentType 区分;页面切换时如果组件反复销毁重建,交互状态会丢。
怎么想:转写中先想用 keep-alive,但 uni-app 小程序端不支持,只能 H5 用。于是改用 v-if 首次加载、v-show 后续显隐:第一次点开时用 v-if 把组件加载出来,之后只切换显示隐藏,不再销毁。
怎么做:定义两个 tab 数据项,每项有 render 标记;点击某个 tab 时把对应 render 设为 true,且不再设回 false;模板中第一个组件写 v-if="tabs[0].render" v-show="activeIndex === 0",第二个同理。
html
<first v-if="tabs[0].render" v-show="activeIndex === 0" />
<second v-if="tabs[1].render" v-show="activeIndex === 1" />易错点:以为 keep-alive 在所有端都能用;把 render 又设回 false 导致组件销毁;v-if 和 v-show 条件写反;只写 v-show 会导致首次不加载。
任务通知列表:先封装接口再调用
为什么需要:任务通知列表要展示通知数据,并支持分页、下拉刷新、上拉加载、全部已读等交互,第一步必须拿到列表数据。
怎么想:转写中反复强调“凡是涉及接口,第一步先封装调用接口的方法;第二步到页面或组件中调用;第三步结合生命周期取数据”。消息相关接口放到 apis/message.js,列表方法参数为 contentType、page、pageSize,默认 page=1、pageSize=10。
怎么做:在 notify 组件中引入 message 的 list 方法,在 onMounted 中调用;先打印结果,再判断 code。转写中第一次漏传 contentType,页面报 500;补上后请求成功,返回 30 条,当前拿到 0—9 条。
js
// apis/message.js
export const list = (contentType, page = 1, pageSize = 10) => {
return uni.request({
url: '按接口文档填写',
method: 'GET',
data: { contentType, page, pageSize }
})
}易错点:参数名 contentType 写错;漏传必传参数导致 500;组件中用错生命周期;先解构后打印,出错时看不出原始结构。
任务通知列表渲染与空状态
为什么需要:列表可能有数据也可能没有数据,严谨页面必须有空提示;未读消息还要显示小红点。
怎么想:先把列表数据赋给 ref,再判断 notifyList.value.length === 0 得到 isEmpty;模板中 v-for 渲染列表,key 用 id;未读判断看 isRead,0 表示未读,取反后绑定类名。
怎么做:请求成功后把真正的列表数组赋给 notifyList.value,不要写顺手写成整个 data;空状态变量初始为 false,请求后按长度判断;跳转详情地址改为任务详情并带 id。
html
<div v-for="item in notifyList" :key="item.id">
<div :class="{ unread: !item.isRead }">{{ item.content }}</div>
<div>{{ item.created }}</div>
</div>
<div v-if="isEmpty">暂无消息</div>易错点:列表数据层级看错;key 不用 id;未读判断漏了取反;空状态初始值写错;有数据时看到 isEmpty 为 false 就以为写错,其实这是正常结果。
作业检查清单
完成后逐项检查:测试接口 500 是否已解释并补参;用户信息接口是否封装、调用、渲染成功;消息模块是否理解通知/公告共用接口;组件缓存是否用 v-if + v-show 替代 keep-alive;任务通知列表是否先封装再调用;contentType、page、pageSize 是否齐全;列表是否渲染、是否处理空状态和未读红点。转写中代码已给出参考,作业时不要只抄结果,要按“先封装、再调用、先打印、再解构、再渲染”的顺序自己走一遍。
代码示例
示例 1:用户信息接口封装 + “我的”页面加载渲染
src/apis/user.js
js
import { http } from '@/utils/uni-fetch'
// 登录(上午已写)
export const login = (data) => {
return http({ url: '/driver/login/account', method: 'POST', data })
}
// 用户信息:不需要任何参数,只要请求头带 token 就能拿到数据
export const profile = () => {
return http({
url: '/driver/profile', // 以接口文档为准
method: 'GET'
})
}src/pages/my/index.vue
vue
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { profile } from '@/apis/user'
// 页面要渲染的数据必须是响应式的
const userProfile = ref({})
const getUser = async () => {
const res = await profile()
console.log(res) // 第一步先打印,别急着解构
if (res.code !== 200) return // code 不对,后面逻辑白执行
userProfile.value = res.data
}
// 页面加载时调用
onLoad(() => {
getUser()
})
</script>
<template>
<view class="user-info">
<image class="avatar" :src="userProfile.avatar" />
<view class="name">{{ userProfile.name }}</view>
<view class="number">{{ userProfile.number }}</view>
<view class="mobile">{{ userProfile.mobile }}</view>
</view>
</template>关键行与易错点
import { onLoad } from '@dcloudio/uni-app':小程序里没有 vue-router,onLoad是 uni-app 的生命周期,页面加载即发请求。const userProfile = ref({}):数据要渲染到页面,必须用ref包成响应式;赋值时不能写userProfile = res.data,必须写userProfile.value = res.data(Vue3 新手最容易漏.value)。if (res.code !== 200) return:先校验code再赋值,避免脏数据。- 调试时先
console.log(res)看结构,不要一上来就解构,否则解构不出数据时很难定位问题。 - 相关易错点(视频 1):调任务列表
list接口时,文档要求contentType、page、pageSize三个参数都是必传,少传后端直接返回 500。测试请求也要把参数补齐:
js
const res = await http({
url: '/driver/task/list',
method: 'GET',
data: { contentType: '1', page: 1, pageSize: 10 }
})示例 2:消息列表接口封装 + 任务通知列表渲染(含空提示、未读红点)
src/apis/message.js
js
import { http } from '@/utils/uni-fetch'
export default {
// 消息列表:contentType 200=任务通知,201=公告
// page/pageSize 给默认值,调用时可以只传 contentType
list(contentType, page = 1, pageSize = 10) {
return http({
url: '/driver/message/list',
method: 'GET',
data: { contentType, page, pageSize }
})
}
}src/pages/message/components/notify.vue
vue
<script setup>
import { ref, onMounted } from 'vue'
import messageApi from '@/apis/message'
const notifyList = ref([]) // 任务通知列表
const isEmpty = ref(false) // 列表是否为空
const getNotify = async () => {
const res = await messageApi.list(201)
console.log(res)
if (res.code !== 200) {
uni.showToast({ title: '获取消息失败', icon: 'none' })
return
}
// 真正的列表数据在 data.items 里,不是 data 本身
notifyList.value = res.data.items
isEmpty.value = notifyList.value.length === 0
}
// 组件中发请求,推荐使用 vue 的生命周期
onMounted(() => {
getNotify()
})
const toDetail = (id) => {
uni.navigateTo({ url: `/subpkg/task/detail?id=${id}` })
}
</script>
<template>
<view class="notify">
<view
v-for="item in notifyList"
:key="item.id"
class="card"
@click="toDetail(item.id)"
>
<view class="content">{{ item.content }}</view>
<view class="time">{{ item.created }}</view>
<!-- isRead 为 0 表示未读,取反后为 true 才加 unread 类名 -->
<view class="dot" :class="{ unread: !item.isRead }"></view>
</view>
<!-- 空提示:v-for 本身不会渲染,这里不需要写 v-else -->
<view v-if="isEmpty" class="empty">暂无消息</view>
</view>
</template>关键行与易错点
page = 1, pageSize = 10:给形参默认值,调用时messageApi.list(201)只传一个参数即可,代码更干净。- 调用接口必须传
contentType(200 通知 / 201 公告),不传同样返回 500;参数名和值直接从接口文档复制,不要手敲。 res.data.items:分页接口返回里还有count、pageSize等字段,列表数据在items里,顺手写res.data会渲染不出来。:class="{ unread: !item.isRead }":isRead为0(未读)时取反得true,才显示小红点;为1时不加类名。v-for必须写:key="item.id",优先用数据里自带的id。- 空提示用
v-if="isEmpty"单独判断即可,v-for遍历空数组本来就不渲染,不需要再写v-else。 - 列表跳转地址注意是任务详情(
/subpkg/task/detail)而不是消息详情,并把id通过 query 带过去。
示例 3:消息页 Tab 切换 + 组件缓存(v-if 配合 v-show)
src/pages/message/index.vue
vue
<script setup>
import { ref } from 'vue'
import Notify from './components/notify.vue'
import Notice from './components/notice.vue'
// 每个 tab 带一个 render 标记:是否已经首次加载过
const tabList = ref([
{ title: '任务通知', render: false },
{ title: '公告', render: false }
])
const currentIndex = ref(0)
const switchTab = (index) => {
currentIndex.value = index
// 只置为 true,不再改回 false —— 组件一旦加载就一直保留
tabList.value[index].render = true
}
</script>
<template>
<view class="message">
<view class="tab-bar">
<view
v-for="(item, index) in tabList"
:key="index"
class="tab"
:class="{ active: currentIndex === index }"
@click="switchTab(index)"
>
{{ item.title }}
</view>
</view>
<!-- v-if 负责首次加载,v-show 负责之后显示/隐藏,变相实现 keep-alive -->
<Notify v-if="tabList[0].render" v-show="currentIndex === 0" />
<Notice v-if="tabList[1].render" v-show="currentIndex === 1" />
</view>
</template>关键行与易错点
- uni-app 小程序端
keep-alive不支持(只支持 H5),所以不能用原生组件缓存,这里用v-if + v-show组合替代。 v-if="tabList[0].render":第一次点开该 tab 时才创建组件(render由false变true),组件内的onMounted只执行一次。v-show="currentIndex === 0":之后切换只做显示隐藏,组件实例和数据都保留,切回来不用重新请求。- 易错点:
render只置true,千万不要在switchTab里把它改回false,否则等于每次切换都销毁重建,缓存就失效了。 v-if和v-show同时写在一个标签上时,v-if优先级更高,先判断是否加载、再判断是否显示,顺序不能反。- 同样的思路后面任务模块的切换也能复用。
常见错误
测试任务列表接口报 500,误以为登录逻辑写错
- 现象:登录成功、token 已带上,但
driverTask/list请求返回 500,页面报错。 - 原因:该接口有三个必传参数(
configType、page、pageSize),测试时用uni.request直接发请求没走data传参,后端缺参直接 500。 - 修法:在请求配置里补
data: { configType: 1, page: 1, pageSize: 10 };或者干脆删掉这段测试代码——它只是验证拦截器用的,正式页面不会用。
- 现象:登录成功、token 已带上,但
拿到接口响应后直接解构,结果
undefined或渲染空白- 现象:
const { code, data } = res之后data.xxx报错,或页面什么都不显示。 - 原因:没确认返回结构就写解构,字段层级(
res.data.items/res.data.counts)猜错了。 - 修法:先
console.log(res)打印完整响应,在浏览器 Vue Devtools 里确认层级后再解构;解构前先判code !== 200直接return+uni.showToast,避免后续逻辑白跑。
- 现象:
列表渲染不出来,或把整个分页对象 v-for 了一遍
- 现象:
v-for无内容,或页面上出现奇怪的对象字段。 - 原因:把
res.data整个赋给了列表变量,而真正的数组在res.data.items里(同级还有counts、pageSize)。 - 修法:
list.value = res.data.items;赋值后立刻在 Devtools 里检查该 ref 是否有 10 个元素、是否被v-for正确消费;同时补上:key="item.id",不要用 index。
- 现象:
在组件里调接口用了
onLoad,请求不触发- 现象:页面能进,但接口一直没发出去,数据为空。
- 原因:
onLoad是页面生命周期,在components/下的组件中不生效;组件里要用 Vue 生命周期。 - 修法:页面用
onLoad,组件内用onMounted(从vue引入);团队内约定"组件一律用 Vue 生命周期",避免混乱。
切换 tab 时组件状态丢失,想用
keep-alive但小程序端不生效- 现象:两个 tab 来回切,已加载的列表重新请求、滚动位置丢失。
- 原因:
keep-alive在 uni-app 中仅 H5 支持,小程序端不支持,跨端方案里不能依赖它。 - 修法:
v-if+v-show组合——首次进入用v-if挂载(配合一个render[i]标记置 true),之后显隐全部交给v-show;标记一旦置 true 就不再回退。
未读小红点判断写反,已读的反而显示红点
- 现象:所有消息都带红点,或已读消息也标红。
- 原因:接口
isRead中0表示未读、1表示已读,直接:class="{ unread: item.isRead }"语义就颠倒了。 - 修法:写成
:class="{ unread: !item.isRead }";并在 Devtools 里挑一条isRead=1的数据验证类名确实消失。
练习
封装消息列表 API:在
apis/message.js中新增list方法,三个必传参数中page、pageSize给默认值(1 / 10),并补上 JSDoc 注释说明参数含义。写完后在调用处验证:只传configType也能正常拿到 10 条数据。列表空态:给任务通知列表加
isEmpty判断,要求"接口返回后再判定、初始不显示空提示",并在 Devtools 中确认有数据时该值为false、模拟空数组时提示正常出现。思考:为什么空态不需要写v-else。组件缓存改造:用
v-if+v-show+render数组实现"任务通知/公告"两个 tab 的缓存,要求首次切换时组件才挂载、二次切换不再重新请求接口。写完后说明:如果强行用keep-alive在小程序端会发生什么。未读红点:根据
isRead字段完成小红点绑定,并额外实现"全部已读"点击后把列表中所有项标记为已读(本地先改数据,再考虑是否需要重新请求接口)。故障排查:故意删掉
configType参数发起请求,按"看 Network 请求 URL 与 query → 看响应体 → 对照接口文档必填项"的顺序定位问题,把排查过程写成三行以内的结论。
课堂画面






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 解释测试接口返回500的原因 | 04:09 |
| 2 | 神领物流-用户信息 | 12:11 |
| 3 | 神领物流-消息模块介绍 | 03:32 |
| 4 | 神领物流-消息组件缓存 | 07:08 |
| 5 | 神领物流-任务通知列表-1 | 11:51 |
| 6 | 神领物流-任务通知列表-2 | 12:57 |
小结
- 接口报错先分清责任方:500 优先怀疑参数缺失/后端校验,401 优先怀疑 token 与拦截器,不要一上来就改登录逻辑。
- 调接口固定三步:先封装 API 方法(带注释和默认参数)→ 在页面/组件中写方法调用 → 由生命周期触发,顺序别乱。
- "先打印、再解构、先判 code、再取数"应固化成肌肉记忆,能省掉大部分调试时间。
- 列表功能的标准三件套:正确取到数组字段、
:key用业务 id、空态提示——缺一个都算没写完。 - 跨端项目要提前确认 API 的端能力边界(如
keep-alive仅 H5),能力缺失时用组合方案兜底而不是硬扛。 - 小程序环境没有
vue-router,跳转与传参要走平台自己的路由方式,跳转地址和携带的 id 都要逐字核对。 - Vue Devtools 是验证响应式数据的首选手段,比
console.log更直观,应作为每次渲染前的固定检查动作。 - 为验证某个机制而写的临时代码(如登录后的测试请求),完成后要么删掉、要么补齐参数,别留成"看起来有 bug"的历史包袱。