hookehuyr

docs: 更新项目文档和经验教训总结

- 更新 CLAUDE.md:添加 2026-02-12 最新变更记录
- 更新 README.md:完善项目说明和最新更新内容
- 更新经验教训总结:新增 3 个重要实践案例
  * 权限检查 Composable 模式
  * 全局状态管理与页面生命周期协调
  * 导航返回按钮与路由栈协调

记录了计划书模块、认证权限、消息功能等最佳实践

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -28,7 +28,7 @@ pnpm dev:tt # 字节跳动小程序开发 ...@@ -28,7 +28,7 @@ pnpm dev:tt # 字节跳动小程序开发
28 28
29 ## 📋 快速参考 29 ## 📋 快速参考
30 30
31 -### 🆕 最新更新(2026-02) 31 +### 🆕 最新更新(2026-02-12
32 32
33 **计划书功能优化** 33 **计划书功能优化**
34 - ✅ 添加计划书卡片状态标记("生成中"/"已完成",黄色/绿色背景) 34 - ✅ 添加计划书卡片状态标记("生成中"/"已完成",黄色/绿色背景)
...@@ -36,6 +36,23 @@ pnpm dev:tt # 字节跳动小程序开发 ...@@ -36,6 +36,23 @@ pnpm dev:tt # 字节跳动小程序开发
36 - ✅ 优化页面滚动加载并清理调试代码 36 - ✅ 优化页面滚动加载并清理调试代码
37 - ✅ 修复搜索栏清空按钮点击无效 37 - ✅ 修复搜索栏清空按钮点击无效
38 - ✅ 修改提交结果页按钮为"返回上一页" 38 - ✅ 修改提交结果页按钮为"返回上一页"
39 +- ✅ 预览成功后才调用查看接口,避免预览失败也翻状态
40 +- ✅ 优化计划书提交跳转体验:关闭弹框时清理已选产品
41 +- ✅ 提取计划书提交回调逻辑为 composable
42 +
43 +**认证与权限优化**
44 +- ✅ 为所有制作计划书按钮添加登录权限检查
45 +- ✅ 修复退出登录时红点状态未重置的问题
46 +- ✅ 修复登录页返回按钮:清空 router store 并跳转到首页
47 +- ✅ 修复 401 重定向死循环和返回报错问题
48 +
49 +**消息功能优化**
50 +- ✅ 配置 TabBar 红点功能使用新的 unread_msg_count 字段
51 +- ✅ 修复 TabBar 未读红点显示问题
52 +- ✅ 优化消息列表卡片布局,提升信息可读性
53 +- ✅ 增加未读消息红点提示
54 +- ✅ 优化消息详情页布局,避免内容重复显示
55 +- ✅ 添加消息列表 API 错误提示
39 56
40 **样式改进** 57 **样式改进**
41 - ✅ 增强资料卡片边框可见性(border-gray-200) 58 - ✅ 增强资料卡片边框可见性(border-gray-200)
...@@ -46,12 +63,21 @@ pnpm dev:tt # 字节跳动小程序开发 ...@@ -46,12 +63,21 @@ pnpm dev:tt # 字节跳动小程序开发
46 - ✅ 重构"我的"页面为专业高端风格 63 - ✅ 重构"我的"页面为专业高端风格
47 - ✅ 优化 ProductCard 组件视觉样式 64 - ✅ 优化 ProductCard 组件视觉样式
48 - ✅ 统一视觉柔和度和整体设计一致性 65 - ✅ 统一视觉柔和度和整体设计一致性
66 +- ✅ 优化首页头图 CDN 加载
49 67
50 -**认证优化** 68 +**计划书字段优化**
51 -- ✅ 修复 401 重定向死循环和返回报错问题 69 +- ✅ 优化提取金额字段并新增每年提取字段
70 +- ✅ 隐藏产品详情页附件下载提示
71 +- ✅ 优化输入框间距
52 72
53 **代码质量** 73 **代码质量**
54 - ✅ 从版本控制中移除本地配置文件 settings.local.json 74 - ✅ 从版本控制中移除本地配置文件 settings.local.json
75 +- ✅ 禁用消息列表 Mock 数据,使用真实接口
76 +- ✅ 清理调试日志
77 +
78 +**API 集成进度**
79 +- ✅ 总接口数:29,已完成:26 (89.7%)
80 +- ✅ 计划书模块接口联调完成(submitPlanAPI、listAPI)
55 81
56 **新增功能** 82 **新增功能**
57 - ✅ 消息列表和消息详情页 83 - ✅ 消息列表和消息详情页
...@@ -60,6 +86,7 @@ pnpm dev:tt # 字节跳动小程序开发 ...@@ -60,6 +86,7 @@ pnpm dev:tt # 字节跳动小程序开发
60 - ✅ 分类列表页 86 - ✅ 分类列表页
61 - ✅ PlanFields 表单字段组件集 87 - ✅ PlanFields 表单字段组件集
62 - ✅ useCollectOperation composable 88 - ✅ useCollectOperation composable
89 +- ✅ usePlanPermission composable
63 90
64 --- 91 ---
65 92
......
...@@ -45,7 +45,7 @@ pnpm lint ...@@ -45,7 +45,7 @@ pnpm lint
45 -**组件复用** - "第 3 次出现原则"抽取 Composables 45 -**组件复用** - "第 3 次出现原则"抽取 Composables
46 -**可复用组件** - TabBar、NavHeader、IconFont 46 -**可复用组件** - TabBar、NavHeader、IconFont
47 47
48 -## 🆕 最新更新(2026-02) 48 +## 🆕 最新更新(2026-02-12
49 49
50 ### 计划书功能优化 50 ### 计划书功能优化
51 -**状态标记** - 添加计划书卡片状态标记("生成中" / "已完成") 51 -**状态标记** - 添加计划书卡片状态标记("生成中" / "已完成")
...@@ -54,16 +54,38 @@ pnpm lint ...@@ -54,16 +54,38 @@ pnpm lint
54 - 使用条件类名动态切换样式 54 - 使用条件类名动态切换样式
55 -**查看状态更新** - 仅在预览成功后标记为已查看,返回列表不刷新位置 55 -**查看状态更新** - 仅在预览成功后标记为已查看,返回列表不刷新位置
56 -**提交跳转体验** - 提交后先关闭并重置弹框,再无固定延迟跳转结果页 56 -**提交跳转体验** - 提交后先关闭并重置弹框,再无固定延迟跳转结果页
57 --**返回重置体验** - 结果页返回后表单不保留旧数据 57 +-**返回重置体验** - 关闭弹框时清理已选产品,确保返回后表单为空
58 +-**字段优化** - 优化提取金额字段并新增每年提取字段
59 +
60 +### 认证与权限优化
61 +-**登录权限检查** - 为所有制作计划书按钮添加登录权限检查
62 +-**红点状态管理** - 修复退出登录时红点状态未重置的问题
63 +-**TabBar 红点** - 配置 TabBar 红点功能使用新的 unread_msg_count 字段
64 +-**登录页返回** - 修复登录页返回按钮,清空 router store 并跳转到首页
65 +-**401 修复** - 修复 401 重定向死循环和返回报错问题
66 +
67 +### 消息功能优化
68 +-**消息列表** - 优化消息列表卡片布局,提升信息可读性
69 +-**消息详情** - 优化消息详情页布局,避免内容重复显示
70 +-**未读提示** - 增加未读消息红点提示
71 +-**API 错误处理** - 添加消息列表 API 错误提示
58 72
59 ### 视觉优化 73 ### 视觉优化
60 -**首页网格导航** - 优化导航图标视觉体验 74 -**首页网格导航** - 优化导航图标视觉体验
61 -**产品卡片** - 优化 ProductCard 组件视觉样式 75 -**产品卡片** - 优化 ProductCard 组件视觉样式
62 -**页面风格** - 重构"我的"页面为专业高端风格 76 -**页面风格** - 重构"我的"页面为专业高端风格
63 -**统一视觉** - 优化视觉柔和度和整体统一性 77 -**统一视觉** - 优化视觉柔和度和整体统一性
78 +-**头图优化** - 优化首页头图 CDN 加载
79 +
80 +### API 集成
81 +-**接口联调完成** - 计划书模块接口联调完成(submitPlanAPI、listAPI)
82 +-**总进度** - 29 个接口,已完成 26 个(89.7%)
83 +-**关闭 Mock** - 禁用消息列表 Mock 数据,使用真实接口
64 84
65 ### 代码质量 85 ### 代码质量
66 -**移除本地配置** - 从版本控制中移除本地配置文件 settings.local.json 86 -**移除本地配置** - 从版本控制中移除本地配置文件 settings.local.json
87 +-**Composable 抽取** - 提取计划书提交回调逻辑为 composable
88 +-**清理调试日志** - 清理项目中的调试日志
67 -**文档更新** - 完善开发文档和经验教训总结 89 -**文档更新** - 完善开发文档和经验教训总结
68 90
69 ## ⚡ 常见问题 91 ## ⚡ 常见问题
......
...@@ -1200,6 +1200,304 @@ const fetchList = async (params) => { ...@@ -1200,6 +1200,304 @@ const fetchList = async (params) => {
1200 } 1200 }
1201 ``` 1201 ```
1202 1202
1203 +### ✅ 5. 权限检查 Composable 模式 ⭐ 2026-02-12 新增
1204 +
1205 +**场景描述**:
1206 +
1207 +项目中需要在多个入口点(如首页、搜索页、产品中心页、产品详情页)添加"制作计划书"按钮的登录权限检查功能。
1208 +
1209 +**问题**:
1210 +- 如果在每个页面都重复写权限检查逻辑,会导致代码重复
1211 +- 权限逻辑分散在各个页面,难以统一维护
1212 +
1213 +**解决方案:抽取为 Composable**
1214 +
1215 +创建 `src/composables/usePlanPermission.js`:
1216 +
1217 +```javascript
1218 +/**
1219 + * 计划书权限检查 Composable
1220 + *
1221 + * @description 检查用户是否已登录,未登录时引导用户登录
1222 + * @returns {Object} { checkPlanPermission } 权限检查函数
1223 + */
1224 +import { useUserStore } from '@/stores/user'
1225 +import Taro from '@tarojs/taro'
1226 +
1227 +export function usePlanPermission() {
1228 + const userStore = useUserStore()
1229 +
1230 + /**
1231 + * 检查计划书权限
1232 + *
1233 + * @param {Function} callback - 权限验证通过后的回调函数
1234 + * @description 如果用户未登录,显示登录提示;否则执行回调函数
1235 + */
1236 + const checkPlanPermission = (callback) => {
1237 + console.log('[usePlanPermission] 检查登录状态:', userStore.isLoggedIn)
1238 +
1239 + if (!userStore.isLoggedIn) {
1240 + // 未登录:显示登录提示
1241 + Taro.showModal({
1242 + title: '提示',
1243 + content: '请先登录后再制作计划书',
1244 + confirmText: '去登录',
1245 + cancelText: '取消',
1246 + success: (res) => {
1247 + if (res.confirm) {
1248 + // 跳转到登录页
1249 + Taro.navigateTo({
1250 + url: '/pages/login/index'
1251 + })
1252 + }
1253 + }
1254 + })
1255 + } else {
1256 + // 已登录:执行回调函数
1257 + callback?.()
1258 + }
1259 + }
1260 +
1261 + return {
1262 + checkPlanPermission
1263 + }
1264 +}
1265 +```
1266 +
1267 +**使用方式**:
1268 +
1269 +```vue
1270 +<script setup>
1271 +import { usePlanPermission } from '@/composables/usePlanPermission'
1272 +
1273 +const { checkPlanPermission } = usePlanPermission()
1274 +
1275 +const handlePlanClick = () => {
1276 + checkPlanPermission(() => {
1277 + // 权限验证通过后的操作
1278 + openPlanPopup()
1279 + })
1280 +}
1281 +</script>
1282 +
1283 +<template>
1284 + <button @tap="handlePlanClick">制作计划书</button>
1285 +</template>
1286 +```
1287 +
1288 +**收益**:
1289 +- ✅ 减少代码重复,统一权限检查逻辑
1290 +- ✅ 易于维护:修改权限逻辑只需更新一处
1291 +- ✅ 复用性强:任何需要权限检查的地方都可以使用
1292 +
1293 +**适用场景**:
1294 +- 需要登录才能使用的功能
1295 +- 需要特定权限才能访问的功能
1296 +- 需要统一权限检查逻辑的场景
1297 +
1298 +---
1299 +
1300 +### ✅ 6. 全局状态管理与页面生命周期协调 ⭐ 2026-02-12 新增
1301 +
1302 +**场景描述**:
1303 +
1304 +项目中需要管理 TabBar 红点的显示状态,红点数据来自用户信息接口返回的 `unread_msg_count` 字段。当用户退出登录时,需要清除红点状态。
1305 +
1306 +**问题**:
1307 +- 红点状态存储在用户 store 中
1308 +- 退出登录时需要重置红点状态
1309 +- 登录后需要自动显示/隐藏红点
1310 +
1311 +**解决方案:在登录/登出时管理红点状态**
1312 +
1313 +**1. 登录成功后显示红点**:
1314 +
1315 +```javascript
1316 +// src/pages/login/index.vue
1317 +const handleLogin = async () => {
1318 + try {
1319 + const res = await loginAPI({ phone, code })
1320 +
1321 + if (res.code === 1) {
1322 + // 更新用户信息(包含 unread_msg_count)
1323 + userStore.setUserInfo(res.data.userInfo)
1324 +
1325 + // TabBar 会自动读取 userStore.unread_msg_count
1326 + // 如果大于 0,TabBar 组件会自动显示红点
1327 + }
1328 + } catch (err) {
1329 + console.error('登录失败:', err)
1330 + }
1331 +}
1332 +```
1333 +
1334 +**2. 退出登录时清除红点**:
1335 +
1336 +```javascript
1337 +// src/stores/user.js
1338 +export const useUserStore = defineStore('user', () => {
1339 + const userInfo = ref(null)
1340 + const unreadMsgCount = ref(0) // 红点数量
1341 +
1342 + const setUserInfo = (info) => {
1343 + userInfo.value = info
1344 + // 从用户信息中提取未读消息数量
1345 + unreadMsgCount.value = info.unread_msg_count || 0
1346 + }
1347 +
1348 + const logout = () => {
1349 + userInfo.value = null
1350 + unreadMsgCount.value = 0 // 清除红点状态 ⚠️ 重要
1351 + Taro.clearStorageSync() // 清除本地缓存
1352 + }
1353 +
1354 + return {
1355 + userInfo,
1356 + unreadMsgCount,
1357 + setUserInfo,
1358 + logout
1359 + }
1360 +})
1361 +```
1362 +
1363 +**3. TabBar 组件读取红点状态**:
1364 +
1365 +```vue
1366 +<!-- src/components/TabBar.vue -->
1367 +<script setup>
1368 +import { useUserStore } from '@/stores/user'
1369 +
1370 +const userStore = useUserStore()
1371 +const hasUnread = computed(() => userStore.unreadMsgCount > 0)
1372 +</script>
1373 +
1374 +<template>
1375 + <view class="tabbar-item">
1376 + <image :src="iconPath" />
1377 + <view class="badge" v-if="hasUnread"></view> <!-- 红点 -->
1378 + </view>
1379 +</template>
1380 +```
1381 +
1382 +**关键要点**:
1383 +1. ✅ **状态同步**:登录后从 `userInfo` 中提取 `unread_msg_count`
1384 +2. ✅ **状态清除**:退出登录时必须重置 `unreadMsgCount = 0`
1385 +3. ✅ **响应式更新**:使用 `computed` 自动响应状态变化
1386 +4. ✅ **单一数据源**:红点状态只由 `userStore` 管理
1387 +
1388 +**注意事项**:
1389 +- ⚠️ **必须在退出时清除状态**:否则下次登录会显示错误的红点
1390 +- ⚠️ **字段名对齐**:确保后端返回字段名与前端一致(`unread_msg_count`)
1391 +- ⚠️ **类型转换**:如果后端返回字符串,需要转换为数字
1392 +
1393 +---
1394 +
1395 +### ✅ 7. 导航返回按钮与路由栈协调 ⭐ 2026-02-12 新增
1396 +
1397 +**场景描述**:
1398 +
1399 +当用户从 401 错误被重定向到登录页后,点击返回按钮会出现问题,因为导航栈已被清空(`router store` 中存储的路径已丢失)。
1400 +
1401 +**问题**:
1402 +- 401 重定向后,`router store` 被清空
1403 +- 登录页点击返回按钮时,没有历史页面可以返回
1404 +- 用户被困在登录页,无法返回首页
1405 +
1406 +**解决方案 1:检测路由栈,为空时跳转首页**
1407 +
1408 +```vue
1409 +<!-- src/pages/login/index.vue -->
1410 +<script setup>
1411 +import { useRouterStore } from '@/stores/router'
1412 +
1413 +const routerStore = useRouterStore()
1414 +
1415 +const handleBack = () => {
1416 + // 检查路由栈是否有历史记录
1417 + if (routerStore.history && routerStore.history.length > 0) {
1418 + // 有历史记录:正常返回
1419 + Taro.navigateBack()
1420 + } else {
1421 + // 无历史记录:清空 router store 并跳转首页
1422 + routerStore.clearHistory() // 清空路由栈
1423 + Taro.switchTab({
1424 + url: '/pages/index/index'
1425 + })
1426 + }
1427 +}
1428 +</script>
1429 +
1430 +<template>
1431 + <NavHeader @back="handleBack" />
1432 +</template>
1433 +```
1434 +
1435 +**解决方案 2:NavHeader 组件提供 `preventDefaultBack` prop**
1436 +
1437 +```vue
1438 +<!-- src/components/navigation/NavHeader.vue -->
1439 +<script setup>
1440 +const props = defineProps({
1441 + preventDefaultBack: {
1442 + type: Boolean,
1443 + default: false
1444 + }
1445 +})
1446 +
1447 +const emit = defineEmits(['back'])
1448 +
1449 +const handleBack = () => {
1450 + if (props.preventDefaultBack) {
1451 + // 阻止默认返回,由父组件处理
1452 + emit('back')
1453 + } else {
1454 + // 默认返回行为
1455 + Taro.navigateBack()
1456 + }
1457 +}
1458 +</script>
1459 +
1460 +<template>
1461 + <view class="nav-header" @tap="handleBack">
1462 + <image class="back-icon" src="/assets/images/back.png" />
1463 + </view>
1464 +</template>
1465 +```
1466 +
1467 +**在登录页使用**:
1468 +
1469 +```vue
1470 +<!-- src/pages/login/index.vue -->
1471 +<script setup>
1472 +import { useRouterStore } from '@/stores/router'
1473 +
1474 +const routerStore = useRouterStore()
1475 +
1476 +const handleBack = () => {
1477 + // 清空路由栈并跳转首页
1478 + routerStore.clearHistory()
1479 + Taro.switchTab({
1480 + url: '/pages/index/index'
1481 + })
1482 +}
1483 +</script>
1484 +
1485 +<template>
1486 + <NavHeader :prevent-default-back="true" @back="handleBack" />
1487 +</template>
1488 +```
1489 +
1490 +**关键要点**:
1491 +1. ✅ **路由栈管理**:使用 `routerStore` 维护导航历史
1492 +2. ✅ **状态检测**:检查路由栈是否有历史记录
1493 +3. ✅ **降级方案**:无历史记录时,跳转到首页
1494 +4. ✅ **组件通信**:通过 `@back` 事件和 `preventDefaultBack` prop 协调返回行为
1495 +
1496 +**收益**:
1497 +- ✅ 避免用户被困在登录页
1498 +- ✅ 提供更好的用户体验
1499 +- ✅ 组件职责清晰,易于复用
1500 +
1203 --- 1501 ---
1204 1502
1205 ## Vue 3 响应式数据和表单状态管理 1503 ## Vue 3 响应式数据和表单状态管理
......