hookehuyr

docs(readme): 重构项目文档结构

- 优化 CLAUDE.md 文档,添加设计原则说明
- 新增 CHANGELOG.md 版本更新日志
- 新增最佳实践指南
- 新增调试指南
- 新增 API 集成、导航、页面开发指南
- 新增参考文档目录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# 变更日志
记录项目开发过程中的重要变更和完成任务。
## 2026-02-24
### 23:59:18 - 完成任务
**影响文件**:
- `.husky/README.md`
- `.husky/post-commit`
- `docs/CHANGELOG.md`
- `scripts/changelog/README.md`
- `scripts/changelog/update-changelog.sh`
**变更摘要**:
- 无详细描述
......@@ -2,6 +2,10 @@
本文件为 Claude Code (claude.ai/code) 在处理此仓库代码时提供指导。
**设计原则**:这是一个轻量级索引文档,只保留必须立即知道的信息。详细内容通过链接访问。
---
## 📚 项目文档索引
- **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱
......@@ -9,6 +13,8 @@
- **[变更日志](docs/CHANGELOG.md)** - 项目版本更新历史
- **[项目 README](README.md)** - 项目概述和快速开始指南
---
## 🚀 开发命令
### 核心命令
......@@ -53,26 +59,10 @@ git branch -d feature/功能名称
- `docs/style/refactor/test/chore` - 不更新
**实现方式**
-`commit-msg` hook �用 `scripts/update-version.sh` 自动更新
-更新后的 `package.json` 自动加入暂存区
-使用 `standard-version` 自动更新版本
-配置 conventional commits 规范
- ✅ 支持 `feat(version):` 格式跳过版本更新
**使用示例**
```bash
# 在当前功能分支开发
git checkout -b feature/new-page
# ... 开发代码 ...
git add .
git commit -m "feat(page): 添加新页面"
# 合并回 develop
git checkout develop
git merge feature/new-page
# 删除分支(可选)
git branch -d feature/new-page
```
### 其他平台构建
```bash
pnpm dev:alipay # 支付宝小程序开发
......@@ -80,106 +70,22 @@ pnpm dev:swan # 百度小程序开发
pnpm dev:tt # 字节跳动小程序开发
```
---
## 📋 快速参考
### 🆕 最新更新(2026-02-15)
**配置清理**
- ✅ 禁用 Mock 数据(`USE_MOCK_DATA: false`
- ✅ 移除测试计划书模板(测试计划书-智享未来2、长宁終身壽險計劃3)
**合并选项控制功能**
- ✅ 合并选项控制功能到 develop 分支
- ✅ 添加字段条件显示系统扩展功能
- ✅ 重组脚本目录结构
**文档解析重大升级**
- ✅ 支持多产品文档解析(自动识别和分割多个保险产品)
- ✅ 实现智能字段提取器 smartExtractList()
- ✅ 新增产品边界检测模块(支持产品代码和命名模式识别)
- ✅ 完善 MCP 解析切换功能和审核流程
- ✅ 待审核文件按原始文档名分目录生成
**计划书模块重构**
- ✅ 计划书表单字段 Schema 化(人寿/重疾模板)
- ✅ 优化字段配置管理,提取订单状态常量
- ✅ 重构表单提交逻辑,使用模板字段映射
- ✅ 完善表单交互和字段验证
**消息功能全面优化**
- ✅ 优化消息列表卡片布局,提升信息可读性
- ✅ 简化消息列表逻辑,移除冗余代码
- ✅ 优化消息详情页布局和样式
- ✅ 统一消息页面交互体验
**Git 工作流与版本管理**
- ✅ 使用 standard-version 替代自定义版本更新脚本
- ✅ 配置 conventional commits 规范
- ✅ 完善版本发布流程文档
- ✅ 移除旧的版本自动更新脚本
**Mock 数据优化**
- ✅ 优化产品列表 Mock 数据结构
- ✅ 添加测试商品到列表首位
- ✅ 修正搜索测试 Mock 开关
**计划书功能优化(历史更新)**
- ✅ 添加计划书卡片状态标记("生成中"/"已完成",黄色/绿色背景)
- ✅ 修复嵌套弹窗层级冲突(使用 provide/inject 模式)
- ✅ 优化页面滚动加载并清理调试代码
- ✅ 修复搜索栏清空按钮点击无效
- ✅ 修改提交结果页按钮为"返回上一页"
- ✅ 预览成功后才调用查看接口,避免预览失败也翻状态
- ✅ 优化计划书提交跳转体验:关闭弹框时清理已选产品
- ✅ 提取计划书提交回调逻辑为 composable
**认证与权限优化**
- ✅ 为所有制作计划书按钮添加登录权限检查
- ✅ 修复退出登录时红点状态未重置的问题
- ✅ 修复登录页返回按钮:清空 router store 并跳转到首页
- ✅ 修复 401 重定向死循环和返回报错问题
**消息功能优化**
- ✅ 配置 TabBar 红点功能使用新的 unread_msg_count 字段
- ✅ 修复 TabBar 未读红点显示问题
- ✅ 优化消息列表卡片布局,提升信息可读性
- ✅ 增加未读消息红点提示
- ✅ 优化消息详情页布局,避免内容重复显示
- ✅ 添加消息列表 API 错误提示
**样式改进**
- ✅ 增强资料卡片边框可见性(border-gray-200)
- ✅ 增大产品中心和详情页的字体与图标尺寸
- ✅ 添加全局背景色
- ✅ 增大全局字体和图标尺寸以提升可读性
- ✅ 优化首页网格导航视觉体验
- ✅ 重构"我的"页面为专业高端风格
- ✅ 优化 ProductCard 组件视觉样式
- ✅ 统一视觉柔和度和整体设计一致性
- ✅ 优化首页头图 CDN 加载
**计划书字段优化**
- ✅ 优化提取金额字段并新增每年提取字段
- ✅ 隐藏产品详情页附件下载提示
- ✅ 优化输入框间距
**代码质量**
- ✅ 从版本控制中移除本地配置文件 settings.local.json
- ✅ 禁用消息列表 Mock 数据,使用真实接口
- ✅ 清理调试日志
**API 集成进度**
- ✅ 总接口数:29,已完成:26 (89.7%)
- ✅ 计划书模块接口联调完成(submitPlanAPI、listAPI)
**新增功能**
- ✅ 消息列表和消息详情页
- ✅ 产品中心页
- ✅ 周热门资料页
- ✅ 分类列表页
- ✅ PlanFields 表单字段组件集
- ✅ useCollectOperation composable
- ✅ usePlanPermission composable
**详细更新记录**
- **[变更日志](docs/CHANGELOG.md)** - 完整版本更新历史
- **[接口联调日志](docs/api-integration-log.md)** - API 集成状态
**最新亮点**
- 🎯 文档解析重大升级(多产品支持、智能字段提取)
- 🎯 计划书模块重构(Schema 化、模板字段映射)
- 🎯 消息功能全面优化(布局、交互、红点)
- 🎯 选项控制功能合并
- 🎯 Git 工作流优化(standard-version)
---
......@@ -192,10 +98,11 @@ pnpm dev:tt # 字节跳动小程序开发
| SVG 图标加载失败(500 错误) | 使用 `import` 导入 | [经验教训](docs/lessons-learned.md#静态资源加载问题) |
| 代码重复 3 次 | 抽取为 Composable | [经验教训](docs/lessons-learned.md#组件抽取与复用) |
| 组件对象响应式警告 | 使用 `shallowRef` + `markRaw` | [经验教训](docs/lessons-learned.md#性能优化) |
| 业务状态标记显示 | 使用条件类名 + 语义化颜色 | [经验教训](docs/lessons-learned.md#案例-4-状态标记组件模式) |
| 嵌套弹窗层级冲突 | 使用 provide/inject 模式 | 最近提交记录(3357bed) |
| 401 重定向死循环 | 检查重定向拦截器逻辑 | 最近提交记录(6d2a4ec) |
---
### 🎯 核心架构模式
1. **认证流程** - 静默认证 + 401 自动刷新
......@@ -203,6 +110,8 @@ pnpm dev:tt # 字节跳动小程序开发
3. **样式策略** - TailwindCSS(80%) + Less(20%)
4. **组件抽取** - "第 3 次出现原则"
---
### 📦 技术栈
- **框架**: Taro 4.1.9 + Vue 3.3.0 + Composition API
......@@ -225,26 +134,11 @@ pnpm dev:tt # 字节跳动小程序开发
- **签单** - 签约流程
- **用户中心** - 个人资料、收藏、反馈、帮助中心
## 🏗️ 核心架构
### 1. 可复用的导航组件
**TabBar 组件**`src/components/TabBar.vue`
- 固定底部导航栏,自动适配安全区域
- 支持图标 + 文字布局,激活状态高亮
- 使用于:首页、我的、家办、知识库、签单页面
**NavHeader 组件**`src/components/NavHeader.vue`
- 带返回按钮的自定义导航头
- 透明/背景变体,刘海屏设备的安全区域内边距
**IconFont 组件**`src/components/IconFont.vue`
- 自定义图标的图标字体包装器
- ⚠️ **动态切换时需添加 `:key="name"`** [详见经验教训](docs/lessons-learned.md#坑-2-iconfont-动态切换不响应)
---
### 2. 身份认证流程(必需)
## 🏗️ 核心架构
项目具有完善的身份认证系统,支持静默认证和会话管理。
### 1. 身份认证流程(必需)
**核心文件**
- `src/utils/openid.js` - 微信授权和会话管理
......@@ -256,24 +150,16 @@ pnpm dev:tt # 字节跳动小程序开发
- 调用 `wx.login()` 获取 code
- 调用后端 `/srv/?a=openid` 接口换取 sessionid
- 自动将 sessionid 写入 localStorage
- 尝试调用 `loginStatusAPI` 获取用户信息(如已登录)
2. **401 自动刷新**
- API 返回 401 时触发
- 拦截器保存当前页面路径
- 调用 `miniProgramAuth()` 重新获取会话
- 使用新会话重试原始请求
- 如果刷新失败,跳转到登录页
**重要**
- 后端必须提供 `/srv/?a=openid` 端点用于微信登录
- 后端必须提供 `loginStatusAPI` 接口用于检查登录状态
- sessionid 存储在 `localStorage.sessionid`
- sessionid 由请求拦截器自动注入到请求头的 `cookie` 字段
### 3. API 层架构
### 2. API 层架构
**API 定义模式**`src/api/index.js`):
**API 定义**`src/api/index.js`):
```javascript
export const yourAPI = (params) => {
return buildApiUrl('your_action', params)
......@@ -285,7 +171,7 @@ export const yourAPI = (params) => {
- 处理常见错误场景
- **始终检查 `res.code === 1` 判断成功**
### 4. 增强导航系统
### 3. 增强导航系统
**useGo Hook**`src/hooks/useGo.js`):
```javascript
......@@ -297,496 +183,53 @@ go('/pages/product-detail/index', { id: 123 }) // 带查询参数
go.back() // 返回上一页
```
**路由存储**`src/stores/router.js`):
- 维护已访问路由的栈
- 用于认证回调导航
### 5. 可复用 Composables
**项目中的 Composables**
| Composable | 用途 | 文档 |
|-----------|------|------|
| `useSectionList` | 分组列表管理 | [经验教训](docs/lessons-learned.md#案例-1-usesectionlist-composable) |
| `useFileOperation` | 文件下载、预览、打开 | [经验教训](docs/lessons-learned.md#案例-2-usefileoperation-composable) |
| `useListItemClick` | 统一的列表点击处理 | [经验教训](docs/lessons-learned.md#案例-3-uselistitemclick-composable) |
| `useCollectOperation` | 收藏操作(新增) | 处理收藏/取消收藏逻辑 |
**抽取原则**:"第 3 次出现原则" - 当相同代码模式出现 3 次时,**必须**抽取为 Composable。
**组件自包含原则**(新增):
- 对于重复的UI结构,抽取为可复用组件(如 MaterialCard、ProductCard)
- 组件应该自包含业务逻辑(查看、收藏等),通过事件与父组件通信
- 避免在父组件中重复编写相同的逻辑代码
### 6. 样式处理策略
### 4. 样式处理策略
**TailwindCSS vs Less 使用指南**
| 场景 | 使用 | 比例 |
|------|------|------|
| 布局(flex、grid、absolute) | TailwindCSS | 80% |
| 间距(padding、margin、gap) | TailwindCSS | |
| 排版(font-size、text-align) | TailwindCSS | |
| 颜色(bg-*、text-*、border-*) | TailwindCSS | |
| 响应式设计(sm:、md:、lg:) | TailwindCSS | |
| 组件特定样式(需要 scoped) | Less | 20% |
| 深度选择器(`:deep()`) | Less | |
| 动画和过渡 | Less | |
| 伪元素(`::before``::after`) | Less | |
| 布局、间距、排版、颜色 | TailwindCSS | 80% |
| 组件特定样式、深度选择器 | Less | 20% |
[详见样式处理策略](docs/lessons-learned.md#✅-tailwindcss-vs-less-使用指南)
### 7. 响应式优化
### 5. 响应式优化
**处理组件对象响应式**
```javascript
// ❌ BAD - 深度响应式导致性能问题
const menuItems = ref([
{ icon: IconFont, name: 'heart' } // Vue 会深度代理组件对象
{ icon: IconFont, name: 'heart' }
])
// ✅ GOOD - 使用 shallowRef + markRaw
import { shallowRef, markRaw } from 'vue'
const menuItems = shallowRef([
{ icon: markRaw(IconFont), name: 'heart' } // 避免深度代理
{ icon: markRaw(IconFont), name: 'heart' }
])
```
[详见性能优化](docs/lessons-learned.md#✅-1-响应式数据优化)
### 页面结构
所有页面遵循以下目录结构:
```
src/pages/your-page/
├── index.vue # 页面组件(必须使用 <script setup>)
├── index.config.js # 页面配置(navigationBarTitleText 等)
└── assets/ # 页面特定资源(可选)
```
### 当前页面
所有页面按 `src/app.config.js` 注册顺序排列:
**核心页面**
1. `pages/index/index` - 首页(产品展示、搜索、网格导航)
- 热门产品的"产品资料"按钮跳转到 `product-detail` 页面,带产品 ID
- 热门资料的"查看更多"跳转到 `material-list` 页面
- 网格导航图标跳转到各个业务页面
- 使用可复用组件(MaterialCard、ProductCard)
2. `pages/search/index` - 产品和资料搜索页
- 支持实时搜索(输入关键字自动调用 searchAPI)
- 双Tab切换(产品、资料)
- 支持分页加载和触底加载更多
- 使用可复用组件(MaterialCard、ProductCard)
3. `pages/webview/index` - 外部 URL 的 WebView 包装器
4. `pages/document-preview/index` - 文档预览页
5. `pages/document-demo/index` - 文档预览演示页
6. `pages/onboarding/index` - 新用户引导
**业务页面**
7. `pages/family-office/index` - 家族办公室服务
8. `pages/product-center/index` - 产品中心页
9. `pages/category-list/index` - 分类列表页
10. `pages/product-detail/index` - 产品详情页
- 通过 Taro 的 `useLoad` hook 接收 `id` 参数
- 导航示例:`go('/pages/product-detail/index', { id: 1 })`
- 参数可用于从 API 获取产品详情
11. `pages/material-list/index` - 资料/文档列表页
12. `pages/week-hot-material/index` - 周热门资料页
- 使用 MaterialCard 组件展示热门资料
- 支持分页加载和触底加载更多
13. `pages/signing/index` - 签约
14. `pages/mine/index` - 用户资料
15. `pages/plan/index` - 业务计划管理
- 使用 PlanSchemes 和 PlanPopup 组件
- 支持嵌套弹窗交互(provide/inject 模式)
- 支持滚动加载和分页
16. `pages/plan-submit-result/index` - 计划提交结果页
- 导航按钮:返回上一页(非首页)
**用户相关页面**
17. `pages/favorites/index` - 用户收藏
18. `pages/avatar/index` - 头像设置
19. `pages/message/index` - 消息列表页
20. `pages/message-detail/index` - 消息详情页
21. `pages/feedback-list/index` - 反馈列表
22. `pages/feedback/index` - 用户反馈
23. `pages/login/index` - 登录页
24. `pages/help-center/index` - 帮助中心和常见问题页
**开发测试页面**(仅开发环境):
- `pages/test-tabs/index` - 标签页测试
### 组件库
**导航与布局组件**
- `TabBar.vue` - 底部导航栏
- `NavHeader.vue` - 自定义导航头
- `indexNav.vue` - 首页网格导航
**图标与媒体组件**
- `IconFont.vue` - 图标字体包装器
- `qrCode.vue` - 二维码显示
- `qrCodeSearch.vue` - 二维码扫描
**列表与展示组件**
- `SectionCard.vue` - 分组卡片组件
- `SectionItem.vue` - 分组列表项组件
- `ListItemActions/` - 列表项操作按钮
- `MaterialCard.vue` - 资料卡片组件(可复用)
- 自包含业务逻辑:查看、收藏
- 支持动态标签、文件大小格式化、学习人数显示
- 支持图片文件预览(使用 Taro.previewImage)
- 使用页面:首页、搜索页、周热门资料页
- 使用 composable:useCollectOperation、useListItemClick
- `ProductCard.vue` - 产品卡片组件(可复用)
- 自定义样式:动态标签、封面图
- 支持产品详情查看和计划书功能
- 使用页面:首页、搜索页、产品中心页
**表单与输入组件**
- `FilterTabs.vue` - 过滤标签组件
- `SearchBar.vue` - 搜索栏组件
**文档预览组件**
- `DocumentPreview/` - 文档预览组件
- `PdfPreview.vue` - PDF 预览组件
- `OfficeViewer.vue` - Office 文档查看器
**业务组件**
- `PlanSchemes/` - 计划方案组件(SchemeA, SchemeB)
- `PlanPopup/` - 计划弹窗组件
- 使用 provide/inject 模式实现父子弹窗通信
- 子弹窗打开时自动隐藏父级 footer
- 支持 AgePicker、DatePicker、SelectPicker 等字段组件
- `PlanFields/` - 计划书表单字段组件
- `AgePicker.vue` - 年龄选择器
- `AmountInput.vue` - 金额输入框
- `DatePicker.vue` - 日期选择器
- `SelectPicker.vue` - 下拉选择器
- `RadioGroup.vue` - 单选按钮组
- `PlanTemplates/` - 计划模板组件
**工具组件**
- `PosterBuilder/` - 海报生成器
- `time-picker-data/` - 时间选择器数据
### 路径别名
全部配置在 `config/index.js:30-38`
```javascript
@/utils src/utils
@/components src/components
@/images src/assets/images
@/assets src/assets
@/composables src/composables
@/api src/api
@/stores src/stores
@/hooks src/hooks
```
### 配置管理
**环境配置**`src/utils/config.js`):
**⚠️ 使用前必须修改**
- `BASE_URL` - 设置开发/生产域名
- `REQUEST_DEFAULT_PARAMS.f` - 设置业务模块标识符
- `REQUEST_DEFAULT_PARAMS.client_name` - 设置应用名称
**构建配置**`config/index.js`):
- 路径别名
- 设计宽度规则
- NutUI 自动导入
- 平台特定设置
**应用配置**`src/app.config.js`):
- 页面路由注册
- 窗口配置
- 标签栏配置(可选)
- 分包(如需要)
## 🔧 重要实现细节
### 会话管理
- **会话 ID 存储**`localStorage.sessionid`
- **静默登录**`openid.js``miniProgramAuth()` 函数
- 调用 `wx.login()` 获取 code
- 调用 `/srv/?a=openid` 接口换取 sessionid
- 自动写入 localStorage
- **请求注入**`request.js` 拦截器自动读取并注入到请求头的 `cookie` 字段
- **会话清除**:登出或 401 失败时清除
- **401 刷新**:拦截器捕获 401 → 调用 `miniProgramAuth()` → 重试原始请求
### 请求超时处理
- 默认超时:5 秒(`src/utils/request.js:79`
- 通过 `is_timeout_error()` 辅助函数检测超时
- 通过 `is_network_error()` 辅助函数检测网络错误
- 两者都会触发弱网络降级流程
### NutUI 自动导入
NutUI 组件通过 unplugin-vue-components 自动导入(`config/index.js:91-93`)。
**无需手动导入** - 直接在模板中使用组件即可。
### TailwindCSS 集成
- 为小程序兼容性禁用了 Preflight
- 启用了 `rem2rpx` 转换
- 内容路径配置在 `tailwind.config.js`
- [详见样式处理策略](docs/lessons-learned.md#✅-tailwindcss-vs-less-使用指南)
## 📦 可选功能
如果不需要,可以移除以下功能:
- **微信支付**`src/utils/wechatPay.js``src/api/wx/pay.js`
- **二维码**`src/components/qrCode.vue``src/components/qrCodeSearch.vue`
- **海报生成器**`src/components/PosterBuilder/`
- **时间选择器**`src/components/time-picker-data/`
## 开发工作流
### 添加新页面
1. **创建页面目录**
```bash
src/pages/your-page/
├── index.vue
└── index.config.js
```
2. **配置页面**`index.config.js`):
```javascript
export default {
navigationBarTitleText: '您的页面标题',
enablePullDownRefresh: true,
backgroundColor: '#f5f5f5'
}
```
3. **`src/app.config.js` 中注册路由**
```javascript
pages: [
'pages/your-page/index',
// ...
]
```
4. **`index.vue` 中使用 composition API**
```vue
<script setup>
import { ref } from 'vue'
import { useLoad, useShow } from '@tarojs/taro'
const pageId = ref(null)
useLoad((options) => {
console.log('页面加载,参数:', options)
// 接收导航参数
if (options.id) {
pageId.value = options.id
// 根据 ID 获取数据
}
})
useShow(() => {
console.log('页面显示')
})
// 您的组件逻辑
</script>
```
**带参数导航**
```javascript
// 从另一个页面
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 带查询参数导航
go('/pages/product-detail/index', { id: 123, type: 'insurance' })
```
5. **添加导航**(可选的 TabBar 集成):
- 导入并使用 `TabBar` 组件
- 根据路由配置激活状态
### 添加 API 调用
1. **`src/api/index.js` 中定义 API**
```javascript
export const getProductListAPI = (params) => {
return buildApiUrl('product_list', params)
}
```
2. **在页面中使用**
```javascript
import { getProductListAPI } from '@/api'
import { fn } from '@/api/fn'
const fetchProducts = async () => {
try {
const res = await fn(getProductListAPI({ page: 1 }))
if (res.code === 1) {
products.value = res.data
}
} catch (err) {
console.error('获取产品失败:', err)
}
}
```
### 使用导航
**推荐**:使用 `useGo` hook 进行增强导航:
```javascript
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 导航到页面
go('/pages/detail/index')
// 带参数导航(例如产品 ID)
go('/pages/product-detail/index', { id: 123 })
// 带多个参数导航
go('/pages/material-list/index', { category: 'insurance', page: 1 })
// 返回
go.back()
```
**在目标页面接收参数**:
```javascript
import { useLoad } from '@tarojs/taro'
import { ref } from 'vue'
const productId = ref(null)
useLoad((options) => {
// 访问导航参数
console.log('接收到的参数:', options)
productId.value = options.id
// 根据参数获取数据
fetchProductDetail(options.id)
})
```
**替代方案**:使用 Taro 内置导航:
```javascript
import Taro from '@tarojs/taro'
Taro.navigateTo({
url: '/pages/detail/index?id=123'
})
```
### 使用 Pinia 状态管理
**创建 store**(`src/stores/yourStore.js`):
```javascript
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useYourStore = defineStore('yourStore', () => {
const state = ref(null)
function setState(newState) {
state.value = newState
}
return { state, setState }
})
```
**在组件中使用**:
```javascript
import { useYourStore } from '@/stores/yourStore'
const store = useYourStore()
store.setState('新值')
```
### 使用 Mock 数据工具
项目提供了统一的 Mock 数据工具(`src/utils/mockData.js`),用于开发阶段测试分页加载等功能。
**支持的 Mock API**:
| API 名称 | 功能说明 | 使用页面 |
|---------|---------|---------|
| `mockWeekHotAPI` | 周热门资料 | 周热门资料页 |
| `mockFileListAPI` | 资料列表 | 资料列表页 |
| `mockProductListAPI` | 产品列表 | 产品中心页 |
| `mockSearchAPI` | 搜索(产品+资料) | 搜索页 |
| `mockMessageListAPI` | 消息列表 | 消息列表页 |
| `mockFavoriteListAPI` | 收藏列表 | 收藏页 |
| `mockFeedbackListAPI` | 意见反馈列表 | 意见反馈页 |
**使用方式**:
1. **在页面中导入 Mock 函数**:
```javascript
import { mockWeekHotAPI } from '@/utils/mockData'
```
2. **设置 Mock 开关**:
```javascript
// 开发环境使用 Mock,生产环境使用真实 API
const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
```
3. **在数据获取函数中使用**:
```javascript
const fetchWeekHot = async (page = 0) => {
loading.value = true
try {
// 根据 USE_MOCK_DATA 开关决定使用 Mock 数据还是真实 API
const res = USE_MOCK_DATA
? await mockWeekHotAPI({ page, limit: 20 })
: await fn(weekHotAPI({ page, limit: 20 }))
if (res.code === 1) {
weekHotList.value.push(...res.data.list)
hasMore.value = res.data.list.length >= 20
}
} catch (err) {
console.error('获取周热门资料失败:', err)
} finally {
loading.value = false
}
}
```
**Mock 数据特性**:
---
- ✅ **模拟网络延迟**:100-300ms 随机延迟
- ✅ **支持分页加载**:可配置 `page` 和 `limit` 参数
- ✅ **随机数据生成**:文件大小、学习人数、收藏状态等
- ✅ **支持关键词搜索**:搜索 API 会根据关键词过滤数据
- ✅ **真实数据结构**:返回的数据结构与真实 API 一致
- ✅ **控制台日志**:每次请求都会打印 `[Mock]` 日志便于调试
## 📁 详细文档
**统一调用器**(可选):
### 开发指南
- **[页面开发指南](docs/guides/page-development.md)** - 如何添加新页面
- **[API 集成指南](docs/guides/api-integration.md)** - API 调用详解
- **[导航系统指南](docs/guides/navigation.md)** - 导航和参数传递
也可以使用 `mockAPI()` 统一调用器:
```javascript
import { mockAPI } from '@/utils/mockData'
### 参考文档
- **[页面参考](docs/reference/pages.md)** - 所有页面详细说明
- **[组件参考](docs/reference/components.md)** - 组件库文档
- **[Composables 参考](docs/reference/composables.md)** - 可复用逻辑
// 调用指定的 Mock API
const res = await mockAPI('weekHotAPI', { page: 0, limit: 20 })
```
### 最佳实践
- **[最佳实践](docs/best-practices.md)** - 组件开发、API 集成、性能优化
- **[调试指南](docs/debugging.md)** - 调试技巧和工具
**注意事项**
- ⚠️ **生产环境关闭**:部署时确保 `USE_MOCK_DATA = false`
- ⚠️ **数据结构对齐**:Mock 数据结构必须与真实 API 保持一致
- ⚠️ **分页逻辑**:Mock API 返回的 `list` 为空时表示没有更多数据
---
## 关键文件总结
## 🔧 关键文件
### 修改前必须理解
1. **`src/utils/openid.js`** - 微信授权和会话管理逻辑
......@@ -801,105 +244,53 @@ const res = await mockAPI('weekHotAPI', { page: 0, limit: 20 })
4. **`src/stores/user.js`** - 用户状态管理
5. **`src/stores/router.js`** - 路由状态管理
### 认证与会话
1. **`src/utils/openid.js`** - `miniProgramAuth()` 静默登录
2. **`src/utils/request.js`** - 401 拦截器和 sessionid 管理
3. **`src/pages/login/index.vue`** - 登录页
### 可复用组件
1. **`src/components/TabBar.vue`** - 底部导航栏
2. **`src/components/NavHeader.vue`** - 自定义导航头
3. **`src/components/SectionCard.vue`** - 分组卡片
4. **`src/components/DocumentPreview/`** - 文档预览
5. **`src/components/MaterialCard.vue`** - 资料卡片(可复用)
- 自包含业务逻辑:查看、收藏
- 支持动态标签、文件大小格式化、学习人数显示
- 使用页面:首页、搜索页、周热门资料页
6. **`src/components/ProductCard.vue`** - 产品卡片(可复用)
- 自定义样式:动态标签、封面图
- 支持产品详情查看和计划书功能
- 使用页面:首页、搜索页
3. **`src/components/MaterialCard.vue`** - 资料卡片(可复用)
4. **`src/components/ProductCard.vue`** - 产品卡片(可复用)
### Composables
1. **`src/composables/useSectionList.js`** - 分组列表管理
2. **`src/composables/useFileOperation.js`** - 文件操作
3. **`src/composables/useListItemClick.js`** - 列表点击处理
### 工具函数
1. **`src/utils/mockData.js`** - Mock 数据生成工具(开发测试用)
- 支持多个 API 的 Mock:周热门资料、资料列表、产品列表、搜索、消息列表、收藏列表、意见反馈
- 统一调用器:`mockAPI(apiName, params)`
- 支持分页加载、模拟网络延迟、随机数据生成
2. **`src/utils/documentIcons.js`** - 文档类型图标识别
3. **`src/utils/tools.js`** - 通用工具函数集合
4. **`src/utils/network.js`** - 网络状态工具
5. **`src/hooks/useGo.js`** - 增强导航 hook
## 调试技巧
调试问题时:
1. **检查环境配置**
- 验证 `src/utils/config.js` 中的 `BASE_URL`
- 检查业务模块标识符 `f``client_name`
2. **验证身份认证**
- 检查 localStorage 中的 `sessionid`
-`src/utils/request.js` 拦截器中启用详细日志
- 测试 401 刷新流程
3. **网络问题**
- 使用 Taro 内置的网络状态监控
- 检查弱网络降级场景
- 验证离线缓存交互
4. **样式问题**
- 确认设计宽度(375px vs 750px)
- 检查是 NutUI 组件还是自定义组件
- 验证 TailwindCSS 类是否已应用
5. **导航问题**
- 检查路由是否在 `src/app.config.js` 中注册
- 验证页面目录结构与路由匹配
- 使用 `useGo` hook 进行一致的导航
## ✨ 最佳实践
### 组件开发
- ✅ 使用 `<script setup>` 语法
- ✅ 使用 Composables 处理可复用逻辑
- ✅ Props 应该有类型定义
- ✅ 使用 `emit` 进行子到父通信
- ✅ 优先使用 TailwindCSS 进行样式设计
### API 集成
- ✅ 始终检查 `res.code === 1` 判断成功
- ✅ 使用 `try/catch` 进行错误处理
- ✅ 请求期间显示加载状态
- ✅ 优雅地处理网络错误
### 性能
- ✅ 使用页面懒加载(分包)
- ✅ 使用 CDN 参数优化图片
- ✅ 避免无分页的大数据集
- ✅ 在 `onUnmounted` 中清理定时器和监听器
- ✅ 使用 `shallowRef` + `markRaw` 处理组件对象
### 代码质量
- ✅ 遵循 Vue 3 Composition API 模式
- ✅ 使用描述性变量名
- ✅ 保持函数聚焦且简短(< 50 行)
-**所有函数必须有 JSDoc 注释** [详见代码注释规范](~/.claude/rules/code-commenting.md)
- ✅ 提交前运行 `pnpm lint`
### 代码复用
- ✅ 遵循"第 3 次出现原则" - 代码重复 3 次时必须抽取
- ✅ 优先使用 Composables 而非 Mixins
- ✅ 组件职责单一,避免过度复杂
-**组件自包含业务逻辑** - 当UI结构重复出现时,抽取为可复用组件(如 MaterialCard、ProductCard)
- 组件内部处理业务逻辑(查看、收藏等)
- 通过事件与父组件通信
- 减少父组件的重复代码
- 示例:MaterialCard 组件在首页、搜索页、周热门资料页复用
[更多最佳实践详见经验教训总结](docs/lessons-learned.md)
---
## ⚠️ 重要配置
**环境配置**`src/utils/config.js`):
- `BASE_URL` - 设置开发/生产域名
- `REQUEST_DEFAULT_PARAMS.f` - 设置业务模块标识符
- `REQUEST_DEFAULT_PARAMS.client_name` - 设置应用名称
---
## 🚀 快速开始
### 1. 安装依赖
```bash
pnpm install
```
### 2. 配置环境
编辑 `src/utils/config.js`,设置 `BASE_URL` 和业务参数。
### 3. 启动开发服务器
```bash
pnpm dev:weapp
```
### 4. 打开微信开发者工具
导入项目目录:`dist/`
---
## 📖 更多文档
- **[页面参考](docs/reference/pages.md)** - 查看所有页面
- **[组件参考](docs/reference/components.md)** - 查看所有组件
- **[Composables 参考](docs/reference/composables.md)** - 查看所有 Composables
- **[最佳实践](docs/best-practices.md)** - 代码质量和性能优化
- **[调试指南](docs/debugging.md)** - 调试技巧
- **[经验教训总结](docs/lessons-learned.md)** - 常见陷阱和解决方案
......
# 最佳实践
本文档列出项目开发中的最佳实践。
## 组件开发
### ✅ 推荐做法
```vue
<script setup>
// 1. 使用 <script setup> 语法
import { ref, computed } from 'vue'
// 2. Props 应该有类型定义
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// 3. 使用 emit 进行子到父通信
const emit = defineEmits(['update', 'delete'])
// 4. 使用 Composables 处理可复用逻辑
const { data, loading } = use fetchData()
</script>
```
### ❌ 避免做法
```vue
<script>
// ❌ 不要使用 Options API
export default {
data() {
return { }
}
}
</script>
<script setup>
// ❌ 不要省略 Props 类型
const props = defineProps(['title'])
// ❌ 不要直接修改 props
props.title = 'new title'
</script>
```
---
## 样式开发
### TailwindCSS vs Less
| 场景 | 使用 | 比例 |
|------|------|------|
| 布局(flex、grid、absolute) | TailwindCSS | 80% |
| 间距(padding、margin、gap) | TailwindCSS | |
| 排版(font-size、text-align) | TailwindCSS | |
| 颜色(bg-*、text-*、border-*) | TailwindCSS | |
| 响应式设计(sm:、md:、lg:) | TailwindCSS | |
| 组件特定样式(需要 scoped) | Less | 20% |
| 深度选择器(`:deep()`) | Less | |
| 动画和过渡 | Less | |
| 伪元素(`::before``::after`) | Less | |
### ✅ 优先使用 TailwindCSS
```vue
<template>
<!-- ✅ 使用 TailwindCSS -->
<view class="flex items-center gap-4 p-4 bg-white rounded-lg">
<text class="text-lg font-semibold">标题</text>
</view>
</template>
```
### ❌ 避免过度使用 Less
```vue
<style lang="less" scoped>
// ❌ 能用 TailwindCSS 的就不要用 Less
.custom-container {
display: flex;
align-items: center;
padding: 16px;
}
</style>
```
---
## API 集成
### ✅ 推荐做法
```javascript
// 1. 始终检查 res.code === 1
const res = await fn(yourAPI(params))
if (res.code === 1) {
// 成功处理
}
// 2. 使用 try/catch 进行错误处理
try {
const res = await fn(yourAPI(params))
} catch (err) {
console.error('API 错误:', err)
}
// 3. 请求期间显示加载状态
loading.value = true
await fn(yourAPI(params))
loading.value = false
// 4. 优雅地处理网络错误
try {
await fn(yourAPI(params))
} catch (err) {
if (is_network_error(err)) {
Taro.showToast({ title: '网络错误,请重试', icon: 'none' })
}
}
```
### ❌ 避免做法
```javascript
// ❌ 不要只检查 res.code
if (res.code) { }
// ❌ 不要忽略错误处理
await fn(yourAPI(params))
// ❌ 不要不显示加载状态
await fn(yourAPI(params)) // 用户不知道发生了什么
```
---
## 性能优化
### ✅ 推荐做法
```javascript
// 1. 使用页面懒加载(分包)
// 在 app.config.js 中配置分包
subPackages: [
{
root: 'pages/business',
pages: ['/*']
}
]
// 2. 使用 CDN 参数优化图片
const imageUrl = 'https://cdn.example.com/image.jpg?w=750&q=80'
// 3. 避免无分页的大数据集
const res = await fn(getListAPI({ page: 1, limit: 20 }))
// 4. 在 onUnmounted 中清理
onUnmounted(() => {
timer && clearInterval(timer)
})
// 5. 使用 shallowRef + markRaw 处理组件对象
import { shallowRef, markRaw } from 'vue'
const menuItems = shallowRef([
{ icon: markRaw(IconFont), name: 'heart' }
])
```
### ❌ 避免做法
```javascript
// ❌ 不要一次性加载所有数据
const allData = await fn(getAllDataAPI())
// ❌ 不要忘记清理定时器
const timer = setInterval(() => { }, 1000)
// 没有清理
// ❌ 不要深度代理组件对象
const menuItems = ref([
{ icon: IconFont, name: 'heart' } // Vue 会深度代理
])
```
---
## 代码质量
### ✅ 推荐做法
```javascript
// 1. 遵循 Vue 3 Composition API 模式
<script setup>
// ...
</script>
// 2. 使用描述性变量名
const isLoadingUserFavorites = ref(false)
// 3. 保持函数聚焦且简短(< 50 行)
const fetchUserData = async () => {
// 单一职责
}
// 4. 所有函数必须有 JSDoc 注释
/**
* 获取用户数据
* @param {number} userId - 用户 ID
* @returns {Promise<User>} 用户数据
*/
async function getUserData(userId) { }
// 5. 提交前运行 pnpm lint
```
### ❌ 避免做法
```javascript
// ❌ 不要使用无意义的变量名
const a = ref(false)
const temp = ref(null)
// ❌ 不要写超长函数(> 50 行)
const doEverything = async () => {
// 100+ 行代码
}
// ❌ 不要省略函数注释
function process(data) { }
```
---
## 代码复用
### 第 3 次出现原则
当相同代码模式出现 3 次时,**必须**抽取为 Composable 或组件。
### ✅ 推荐做法
```javascript
// 抽取为 Composable
// src/composables/useUserData.js
export function useUserData() {
const user = ref(null)
const loading = ref(false)
const fetchUser = async () => {
loading.value = true
// ...
}
return { user, loading, fetchUser }
}
// 在组件中使用
const { user, loading, fetchUser } = useUserData()
```
### 组件自包含原则
对于重复的UI结构,抽取为可复用组件:
```vue
<!-- MaterialCard.vue - 自包含业务逻辑 -->
<script setup>
const props = defineProps(['item'])
const emit = defineEmits(['view', 'collect'])
// 组件内部处理业务逻辑
const handleView = () => {
emit('view', props.item.id)
}
</script>
<!-- 父组件只需要传递数据 -->
<MaterialCard :item="material" @view="handleView" />
```
---
## 安全性
### ✅ 推荐做法
```javascript
// 1. 用户输入验证
const validateInput = (input) => {
if (!input || input.length > 100) {
return false
}
return true
}
// 2. XSS 防护(使用 v-html 时净化)
import DOMPurify from 'dompurify'
const sanitizedHtml = DOMPurify.sanitize(rawHtml)
// 3. 敏感数据不存储在 localStorage
// ❌ 不要存储
localStorage.setItem('password', password)
// ✅ 使用 Pinia(内存存储)
const authStore = useAuthStore()
authStore.setToken(token)
```
---
## 相关文档
- **[经验教训总结](lessons-learned.md)** - 常见陷阱和解决方案
- **[调试指南](debugging.md)** - 调试技巧
# 调试指南
本文档介绍项目调试的常用技巧和工具。
## 检查环境配置
### 验证 BASE_URL
```bash
# 查看 src/utils/config.js 中的 BASE_URL 配置
cat src/utils/config.js | grep BASE_URL
```
确保:
- 开发环境指向测试服务器
- 生产环境指向正式服务器
### 检查业务参数
```javascript
// src/utils/config.js
REQUEST_DEFAULT_PARAMS: {
f: 'your_module', // 业务模块标识符
client_name: 'your_app' // 应用名称
}
```
---
## 验证身份认证
### 检查 sessionid
```javascript
// 在浏览器控制台或微信开发者工具中执行
console.log(localStorage.sessionid)
```
### 启用详细日志
`src/utils/request.js` 拦截器中启用详细日志:
```javascript
// 请求拦截器
config.headers = {
'cookie': `sessionid=${localStorage.sessionid}`,
// 添加调试日志
'X-Debug': 'true'
}
console.log('[Request]', config.url, config.data)
```
### 测试 401 刷新流程
```javascript
// 1. 清除 sessionid 模拟过期
localStorage.removeItem('sessionid')
// 2. 发送请求触发 401
await fn(someAPI())
// 3. 观察是否自动刷新并重试
```
---
## 网络问题调试
### 检查网络状态
```javascript
import Taro from '@tarojs/taro'
// 获取网络类型
Taro.getNetworkType({
success: (res) => {
console.log('网络类型:', res.networkType)
// wifi, 4g, 5g, none
}
})
// 监听网络变化
Taro.onNetworkStatusChange((result) => {
console.log('网络状态变化:', result)
})
```
### 检查弱网络场景
项目支持弱网络降级,通过以下方式检测:
```javascript
// src/utils/request.js
import { is_timeout_error, is_network_error } from '@/utils/network'
try {
const res = await fn(yourAPI())
} catch (err) {
if (is_timeout_error(err)) {
console.log('请求超时')
} else if (is_network_error(err)) {
console.log('网络错误')
}
}
```
---
## 样式问题调试
### 确认设计宽度
项目中使用双设计宽度系统:
- **NutUI 组件**:375px
- **其他组件**:750px
```javascript
// config/index.js
designWidth: 750, // 默认
// NutUI 特殊配置
nutui: {
designWidth: 375
}
```
### 检查样式应用
```vue
<template>
<!-- 添加调试类名 -->
<view class="debug-component">
内容
</view>
</template>
<style lang="less" scoped>
.debug-component {
/* 调试边框 */
border: 1px solid red;
}
</style>
```
### 验证 TailwindCSS 类
```bash
# 检查 TailwindCSS 配置
cat tailwind.config.js
# 检查类是否生效
# 在微信开发者工具中查看元素的 computed styles
```
---
## 导航问题调试
### 检查路由注册
```javascript
// src/app.config.js
export default {
pages: [
'pages/your-page/index', // 确认已注册
]
}
```
### 验证页面目录结构
```bash
# 确认页面文件存在
ls -la src/pages/your-page/
# 应该包含:
# - index.vue
# - index.config.js
```
### 调试导航参数
```javascript
// 发送页面
go('/pages/detail/index', { id: 123 })
// 接收页面
useLoad((options) => {
console.log('接收到的参数:', options)
console.log('ID:', options.id) // 应该输出 123
})
```
---
## API 问题调试
### 查看 API 请求
```javascript
// src/utils/request.js
// 在拦截器中添加日志
interceptors.request.use((config) => {
console.log('[API Request]', {
url: config.url,
method: config.method,
data: config.data,
headers: config.headers
})
return config
})
interceptors.response.use((response) => {
console.log('[API Response]', {
status: response.status,
data: response.data
})
return response
})
```
### 测试 API 调用
```javascript
// 独立测试 API
import { yourAPI } from '@/api'
import { fn } from '@/api/fn'
async function testAPI() {
try {
const url = yourAPI({ id: 123 })
console.log('API URL:', url)
const res = await fn(url)
console.log('API Response:', res)
if (res.code === 1) {
console.log('API 调用成功')
} else {
console.log('API 业务错误:', res.msg)
}
} catch (err) {
console.error('API 网络错误:', err)
}
}
testAPI()
```
---
## 常见问题
### Q: NutUI 组件样式不生效
A: 检查设计宽度配置,NutUI 使用 375px 设计宽度:
```javascript
// config/index.js
nutui: {
designWidth: 375 // 确认配置正确
}
```
### Q: 页面参数接收不到
A: 检查以下几点:
1. 确认使用 `useLoad` 接收参数
2. 确认参数名称正确
3. 确认参数类型(数字 vs 字符串)
```javascript
useLoad((options) => {
console.log(options) // 先打印看看有什么
})
```
### Q: API 请求不发送
A: 检查以下几点:
1. 确认 BASE_URL 配置正确
2. 确认网络权限
3. 查看控制台是否有错误
4. 检查 request.js 拦截器
### Q: 组件不更新
A: 可能的原因:
1. 响应式数据未正确声明
2. 使用了深度嵌套的对象
3. Key 值未正确设置
```javascript
// 确保使用 ref 或 reactive
const data = ref(null) // ✅
const data = null // ❌
// 列表使用 key
<view v-for="item in list" :key="item.id"> // ✅
<view v-for="item in list"> // ❌
```
---
## 调试工具
### 微信开发者工具
- **Console** - 查看日志输出
- **Network** - 查看网络请求
- **AppData** - 查看 AppData 数据
- **Storage** - 查看 localStorage
- **Wxml** - 查看 DOM 结构
### Vue DevTools
Taro 支持 Vue DevTools,可以:
- 查看组件树
- 查看 Vuex/Pinia 状态
- 查看事件监听器
---
## 相关文档
- **[API 集成指南](guides/api-integration.md)** - API 调用详解
- **[最佳实践](best-practices.md)** - 代码质量建议
# API 集成指南
本文档介绍如何在项目中添加和调用 API。
## API 定义模式
### 步骤 1:在 src/api/index.js 中定义 API
```javascript
export const getProductListAPI = (params) => {
return buildApiUrl('product_list', params)
}
export const getProductDetailAPI = (params) => {
return buildApiUrl('product_detail', params)
}
```
### 步骤 2:在页面中使用
```javascript
import { getProductListAPI } from '@/api'
import { fn } from '@/api/fn'
const fetchProducts = async () => {
try {
const res = await fn(getProductListAPI({ page: 1 }))
if (res.code === 1) {
products.value = res.data
}
} catch (err) {
console.error('获取产品失败:', err)
}
}
```
## 请求包装器(fn.js)
所有 API 调用都应通过 `src/api/fn.js` 的包装器:
- ✅ 处理常见错误场景
- ✅ 统一错误提示
-**始终检查 `res.code === 1` 判断成功**
## 完整示例
### 带加载状态的 API 调用
```javascript
import { ref } from 'vue'
import { getProductListAPI } from '@/api'
import { fn } from '@/api/fn'
const products = ref([])
const loading = ref(false)
const error = ref(null)
const fetchProducts = async (page = 1) => {
loading.value = true
error.value = null
try {
const res = await fn(getProductListAPI({ page, limit: 20 }))
if (res.code === 1) {
products.value = res.data.list
} else {
error.value = res.msg || '获取失败'
}
} catch (err) {
console.error('API 错误:', err)
error.value = '网络错误,请重试'
} finally {
loading.value = false
}
}
```
### 带分页的 API 调用
```javascript
const page = ref(0)
const hasMore = ref(true)
const loading = ref(false)
const loadMore = async () => {
if (loading.value || !hasMore.value) return
loading.value = true
try {
const res = await fn(getProductListAPI({ page: page.value, limit: 20 }))
if (res.code === 1) {
products.value.push(...res.data.list)
page.value++
hasMore.value = res.data.list.length >= 20
}
} catch (err) {
console.error('加载更多失败:', err)
} finally {
loading.value = false
}
}
```
## 错误处理
### 网络错误
```javascript
try {
const res = await fn(yourAPI(params))
// ...
} catch (err) {
if (is_network_error(err)) {
// 网络错误
Taro.showToast({ title: '网络错误', icon: 'none' })
} else if (is_timeout_error(err)) {
// 超时
Taro.showToast({ title: '请求超时', icon: 'none' })
} else {
// 其他错误
Taro.showToast({ title: '请求失败', icon: 'none' })
}
}
```
### 业务错误
```javascript
const res = await fn(yourAPI(params))
if (res.code === 1) {
// 成功
} else if (res.code === 401) {
// 未登录(通常会被拦截器自动处理)
} else {
// 业务错误
Taro.showToast({ title: res.msg || '操作失败', icon: 'none' })
}
```
## API 规范
### 请求格式
```javascript
// 查询列表
yourAPI({ page: 1, limit: 20, keyword: '搜索词' })
// 获取详情
detailAPI({ id: 123 })
// 提交表单
submitAPI({ field1: 'value1', field2: 'value2' })
```
### 响应格式
```javascript
// 成功
{
code: 1,
msg: 'success',
data: { /* 业务数据 */ }
}
// 失败
{
code: 0, // 或其他错误码
msg: '错误信息',
data: null
}
```
## 最佳实践
### ✅ 推荐做法
```javascript
// 1. 使用 async/await
const res = await fn(yourAPI(params))
// 2. 检查 res.code === 1
if (res.code === 1) {
// 成功处理
}
// 3. 使用 try/catch
try {
const res = await fn(yourAPI(params))
} catch (err) {
// 错误处理
}
// 4. 显示加载状态
loading.value = true
// ... API 调用
loading.value = false
```
### ❌ 避免做法
```javascript
// 1. 不要只检查 res.code
if (res.code) { } // ❌
// 2. 不要忽略错误
const res = await fn(yourAPI(params)) // ❌ 无 try/catch
// 3. 不要硬编码 API URL
fetch('/srv/?a=your_action') // ❌
```
## 参考文档
- **[API 接口文档](docs/api-specs/)** - 完整的 API 接口规范
- **[接口联调日志](docs/api-integration-log.md)** - API 集成状态
# 导航系统指南
本文档介绍项目中的导航系统使用方法。
## useGo Hook(推荐)
`useGo` 是增强的导航 Hook,提供自动路径补全和便捷方法。
### 基础用法
```javascript
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 导航到页面
go('/pages/detail/index')
// 带参数导航(例如产品 ID)
go('/pages/product-detail/index', { id: 123 })
// 带多个参数导航
go('/pages/material-list/index', { category: 'insurance', page: 1 })
// 返回上一页
go.back()
```
### 路径自动补全
`useGo` 会自动补全相对路径为绝对路径:
```javascript
go('detail') // → /pages/detail/index
go('product-detail') // → /pages/product-detail/index
```
### 返回导航
```javascript
// 返回上一页
go.back()
// 返回多页
go.back(2)
// 返回首页
go('/pages/index/index')
```
## 在目标页面接收参数
```javascript
import { useLoad } from '@tarojs/taro'
import { ref } from 'vue'
const productId = ref(null)
useLoad((options) => {
// 访问导航参数
console.log('接收到的参数:', options)
productId.value = options.id
// 根据参数获取数据
fetchProductDetail(options.id)
})
```
## Taro 内置导航(备选方案)
如果需要更底层的控制,可以直接使用 Taro 导航 API:
### navigateTo - 保留当前页
```javascript
import Taro from '@tarojs/taro'
Taro.navigateTo({
url: '/pages/detail/index?id=123'
})
```
### redirectTo - 不保留当前页
```javascript
Taro.redirectTo({
url: '/pages/login/index'
})
```
### switchTab - 切换 Tab
```javascript
Taro.switchTab({
url: '/pages/index/index'
})
```
### reLaunch - 重启应用
```javascript
Taro.reLaunch({
url: '/pages/index/index'
})
```
### navigateBack - 返回
```javascript
// 返回上一页
Taro.navigateBack()
// 返回多页
Taro.navigateBack({ delta: 2 })
```
## 路由状态管理
### router Store
项目使用 `src/stores/router.js` 维护已访问路由的栈,主要用于认证回调导航。
```javascript
import { useRouterStore } from '@/stores/router'
const routerStore = useRouterStore()
// 获取上一页路径
const previousPage = routerStore.previousRoute
```
### 导航钩子
```javascript
import { useLoad, useShow, useHide, useUnload } from '@tarojs/taro'
// 页面加载时(只执行一次)
useLoad((options) => {
console.log('页面加载', options)
})
// 页面显示时(每次返回都会执行)
useShow(() => {
console.log('页面显示')
})
// 页面隐藏时
useHide(() => {
console.log('页面隐藏')
})
// 页面卸载时
useUnload(() => {
console.log('页面卸载')
})
```
## 认证场景导航
### 登录后返回原页面
```javascript
import { useGo } from '@/hooks/useGo'
import { useRouterStore } from '@/stores/router'
const go = useGo()
const routerStore = useRouterStore()
// 1. 保存当前路径
routerStore.setRedirect('/pages/some-page/index')
// 2. 跳转登录页
go('/pages/login/index')
// 3. 登录成功后返回
const redirectUrl = routerStore.redirect
if (redirectUrl) {
go(redirectUrl)
routerStore.setRedirect(null)
}
```
## 常见问题
### Q: 导航后页面不刷新?
A: 使用 `useShow` 钩子,每次页面显示时都会执行:
```javascript
useShow(() => {
// 重新加载数据
fetchData()
})
```
### Q: 参数传递丢失?
A: 确保参数类型正确:
```javascript
// ❌ 错误:对象作为参数
go('/pages/detail/index', { data: { id: 1 } })
// ✅ 正确:扁平参数
go('/pages/detail/index', { id: 1, type: 'insurance' })
```
### Q: 如何清除页面栈?
A: 使用 `redirectTo``reLaunch`
```javascript
// 不保留当前页
Taro.redirectTo({ url: '/pages/login/index' })
// 清空所有页面栈
Taro.reLaunch({ url: '/pages/index/index' })
```
## 参考文档
- **[Taro 导航文档](https://docs.taro.zone/docs/vue-navigation)** - 官方文档
- **[页面开发指南](guides/page-development.md)** - 页面创建和路由注册
# 页面开发指南
本文档介绍如何在项目中添加新页面。
## 目录结构
所有页面遵循以下目录结构:
```
src/pages/your-page/
├── index.vue # 页面组件(必须使用 <script setup>)
├── index.config.js # 页面配置(navigationBarTitleText 等)
└── assets/ # 页面特定资源(可选)
```
## 步骤 1:创建页面目录和文件
```bash
mkdir -p src/pages/your-page
touch src/pages/your-page/index.vue
touch src/pages/your-page/index.config.js
```
## 步骤 2:配置页面
**`index.config.js`**
```javascript
export default {
navigationBarTitleText: '您的页面标题',
enablePullDownRefresh: true,
backgroundColor: '#f5f5f5'
}
```
## 步骤 3:编写页面组件
**`index.vue`**
```vue
<script setup>
import { ref } from 'vue'
import { useLoad, useShow } from '@tarojs/taro'
const pageId = ref(null)
useLoad((options) => {
console.log('页面加载,参数:', options)
// 接收导航参数
if (options.id) {
pageId.value = options.id
// 根据 ID 获取数据
}
})
useShow(() => {
console.log('页面显示')
})
// 您的组件逻辑
</script>
<template>
<view class="page">
<!-- 页面内容 -->
</view>
</template>
<style lang="less" scoped>
.page {
padding: 20px;
}
</style>
```
## 步骤 4:注册路由
**`src/app.config.js`** 中注册路由:
```javascript
export default {
pages: [
'pages/your-page/index',
// ... 其他页面
],
// ...
}
```
## 步骤 5:添加导航(可选)
### 使用 useGo Hook(推荐)
```javascript
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 带查询参数导航
go('/pages/your-page/index', { id: 123, type: 'insurance' })
```
### 使用 Taro 内置导航
```javascript
import Taro from '@tarojs/taro'
Taro.navigateTo({
url: '/pages/your-page/index?id=123'
})
```
## 接收导航参数
在目标页面的 `useLoad` 中接收参数:
```javascript
useLoad((options) => {
console.log('接收到的参数:', options)
const { id, type } = options
// 根据参数获取数据
fetchData(id, type)
})
```
## TabBar 集成(可选)
如果页面需要底部导航栏:
1. 导入 `TabBar` 组件
2. 根据路由配置激活状态
```vue
<script setup>
import TabBar from '@/components/TabBar.vue'
</script>
<template>
<view class="page">
<!-- 页面内容 -->
<TabBar />
</view>
</template>
```
## 常见问题
### Q: 页面注册后还是 404?
A: 检查以下几点:
1. 路由路径是否正确(`pages/your-page/index`
2. 是否重启了开发服务器
3. `index.config.js` 是否存在
### Q: 如何隐藏原生导航栏?
A: 在 `index.config.js` 中设置:
```javascript
export default {
navigationStyle: 'custom'
}
```
### Q: 页面参数丢失?
A: 检查参数是否正确编码:
```javascript
// ❌ 错误
go('/pages/detail/index', { id: '123' })
// ✅ 正确
go('/pages/detail/index', { id: 123 }) // 数字类型
// 或
go('/pages/detail/index?id=123') // 字符串类型
```
# 组件库参考文档
本文档列出项目中所有组件的详细说明。
## 导航与布局组件
### TabBar.vue - 底部导航栏
**位置**`src/components/TabBar.vue`
**功能**:固定底部导航栏,自动适配安全区域
**关键特性**
- 支持图标 + 文字布局
- 激活状态高亮
**使用页面**:首页、我的、家办、知识库、签单页面
---
### NavHeader.vue - 自定义导航头
**位置**`src/components/NavHeader.vue`
**功能**:带返回按钮的自定义导航头
**关键特性**
- 透明/背景变体
- 刘海屏设备的安全区域内边距
---
### indexNav.vue - 首页网格导航
**位置**`src/components/indexNav.vue`
**功能**:首页网格导航
---
## 图标与媒体组件
### IconFont.vue - 图标字体包装器
**位置**`src/components/IconFont.vue`
**功能**:自定义图标的图标字体包装器
**⚠️ 注意**:动态切换时需添加 `:key="name"`
```vue
<IconFont :name="iconName" :key="iconName" />
```
---
### qrCode.vue - 二维码显示
**位置**`src/components/qrCode.vue`
**功能**:二维码显示
---
### qrCodeSearch.vue - 二维码扫描
**位置**`src/components/qrCodeSearch.vue`
**功能**:二维码扫描
---
## 列表与展示组件
### SectionCard.vue - 分组卡片
**位置**`src/components/SectionCard.vue`
**功能**:分组卡片组件
---
### SectionItem.vue - 分组列表项
**位置**`src/components/SectionItem.vue`
**功能**:分组列表项组件
---
### ListItemActions/ - 列表项操作按钮
**位置**`src/components/list/ListItemActions/`
**功能**:列表项操作按钮组
**关键特性**
- 权限检查
- 自动埋点上报
- 查看按钮、收藏按钮等
---
### MaterialCard.vue - 资料卡片(可复用)
**位置**`src/components/MaterialCard.vue`
**功能**:资料展示卡片
**关键特性**
- 自包含业务逻辑:查看、收藏
- 支持动态标签
- 文件大小格式化
- 学习人数显示
- 支持图片文件预览(使用 Taro.previewImage)
**使用页面**:首页、搜索页、周热门资料页
**使用的 Composable**
- useCollectOperation
- useListItemClick
---
### ProductCard.vue - 产品卡片(可复用)
**位置**`src/components/ProductCard.vue`
**功能**:产品展示卡片
**关键特性**
- 自定义样式:动态标签、封面图
- 支持产品详情查看
- 支持计划书功能
**使用页面**:首页、搜索页、产品中心页
---
## 表单与输入组件
### FilterTabs.vue - 过滤标签
**位置**`src/components/FilterTabs.vue`
**功能**:过滤标签组件
---
### SearchBar.vue - 搜索栏
**位置**`src/components/SearchBar.vue`
**功能**:搜索栏组件
---
## 文档预览组件
### DocumentPreview/ - 文档预览
**位置**`src/components/DocumentPreview/`
**功能**:文档预览组件
---
### PdfPreview.vue - PDF 预览
**位置**`src/components/PdfPreview.vue`
**功能**:PDF 文件预览
---
### OfficeViewer.vue - Office 文档查看器
**位置**`src/components/OfficeViewer.vue`
**功能**:Office 文档查看
---
## 业务组件
### PlanSchemes/ - 计划方案
**位置**`src/components/plan/PlanSchemes/`
**功能**:计划方案组件(SchemeA, SchemeB)
---
### PlanPopup/ - 计划弹窗
**位置**`src/components/plan/PlanPopup/`
**功能**:计划弹窗组件
**关键特性**
- 使用 provide/inject 模式实现父子弹窗通信
- 子弹窗打开时自动隐藏父级 footer
- 支持 AgePicker、DatePicker、SelectPicker 等字段组件
---
### PlanFields/ - 计划书表单字段
**位置**`src/components/plan/PlanFields/`
**功能**:计划书表单字段组件集
**子组件**
- `AgePicker.vue` - 年龄选择器
- `AmountInput.vue` - 金额输入框
- `DatePicker.vue` - 日期选择器
- `SelectPicker.vue` - 下拉选择器
- `RadioGroup.vue` - 单选按钮组
---
### PlanTemplates/ - 计划模板
**位置**`src/components/plan/PlanTemplates/`
**功能**:计划模板组件
---
## 工具组件
### PosterBuilder/ - 海报生成器
**位置**`src/components/PosterBuilder/`
**功能**:海报生成
---
### time-picker-data/ - 时间选择器数据
**位置**`src/components/time-picker-data/`
**功能**:时间选择器数据配置
---
## 可选功能组件
以下组件如果不需要,可以移除:
- **微信支付**`src/utils/wechatPay.js``src/api/wx/pay.js`
- **二维码**`src/components/qrCode.vue``src/components/qrCodeSearch.vue`
- **海报生成器**`src/components/PosterBuilder/`
- **时间选择器**`src/components/time-picker-data/`
---
## 组件使用原则
### 第 3 次出现原则
当相同代码模式出现 3 次时,**必须**抽取为组件。
### 组件自包含原则
对于重复的UI结构,抽取为可复用组件(如 MaterialCard、ProductCard):
- 组件应该自包含业务逻辑(查看、收藏等)
- 通过事件与父组件通信
- 避免在父组件中重复编写相同的逻辑代码
## 相关文档
- **[Composables 参考](composables.md)** - 可复用逻辑
# Composables 参考文档
本文档列出项目中所有 Composables 的详细说明。
## 项目中的 Composables
| Composable | 用途 | 文档 |
|-----------|------|------|
| `useSectionList` | 分组列表管理 | [详情](#usesectionlist) |
| `useFileOperation` | 文件下载、预览、打开 | [详情](#usefileoperation) |
| `useListItemClick` | 统一的列表点击处理 | [详情](#uselistitemclick) |
| `useCollectOperation` | 收藏操作 | [详情](#usecollectoperation) |
| `useEventTracking` | 事件埋点 | [详情](#useeventtracking) |
| `useGo` | 增强导航 | [详情](#usego) |
| `usePlanPermission` | 计划书权限检查 | [详情](#useplanpermission) |
---
## useSectionList
**位置**`src/composables/useSectionList.js`
**功能**:分组列表管理
**用途**:处理分组数据的展开/收起、过滤等逻辑
**示例**
```javascript
import { useSectionList } from '@/composables/useSectionList'
const { sections, toggleSection, isExpanded } = useSectionList(data)
```
---
## useFileOperation
**位置**`src/composables/useFileOperation.js`
**功能**:文件操作(下载、预览、打开)
**用途**:统一的文件操作逻辑,支持多种文件类型
**示例**
```javascript
import { useFileOperation } from '@/composables/useFileOperation'
const { downloadFile, previewFile, openFile } = useFileOperation()
// 下载文件
await downloadFile(file)
// 预览文件
await previewFile(file)
```
---
## useListItemClick
**位置**`src/composables/useListItemClick.js`
**功能**:统一的列表点击处理
**用途**:处理列表项的点击事件,包含权限检查和埋点
**示例**
```javascript
import { useListItemClick } from '@/composables/useListItemClick'
const { handleItemClick } = useListItemClick()
// 处理点击
await handleItemClick(item, () => {
// 点击后的操作
})
```
---
## useCollectOperation
**位置**`src/composables/useCollectOperation.js`
**功能**:收藏/取消收藏操作
**用途**:处理收藏状态切换和 API 调用
**示例**
```javascript
import { useCollectOperation } from '@/composables/useCollectOperation'
const { isCollected, toggleCollect } = useCollectOperation(metaId)
// 切换收藏状态
await toggleCollect()
```
---
## useEventTracking
**位置**`src/composables/useEventTracking.js`
**功能**:事件埋点
**用途**:统一的事件埋点功能,支持多种埋点类型
**事件类型**
- `READ_FILE` - 阅读素材
**示例**
```javascript
import { useEventTracking } from '@/composables/useEventTracking'
const { trackEvent, trackFileRead } = useEventTracking()
// 追踪阅读事件
await trackFileRead('file-id-123')
// 追踪自定义事件
await trackEvent('CUSTOM_EVENT', 'object-id', {
title: '文档标题',
category: '分类'
})
```
---
## useGo
**位置**`src/hooks/useGo.js`
**功能**:增强导航
**用途**:自动路径补全和便捷导航方法
**示例**
```javascript
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 导航到页面
go('/pages/detail/index')
// 带参数导航
go('/pages/product-detail/index', { id: 123 })
// 返回
go.back()
```
---
## usePlanPermission
**位置**`src/composables/usePlanPermission.js`
**功能**:计划书权限检查
**用途**:检查用户是否有权限操作计划书
**示例**
```javascript
import { usePlanPermission } from '@/composables/usePlanPermission'
const { checkPlanPermission } = usePlanPermission()
// 检查权限后执行操作
await checkPlanPermission(() => {
// 有权限后的操作
})
```
---
## 抽取原则
**第 3 次出现原则**
当相同代码模式出现 3 次时,**必须**抽取为 Composable。
**示例**
```javascript
// ❌ BAD - 在多个组件中重复
const handleClick = async () => {
if (!isLoggedIn()) {
Taro.showToast({ title: '请先登录', icon: 'none' })
return
}
// ... 业务逻辑
}
// ✅ GOOD - 抽取为 Composable
const { requireLogin } = usePermission()
await requireLogin(() => {
// ... 业务逻辑
})
```
---
## 创建新的 Composable
### 命名规范
- 使用 `use` 前缀
- 使用驼峰命名
- 名称应描述功能
**示例**
-`useUserData`
-`useFormValidation`
-`userData`
-`validation`
### 基本结构
```javascript
/**
* 使用 XXX 功能
*
* @description 功能描述
* @returns {Object} 返回值描述
*/
export function useXxx() {
// 响应式状态
const state = ref(null)
// 方法
const method = () => {
// ...
}
// 返回公共 API
return {
state,
method
}
}
```
### 使用示例
```javascript
// 在组件中使用
import { useXxx } from '@/composables/useXxx'
const { state, method } = useXxx()
```
---
## 相关文档
- **[组件参考](components.md)** - 可复用组件
- **[最佳实践](../best-practices.md)** - 代码复用原则
# 页面参考文档
本文档列出项目中所有页面的详细说明。
## 核心页面
### 1. pages/index/index - 首页
**功能**:产品展示、搜索、网格导航
**关键特性**
- 热门产品的"产品资料"按钮跳转到 `product-detail` 页面,带产品 ID
- 热门资料的"查看更多"跳转到 `material-list` 页面
- 网格导航图标跳转到各个业务页面
**使用的组件**
- MaterialCard - 资料卡片
- ProductCard - 产品卡片
---
### 2. pages/search/index - 搜索页
**功能**:产品和资料搜索
**关键特性**
- 支持实时搜索(输入关键字自动调用 searchAPI)
- 双Tab切换(产品、资料)
- 支持分页加载和触底加载更多
**使用的组件**
- MaterialCard
- ProductCard
---
### 3. pages/webview/index - WebView 包装器
**功能**:外部 URL 的 WebView 包装器
---
### 4. pages/document-preview/index - 文档预览页
**功能**:文档预览
---
### 5. pages/document-demo/index - 文档预览演示页
**功能**:文档预览演示
---
### 6. pages/onboarding/index - 新用户引导
**功能**:新用户引导流程
---
## 业务页面
### 7. pages/family-office/index - 家族办公室
**功能**:家族办公室服务
---
### 8. pages/product-center/index - 产品中心
**功能**:产品列表展示
**关键特性**
- 分类筛选
- 分页加载
---
### 9. pages/category-list/index - 分类列表
**功能**:分类列表展示
---
### 10. pages/product-detail/index - 产品详情
**功能**:产品详情展示
**关键特性**
- 通过 Taro 的 `useLoad` hook 接收 `id` 参数
- 导航示例:`go('/pages/product-detail/index', { id: 1 })`
- 参数可用于从 API 获取产品详情
---
### 11. pages/material-list/index - 资料列表
**功能**:资料/文档列表展示
**关键特性**
- 分类筛选
- 分页加载
- 触底加载更多
---
### 12. pages/week-hot-material/index - 周热门资料
**功能**:热门资料展示
**关键特性**
- 使用 MaterialCard 组件展示热门资料
- 支持分页加载和触底加载更多
---
### 13. pages/signing/index - 签约
**功能**:签约流程
---
### 14. pages/mine/index - 我的
**功能**:用户资料页面
**关键特性**
- 用户信息展示
- 设置入口
---
### 15. pages/plan/index - 计划书管理
**功能**:业务计划管理
**关键特性**
- 使用 PlanSchemes 和 PlanPopup 组件
- 支持嵌套弹窗交互(provide/inject 模式)
- 支持滚动加载和分页
---
### 16. pages/plan-submit-result/index - 计划提交结果
**功能**:计划提交结果展示
**关键特性**
- 导航按钮:返回上一页(非首页)
---
## 用户相关页面
### 17. pages/favorites/index - 收藏
**功能**:用户收藏列表
---
### 18. pages/avatar/index - 头像设置
**功能**:用户头像上传和设置
---
### 19. pages/message/index - 消息列表
**功能**:消息列表展示
**关键特性**
- 未读消息红点
- 消息分类
---
### 20. pages/message-detail/index - 消息详情
**功能**:消息详情展示
---
### 21. pages/feedback-list/index - 反馈列表
**功能**:意见反馈列表
---
### 22. pages/feedback/index - 用户反馈
**功能**:提交用户反馈
---
### 23. pages/login/index - 登录
**功能**:用户登录
**关键特性**
- 微信授权登录
- 登录状态检查
---
### 24. pages/help-center/index - 帮助中心
**功能**:帮助中心和常见问题
---
## 开发测试页面
### 25. pages/test-tabs/index - 标签页测试
**功能**:仅开发环境,用于测试标签页组件
---
## 页面注册
所有页面在 `src/app.config.js` 中注册:
```javascript
export default {
pages: [
'pages/index/index',
'pages/search/index',
// ... 其他页面
]
}
```
## 相关文档
- **[页面开发指南](guides/page-development.md)** - 如何添加新页面
- **[导航系统指南](guides/navigation.md)** - 导航和参数传递