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 # 字节跳动小程序开发
## 📋 快速参考
### 🆕 最新更新(2026-02)
### 🆕 最新更新(2026-02-12
**计划书功能优化**
- ✅ 添加计划书卡片状态标记("生成中"/"已完成",黄色/绿色背景)
......@@ -36,6 +36,23 @@ pnpm dev:tt # 字节跳动小程序开发
- ✅ 优化页面滚动加载并清理调试代码
- ✅ 修复搜索栏清空按钮点击无效
- ✅ 修改提交结果页按钮为"返回上一页"
- ✅ 预览成功后才调用查看接口,避免预览失败也翻状态
- ✅ 优化计划书提交跳转体验:关闭弹框时清理已选产品
- ✅ 提取计划书提交回调逻辑为 composable
**认证与权限优化**
- ✅ 为所有制作计划书按钮添加登录权限检查
- ✅ 修复退出登录时红点状态未重置的问题
- ✅ 修复登录页返回按钮:清空 router store 并跳转到首页
- ✅ 修复 401 重定向死循环和返回报错问题
**消息功能优化**
- ✅ 配置 TabBar 红点功能使用新的 unread_msg_count 字段
- ✅ 修复 TabBar 未读红点显示问题
- ✅ 优化消息列表卡片布局,提升信息可读性
- ✅ 增加未读消息红点提示
- ✅ 优化消息详情页布局,避免内容重复显示
- ✅ 添加消息列表 API 错误提示
**样式改进**
- ✅ 增强资料卡片边框可见性(border-gray-200)
......@@ -46,12 +63,21 @@ pnpm dev:tt # 字节跳动小程序开发
- ✅ 重构"我的"页面为专业高端风格
- ✅ 优化 ProductCard 组件视觉样式
- ✅ 统一视觉柔和度和整体设计一致性
- ✅ 优化首页头图 CDN 加载
**认证优化**
- ✅ 修复 401 重定向死循环和返回报错问题
**计划书字段优化**
- ✅ 优化提取金额字段并新增每年提取字段
- ✅ 隐藏产品详情页附件下载提示
- ✅ 优化输入框间距
**代码质量**
- ✅ 从版本控制中移除本地配置文件 settings.local.json
- ✅ 禁用消息列表 Mock 数据,使用真实接口
- ✅ 清理调试日志
**API 集成进度**
- ✅ 总接口数:29,已完成:26 (89.7%)
- ✅ 计划书模块接口联调完成(submitPlanAPI、listAPI)
**新增功能**
- ✅ 消息列表和消息详情页
......@@ -60,6 +86,7 @@ pnpm dev:tt # 字节跳动小程序开发
- ✅ 分类列表页
- ✅ PlanFields 表单字段组件集
- ✅ useCollectOperation composable
- ✅ usePlanPermission composable
---
......
......@@ -45,7 +45,7 @@ pnpm lint
-**组件复用** - "第 3 次出现原则"抽取 Composables
-**可复用组件** - TabBar、NavHeader、IconFont
## 🆕 最新更新(2026-02)
## 🆕 最新更新(2026-02-12
### 计划书功能优化
-**状态标记** - 添加计划书卡片状态标记("生成中" / "已完成")
......@@ -54,16 +54,38 @@ pnpm lint
- 使用条件类名动态切换样式
-**查看状态更新** - 仅在预览成功后标记为已查看,返回列表不刷新位置
-**提交跳转体验** - 提交后先关闭并重置弹框,再无固定延迟跳转结果页
-**返回重置体验** - 结果页返回后表单不保留旧数据
-**返回重置体验** - 关闭弹框时清理已选产品,确保返回后表单为空
-**字段优化** - 优化提取金额字段并新增每年提取字段
### 认证与权限优化
-**登录权限检查** - 为所有制作计划书按钮添加登录权限检查
-**红点状态管理** - 修复退出登录时红点状态未重置的问题
-**TabBar 红点** - 配置 TabBar 红点功能使用新的 unread_msg_count 字段
-**登录页返回** - 修复登录页返回按钮,清空 router store 并跳转到首页
-**401 修复** - 修复 401 重定向死循环和返回报错问题
### 消息功能优化
-**消息列表** - 优化消息列表卡片布局,提升信息可读性
-**消息详情** - 优化消息详情页布局,避免内容重复显示
-**未读提示** - 增加未读消息红点提示
-**API 错误处理** - 添加消息列表 API 错误提示
### 视觉优化
-**首页网格导航** - 优化导航图标视觉体验
-**产品卡片** - 优化 ProductCard 组件视觉样式
-**页面风格** - 重构"我的"页面为专业高端风格
-**统一视觉** - 优化视觉柔和度和整体统一性
-**头图优化** - 优化首页头图 CDN 加载
### API 集成
-**接口联调完成** - 计划书模块接口联调完成(submitPlanAPI、listAPI)
-**总进度** - 29 个接口,已完成 26 个(89.7%)
-**关闭 Mock** - 禁用消息列表 Mock 数据,使用真实接口
### 代码质量
-**移除本地配置** - 从版本控制中移除本地配置文件 settings.local.json
-**Composable 抽取** - 提取计划书提交回调逻辑为 composable
-**清理调试日志** - 清理项目中的调试日志
-**文档更新** - 完善开发文档和经验教训总结
## ⚡ 常见问题
......
......@@ -1200,6 +1200,304 @@ const fetchList = async (params) => {
}
```
### ✅ 5. 权限检查 Composable 模式 ⭐ 2026-02-12 新增
**场景描述**:
项目中需要在多个入口点(如首页、搜索页、产品中心页、产品详情页)添加"制作计划书"按钮的登录权限检查功能。
**问题**:
- 如果在每个页面都重复写权限检查逻辑,会导致代码重复
- 权限逻辑分散在各个页面,难以统一维护
**解决方案:抽取为 Composable**
创建 `src/composables/usePlanPermission.js`:
```javascript
/**
* 计划书权限检查 Composable
*
* @description 检查用户是否已登录,未登录时引导用户登录
* @returns {Object} { checkPlanPermission } 权限检查函数
*/
import { useUserStore } from '@/stores/user'
import Taro from '@tarojs/taro'
export function usePlanPermission() {
const userStore = useUserStore()
/**
* 检查计划书权限
*
* @param {Function} callback - 权限验证通过后的回调函数
* @description 如果用户未登录,显示登录提示;否则执行回调函数
*/
const checkPlanPermission = (callback) => {
console.log('[usePlanPermission] 检查登录状态:', userStore.isLoggedIn)
if (!userStore.isLoggedIn) {
// 未登录:显示登录提示
Taro.showModal({
title: '提示',
content: '请先登录后再制作计划书',
confirmText: '去登录',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 跳转到登录页
Taro.navigateTo({
url: '/pages/login/index'
})
}
}
})
} else {
// 已登录:执行回调函数
callback?.()
}
}
return {
checkPlanPermission
}
}
```
**使用方式**:
```vue
<script setup>
import { usePlanPermission } from '@/composables/usePlanPermission'
const { checkPlanPermission } = usePlanPermission()
const handlePlanClick = () => {
checkPlanPermission(() => {
// 权限验证通过后的操作
openPlanPopup()
})
}
</script>
<template>
<button @tap="handlePlanClick">制作计划书</button>
</template>
```
**收益**:
- ✅ 减少代码重复,统一权限检查逻辑
- ✅ 易于维护:修改权限逻辑只需更新一处
- ✅ 复用性强:任何需要权限检查的地方都可以使用
**适用场景**:
- 需要登录才能使用的功能
- 需要特定权限才能访问的功能
- 需要统一权限检查逻辑的场景
---
### ✅ 6. 全局状态管理与页面生命周期协调 ⭐ 2026-02-12 新增
**场景描述**:
项目中需要管理 TabBar 红点的显示状态,红点数据来自用户信息接口返回的 `unread_msg_count` 字段。当用户退出登录时,需要清除红点状态。
**问题**:
- 红点状态存储在用户 store 中
- 退出登录时需要重置红点状态
- 登录后需要自动显示/隐藏红点
**解决方案:在登录/登出时管理红点状态**
**1. 登录成功后显示红点**:
```javascript
// src/pages/login/index.vue
const handleLogin = async () => {
try {
const res = await loginAPI({ phone, code })
if (res.code === 1) {
// 更新用户信息(包含 unread_msg_count)
userStore.setUserInfo(res.data.userInfo)
// TabBar 会自动读取 userStore.unread_msg_count
// 如果大于 0,TabBar 组件会自动显示红点
}
} catch (err) {
console.error('登录失败:', err)
}
}
```
**2. 退出登录时清除红点**:
```javascript
// src/stores/user.js
export const useUserStore = defineStore('user', () => {
const userInfo = ref(null)
const unreadMsgCount = ref(0) // 红点数量
const setUserInfo = (info) => {
userInfo.value = info
// 从用户信息中提取未读消息数量
unreadMsgCount.value = info.unread_msg_count || 0
}
const logout = () => {
userInfo.value = null
unreadMsgCount.value = 0 // 清除红点状态 ⚠️ 重要
Taro.clearStorageSync() // 清除本地缓存
}
return {
userInfo,
unreadMsgCount,
setUserInfo,
logout
}
})
```
**3. TabBar 组件读取红点状态**:
```vue
<!-- src/components/TabBar.vue -->
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const hasUnread = computed(() => userStore.unreadMsgCount > 0)
</script>
<template>
<view class="tabbar-item">
<image :src="iconPath" />
<view class="badge" v-if="hasUnread"></view> <!-- 红点 -->
</view>
</template>
```
**关键要点**:
1. ✅ **状态同步**:登录后从 `userInfo` 中提取 `unread_msg_count`
2. ✅ **状态清除**:退出登录时必须重置 `unreadMsgCount = 0`
3. ✅ **响应式更新**:使用 `computed` 自动响应状态变化
4. ✅ **单一数据源**:红点状态只由 `userStore` 管理
**注意事项**:
- ⚠️ **必须在退出时清除状态**:否则下次登录会显示错误的红点
- ⚠️ **字段名对齐**:确保后端返回字段名与前端一致(`unread_msg_count`)
- ⚠️ **类型转换**:如果后端返回字符串,需要转换为数字
---
### ✅ 7. 导航返回按钮与路由栈协调 ⭐ 2026-02-12 新增
**场景描述**:
当用户从 401 错误被重定向到登录页后,点击返回按钮会出现问题,因为导航栈已被清空(`router store` 中存储的路径已丢失)。
**问题**:
- 401 重定向后,`router store` 被清空
- 登录页点击返回按钮时,没有历史页面可以返回
- 用户被困在登录页,无法返回首页
**解决方案 1:检测路由栈,为空时跳转首页**
```vue
<!-- src/pages/login/index.vue -->
<script setup>
import { useRouterStore } from '@/stores/router'
const routerStore = useRouterStore()
const handleBack = () => {
// 检查路由栈是否有历史记录
if (routerStore.history && routerStore.history.length > 0) {
// 有历史记录:正常返回
Taro.navigateBack()
} else {
// 无历史记录:清空 router store 并跳转首页
routerStore.clearHistory() // 清空路由栈
Taro.switchTab({
url: '/pages/index/index'
})
}
}
</script>
<template>
<NavHeader @back="handleBack" />
</template>
```
**解决方案 2:NavHeader 组件提供 `preventDefaultBack` prop**
```vue
<!-- src/components/navigation/NavHeader.vue -->
<script setup>
const props = defineProps({
preventDefaultBack: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['back'])
const handleBack = () => {
if (props.preventDefaultBack) {
// 阻止默认返回,由父组件处理
emit('back')
} else {
// 默认返回行为
Taro.navigateBack()
}
}
</script>
<template>
<view class="nav-header" @tap="handleBack">
<image class="back-icon" src="/assets/images/back.png" />
</view>
</template>
```
**在登录页使用**:
```vue
<!-- src/pages/login/index.vue -->
<script setup>
import { useRouterStore } from '@/stores/router'
const routerStore = useRouterStore()
const handleBack = () => {
// 清空路由栈并跳转首页
routerStore.clearHistory()
Taro.switchTab({
url: '/pages/index/index'
})
}
</script>
<template>
<NavHeader :prevent-default-back="true" @back="handleBack" />
</template>
```
**关键要点**:
1. ✅ **路由栈管理**:使用 `routerStore` 维护导航历史
2. ✅ **状态检测**:检查路由栈是否有历史记录
3. ✅ **降级方案**:无历史记录时,跳转到首页
4. ✅ **组件通信**:通过 `@back` 事件和 `preventDefaultBack` prop 协调返回行为
**收益**:
- ✅ 避免用户被困在登录页
- ✅ 提供更好的用户体验
- ✅ 组件职责清晰,易于复用
---
## Vue 3 响应式数据和表单状态管理
......