docs(readme): 重构项目文档结构
- 优化 CLAUDE.md 文档,添加设计原则说明 - 新增 CHANGELOG.md 版本更新日志 - 新增最佳实践指南 - 新增调试指南 - 新增 API 集成、导航、页面开发指南 - 新增参考文档目录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
10 changed files
with
2147 additions
and
701 deletions
CHANGELOG.md
0 → 100644
| ... | @@ -2,6 +2,10 @@ | ... | @@ -2,6 +2,10 @@ |
| 2 | 2 | ||
| 3 | 本文件为 Claude Code (claude.ai/code) 在处理此仓库代码时提供指导。 | 3 | 本文件为 Claude Code (claude.ai/code) 在处理此仓库代码时提供指导。 |
| 4 | 4 | ||
| 5 | +**设计原则**:这是一个轻量级索引文档,只保留必须立即知道的信息。详细内容通过链接访问。 | ||
| 6 | + | ||
| 7 | +--- | ||
| 8 | + | ||
| 5 | ## 📚 项目文档索引 | 9 | ## 📚 项目文档索引 |
| 6 | 10 | ||
| 7 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 | 11 | - **[经验教训总结](docs/lessons-learned.md)** - Taro 项目开发经验、最佳实践和常见陷阱 |
| ... | @@ -9,6 +13,8 @@ | ... | @@ -9,6 +13,8 @@ |
| 9 | - **[变更日志](docs/CHANGELOG.md)** - 项目版本更新历史 | 13 | - **[变更日志](docs/CHANGELOG.md)** - 项目版本更新历史 |
| 10 | - **[项目 README](README.md)** - 项目概述和快速开始指南 | 14 | - **[项目 README](README.md)** - 项目概述和快速开始指南 |
| 11 | 15 | ||
| 16 | +--- | ||
| 17 | + | ||
| 12 | ## 🚀 开发命令 | 18 | ## 🚀 开发命令 |
| 13 | 19 | ||
| 14 | ### 核心命令 | 20 | ### 核心命令 |
| ... | @@ -53,26 +59,10 @@ git branch -d feature/功能名称 | ... | @@ -53,26 +59,10 @@ git branch -d feature/功能名称 |
| 53 | - `docs/style/refactor/test/chore` - 不更新 | 59 | - `docs/style/refactor/test/chore` - 不更新 |
| 54 | 60 | ||
| 55 | **实现方式**: | 61 | **实现方式**: |
| 56 | -- ✅ `commit-msg` hook �用 `scripts/update-version.sh` 自动更新 | 62 | +- ✅ 使用 `standard-version` 自动更新版本 |
| 57 | -- ✅ 更新后的 `package.json` 自动加入暂存区 | 63 | +- ✅ 配置 conventional commits 规范 |
| 58 | - ✅ 支持 `feat(version):` 格式跳过版本更新 | 64 | - ✅ 支持 `feat(version):` 格式跳过版本更新 |
| 59 | 65 | ||
| 60 | -**使用示例**: | ||
| 61 | -```bash | ||
| 62 | -# 在当前功能分支开发 | ||
| 63 | -git checkout -b feature/new-page | ||
| 64 | -# ... 开发代码 ... | ||
| 65 | -git add . | ||
| 66 | -git commit -m "feat(page): 添加新页面" | ||
| 67 | - | ||
| 68 | -# 合并回 develop | ||
| 69 | -git checkout develop | ||
| 70 | -git merge feature/new-page | ||
| 71 | - | ||
| 72 | -# 删除分支(可选) | ||
| 73 | -git branch -d feature/new-page | ||
| 74 | -``` | ||
| 75 | - | ||
| 76 | ### 其他平台构建 | 66 | ### 其他平台构建 |
| 77 | ```bash | 67 | ```bash |
| 78 | pnpm dev:alipay # 支付宝小程序开发 | 68 | pnpm dev:alipay # 支付宝小程序开发 |
| ... | @@ -80,106 +70,22 @@ pnpm dev:swan # 百度小程序开发 | ... | @@ -80,106 +70,22 @@ pnpm dev:swan # 百度小程序开发 |
| 80 | pnpm dev:tt # 字节跳动小程序开发 | 70 | pnpm dev:tt # 字节跳动小程序开发 |
| 81 | ``` | 71 | ``` |
| 82 | 72 | ||
| 73 | +--- | ||
| 74 | + | ||
| 83 | ## 📋 快速参考 | 75 | ## 📋 快速参考 |
| 84 | 76 | ||
| 85 | ### 🆕 最新更新(2026-02-15) | 77 | ### 🆕 最新更新(2026-02-15) |
| 86 | 78 | ||
| 87 | -**配置清理**: | 79 | +**详细更新记录**: |
| 88 | -- ✅ 禁用 Mock 数据(`USE_MOCK_DATA: false`) | 80 | +- **[变更日志](docs/CHANGELOG.md)** - 完整版本更新历史 |
| 89 | -- ✅ 移除测试计划书模板(测试计划书-智享未来2、长宁終身壽險計劃3) | 81 | +- **[接口联调日志](docs/api-integration-log.md)** - API 集成状态 |
| 90 | - | 82 | + |
| 91 | -**合并选项控制功能**: | 83 | +**最新亮点**: |
| 92 | -- ✅ 合并选项控制功能到 develop 分支 | 84 | +- 🎯 文档解析重大升级(多产品支持、智能字段提取) |
| 93 | -- ✅ 添加字段条件显示系统扩展功能 | 85 | +- 🎯 计划书模块重构(Schema 化、模板字段映射) |
| 94 | -- ✅ 重组脚本目录结构 | 86 | +- 🎯 消息功能全面优化(布局、交互、红点) |
| 95 | - | 87 | +- 🎯 选项控制功能合并 |
| 96 | -**文档解析重大升级**: | 88 | +- 🎯 Git 工作流优化(standard-version) |
| 97 | -- ✅ 支持多产品文档解析(自动识别和分割多个保险产品) | ||
| 98 | -- ✅ 实现智能字段提取器 smartExtractList() | ||
| 99 | -- ✅ 新增产品边界检测模块(支持产品代码和命名模式识别) | ||
| 100 | -- ✅ 完善 MCP 解析切换功能和审核流程 | ||
| 101 | -- ✅ 待审核文件按原始文档名分目录生成 | ||
| 102 | - | ||
| 103 | -**计划书模块重构**: | ||
| 104 | -- ✅ 计划书表单字段 Schema 化(人寿/重疾模板) | ||
| 105 | -- ✅ 优化字段配置管理,提取订单状态常量 | ||
| 106 | -- ✅ 重构表单提交逻辑,使用模板字段映射 | ||
| 107 | -- ✅ 完善表单交互和字段验证 | ||
| 108 | - | ||
| 109 | -**消息功能全面优化**: | ||
| 110 | -- ✅ 优化消息列表卡片布局,提升信息可读性 | ||
| 111 | -- ✅ 简化消息列表逻辑,移除冗余代码 | ||
| 112 | -- ✅ 优化消息详情页布局和样式 | ||
| 113 | -- ✅ 统一消息页面交互体验 | ||
| 114 | - | ||
| 115 | -**Git 工作流与版本管理**: | ||
| 116 | -- ✅ 使用 standard-version 替代自定义版本更新脚本 | ||
| 117 | -- ✅ 配置 conventional commits 规范 | ||
| 118 | -- ✅ 完善版本发布流程文档 | ||
| 119 | -- ✅ 移除旧的版本自动更新脚本 | ||
| 120 | - | ||
| 121 | -**Mock 数据优化**: | ||
| 122 | -- ✅ 优化产品列表 Mock 数据结构 | ||
| 123 | -- ✅ 添加测试商品到列表首位 | ||
| 124 | -- ✅ 修正搜索测试 Mock 开关 | ||
| 125 | - | ||
| 126 | -**计划书功能优化(历史更新)**: | ||
| 127 | -- ✅ 添加计划书卡片状态标记("生成中"/"已完成",黄色/绿色背景) | ||
| 128 | -- ✅ 修复嵌套弹窗层级冲突(使用 provide/inject 模式) | ||
| 129 | -- ✅ 优化页面滚动加载并清理调试代码 | ||
| 130 | -- ✅ 修复搜索栏清空按钮点击无效 | ||
| 131 | -- ✅ 修改提交结果页按钮为"返回上一页" | ||
| 132 | -- ✅ 预览成功后才调用查看接口,避免预览失败也翻状态 | ||
| 133 | -- ✅ 优化计划书提交跳转体验:关闭弹框时清理已选产品 | ||
| 134 | -- ✅ 提取计划书提交回调逻辑为 composable | ||
| 135 | - | ||
| 136 | -**认证与权限优化**: | ||
| 137 | -- ✅ 为所有制作计划书按钮添加登录权限检查 | ||
| 138 | -- ✅ 修复退出登录时红点状态未重置的问题 | ||
| 139 | -- ✅ 修复登录页返回按钮:清空 router store 并跳转到首页 | ||
| 140 | -- ✅ 修复 401 重定向死循环和返回报错问题 | ||
| 141 | - | ||
| 142 | -**消息功能优化**: | ||
| 143 | -- ✅ 配置 TabBar 红点功能使用新的 unread_msg_count 字段 | ||
| 144 | -- ✅ 修复 TabBar 未读红点显示问题 | ||
| 145 | -- ✅ 优化消息列表卡片布局,提升信息可读性 | ||
| 146 | -- ✅ 增加未读消息红点提示 | ||
| 147 | -- ✅ 优化消息详情页布局,避免内容重复显示 | ||
| 148 | -- ✅ 添加消息列表 API 错误提示 | ||
| 149 | - | ||
| 150 | -**样式改进**: | ||
| 151 | -- ✅ 增强资料卡片边框可见性(border-gray-200) | ||
| 152 | -- ✅ 增大产品中心和详情页的字体与图标尺寸 | ||
| 153 | -- ✅ 添加全局背景色 | ||
| 154 | -- ✅ 增大全局字体和图标尺寸以提升可读性 | ||
| 155 | -- ✅ 优化首页网格导航视觉体验 | ||
| 156 | -- ✅ 重构"我的"页面为专业高端风格 | ||
| 157 | -- ✅ 优化 ProductCard 组件视觉样式 | ||
| 158 | -- ✅ 统一视觉柔和度和整体设计一致性 | ||
| 159 | -- ✅ 优化首页头图 CDN 加载 | ||
| 160 | - | ||
| 161 | -**计划书字段优化**: | ||
| 162 | -- ✅ 优化提取金额字段并新增每年提取字段 | ||
| 163 | -- ✅ 隐藏产品详情页附件下载提示 | ||
| 164 | -- ✅ 优化输入框间距 | ||
| 165 | - | ||
| 166 | -**代码质量**: | ||
| 167 | -- ✅ 从版本控制中移除本地配置文件 settings.local.json | ||
| 168 | -- ✅ 禁用消息列表 Mock 数据,使用真实接口 | ||
| 169 | -- ✅ 清理调试日志 | ||
| 170 | - | ||
| 171 | -**API 集成进度**: | ||
| 172 | -- ✅ 总接口数:29,已完成:26 (89.7%) | ||
| 173 | -- ✅ 计划书模块接口联调完成(submitPlanAPI、listAPI) | ||
| 174 | - | ||
| 175 | -**新增功能**: | ||
| 176 | -- ✅ 消息列表和消息详情页 | ||
| 177 | -- ✅ 产品中心页 | ||
| 178 | -- ✅ 周热门资料页 | ||
| 179 | -- ✅ 分类列表页 | ||
| 180 | -- ✅ PlanFields 表单字段组件集 | ||
| 181 | -- ✅ useCollectOperation composable | ||
| 182 | -- ✅ usePlanPermission composable | ||
| 183 | 89 | ||
| 184 | --- | 90 | --- |
| 185 | 91 | ||
| ... | @@ -192,10 +98,11 @@ pnpm dev:tt # 字节跳动小程序开发 | ... | @@ -192,10 +98,11 @@ pnpm dev:tt # 字节跳动小程序开发 |
| 192 | | SVG 图标加载失败(500 错误) | 使用 `import` 导入 | [经验教训](docs/lessons-learned.md#静态资源加载问题) | | 98 | | SVG 图标加载失败(500 错误) | 使用 `import` 导入 | [经验教训](docs/lessons-learned.md#静态资源加载问题) | |
| 193 | | 代码重复 3 次 | 抽取为 Composable | [经验教训](docs/lessons-learned.md#组件抽取与复用) | | 99 | | 代码重复 3 次 | 抽取为 Composable | [经验教训](docs/lessons-learned.md#组件抽取与复用) | |
| 194 | | 组件对象响应式警告 | 使用 `shallowRef` + `markRaw` | [经验教训](docs/lessons-learned.md#性能优化) | | 100 | | 组件对象响应式警告 | 使用 `shallowRef` + `markRaw` | [经验教训](docs/lessons-learned.md#性能优化) | |
| 195 | -| 业务状态标记显示 | 使用条件类名 + 语义化颜色 | [经验教训](docs/lessons-learned.md#案例-4-状态标记组件模式) | | ||
| 196 | | 嵌套弹窗层级冲突 | 使用 provide/inject 模式 | 最近提交记录(3357bed) | | 101 | | 嵌套弹窗层级冲突 | 使用 provide/inject 模式 | 最近提交记录(3357bed) | |
| 197 | | 401 重定向死循环 | 检查重定向拦截器逻辑 | 最近提交记录(6d2a4ec) | | 102 | | 401 重定向死循环 | 检查重定向拦截器逻辑 | 最近提交记录(6d2a4ec) | |
| 198 | 103 | ||
| 104 | +--- | ||
| 105 | + | ||
| 199 | ### 🎯 核心架构模式 | 106 | ### 🎯 核心架构模式 |
| 200 | 107 | ||
| 201 | 1. **认证流程** - 静默认证 + 401 自动刷新 | 108 | 1. **认证流程** - 静默认证 + 401 自动刷新 |
| ... | @@ -203,6 +110,8 @@ pnpm dev:tt # 字节跳动小程序开发 | ... | @@ -203,6 +110,8 @@ pnpm dev:tt # 字节跳动小程序开发 |
| 203 | 3. **样式策略** - TailwindCSS(80%) + Less(20%) | 110 | 3. **样式策略** - TailwindCSS(80%) + Less(20%) |
| 204 | 4. **组件抽取** - "第 3 次出现原则" | 111 | 4. **组件抽取** - "第 3 次出现原则" |
| 205 | 112 | ||
| 113 | +--- | ||
| 114 | + | ||
| 206 | ### 📦 技术栈 | 115 | ### 📦 技术栈 |
| 207 | 116 | ||
| 208 | - **框架**: Taro 4.1.9 + Vue 3.3.0 + Composition API | 117 | - **框架**: Taro 4.1.9 + Vue 3.3.0 + Composition API |
| ... | @@ -225,26 +134,11 @@ pnpm dev:tt # 字节跳动小程序开发 | ... | @@ -225,26 +134,11 @@ pnpm dev:tt # 字节跳动小程序开发 |
| 225 | - **签单** - 签约流程 | 134 | - **签单** - 签约流程 |
| 226 | - **用户中心** - 个人资料、收藏、反馈、帮助中心 | 135 | - **用户中心** - 个人资料、收藏、反馈、帮助中心 |
| 227 | 136 | ||
| 228 | -## 🏗️ 核心架构 | 137 | +--- |
| 229 | - | ||
| 230 | -### 1. 可复用的导航组件 | ||
| 231 | - | ||
| 232 | -**TabBar 组件**(`src/components/TabBar.vue`) | ||
| 233 | -- 固定底部导航栏,自动适配安全区域 | ||
| 234 | -- 支持图标 + 文字布局,激活状态高亮 | ||
| 235 | -- 使用于:首页、我的、家办、知识库、签单页面 | ||
| 236 | - | ||
| 237 | -**NavHeader 组件**(`src/components/NavHeader.vue`) | ||
| 238 | -- 带返回按钮的自定义导航头 | ||
| 239 | -- 透明/背景变体,刘海屏设备的安全区域内边距 | ||
| 240 | - | ||
| 241 | -**IconFont 组件**(`src/components/IconFont.vue`) | ||
| 242 | -- 自定义图标的图标字体包装器 | ||
| 243 | -- ⚠️ **动态切换时需添加 `:key="name"`** [详见经验教训](docs/lessons-learned.md#坑-2-iconfont-动态切换不响应) | ||
| 244 | 138 | ||
| 245 | -### 2. 身份认证流程(必需) | 139 | +## 🏗️ 核心架构 |
| 246 | 140 | ||
| 247 | -项目具有完善的身份认证系统,支持静默认证和会话管理。 | 141 | +### 1. 身份认证流程(必需) |
| 248 | 142 | ||
| 249 | **核心文件**: | 143 | **核心文件**: |
| 250 | - `src/utils/openid.js` - 微信授权和会话管理 | 144 | - `src/utils/openid.js` - 微信授权和会话管理 |
| ... | @@ -256,24 +150,16 @@ pnpm dev:tt # 字节跳动小程序开发 | ... | @@ -256,24 +150,16 @@ pnpm dev:tt # 字节跳动小程序开发 |
| 256 | - 调用 `wx.login()` 获取 code | 150 | - 调用 `wx.login()` 获取 code |
| 257 | - 调用后端 `/srv/?a=openid` 接口换取 sessionid | 151 | - 调用后端 `/srv/?a=openid` 接口换取 sessionid |
| 258 | - 自动将 sessionid 写入 localStorage | 152 | - 自动将 sessionid 写入 localStorage |
| 259 | - - 尝试调用 `loginStatusAPI` 获取用户信息(如已登录) | ||
| 260 | 153 | ||
| 261 | 2. **401 自动刷新**: | 154 | 2. **401 自动刷新**: |
| 262 | - API 返回 401 时触发 | 155 | - API 返回 401 时触发 |
| 263 | - 拦截器保存当前页面路径 | 156 | - 拦截器保存当前页面路径 |
| 264 | - 调用 `miniProgramAuth()` 重新获取会话 | 157 | - 调用 `miniProgramAuth()` 重新获取会话 |
| 265 | - 使用新会话重试原始请求 | 158 | - 使用新会话重试原始请求 |
| 266 | - - 如果刷新失败,跳转到登录页 | ||
| 267 | - | ||
| 268 | -**重要**: | ||
| 269 | -- 后端必须提供 `/srv/?a=openid` 端点用于微信登录 | ||
| 270 | -- 后端必须提供 `loginStatusAPI` 接口用于检查登录状态 | ||
| 271 | -- sessionid 存储在 `localStorage.sessionid` 中 | ||
| 272 | -- sessionid 由请求拦截器自动注入到请求头的 `cookie` 字段 | ||
| 273 | 159 | ||
| 274 | -### 3. API 层架构 | 160 | +### 2. API 层架构 |
| 275 | 161 | ||
| 276 | -**API 定义模式**(`src/api/index.js`): | 162 | +**API 定义**(`src/api/index.js`): |
| 277 | ```javascript | 163 | ```javascript |
| 278 | export const yourAPI = (params) => { | 164 | export const yourAPI = (params) => { |
| 279 | return buildApiUrl('your_action', params) | 165 | return buildApiUrl('your_action', params) |
| ... | @@ -285,7 +171,7 @@ export const yourAPI = (params) => { | ... | @@ -285,7 +171,7 @@ export const yourAPI = (params) => { |
| 285 | - 处理常见错误场景 | 171 | - 处理常见错误场景 |
| 286 | - **始终检查 `res.code === 1` 判断成功** | 172 | - **始终检查 `res.code === 1` 判断成功** |
| 287 | 173 | ||
| 288 | -### 4. 增强导航系统 | 174 | +### 3. 增强导航系统 |
| 289 | 175 | ||
| 290 | **useGo Hook**(`src/hooks/useGo.js`): | 176 | **useGo Hook**(`src/hooks/useGo.js`): |
| 291 | ```javascript | 177 | ```javascript |
| ... | @@ -297,496 +183,53 @@ go('/pages/product-detail/index', { id: 123 }) // 带查询参数 | ... | @@ -297,496 +183,53 @@ go('/pages/product-detail/index', { id: 123 }) // 带查询参数 |
| 297 | go.back() // 返回上一页 | 183 | go.back() // 返回上一页 |
| 298 | ``` | 184 | ``` |
| 299 | 185 | ||
| 300 | -**路由存储**(`src/stores/router.js`): | 186 | +### 4. 样式处理策略 |
| 301 | -- 维护已访问路由的栈 | ||
| 302 | -- 用于认证回调导航 | ||
| 303 | - | ||
| 304 | -### 5. 可复用 Composables | ||
| 305 | - | ||
| 306 | -**项目中的 Composables**: | ||
| 307 | - | ||
| 308 | -| Composable | 用途 | 文档 | | ||
| 309 | -|-----------|------|------| | ||
| 310 | -| `useSectionList` | 分组列表管理 | [经验教训](docs/lessons-learned.md#案例-1-usesectionlist-composable) | | ||
| 311 | -| `useFileOperation` | 文件下载、预览、打开 | [经验教训](docs/lessons-learned.md#案例-2-usefileoperation-composable) | | ||
| 312 | -| `useListItemClick` | 统一的列表点击处理 | [经验教训](docs/lessons-learned.md#案例-3-uselistitemclick-composable) | | ||
| 313 | -| `useCollectOperation` | 收藏操作(新增) | 处理收藏/取消收藏逻辑 | | ||
| 314 | - | ||
| 315 | -**抽取原则**:"第 3 次出现原则" - 当相同代码模式出现 3 次时,**必须**抽取为 Composable。 | ||
| 316 | - | ||
| 317 | -**组件自包含原则**(新增): | ||
| 318 | -- 对于重复的UI结构,抽取为可复用组件(如 MaterialCard、ProductCard) | ||
| 319 | -- 组件应该自包含业务逻辑(查看、收藏等),通过事件与父组件通信 | ||
| 320 | -- 避免在父组件中重复编写相同的逻辑代码 | ||
| 321 | - | ||
| 322 | -### 6. 样式处理策略 | ||
| 323 | 187 | ||
| 324 | **TailwindCSS vs Less 使用指南**: | 188 | **TailwindCSS vs Less 使用指南**: |
| 325 | 189 | ||
| 326 | | 场景 | 使用 | 比例 | | 190 | | 场景 | 使用 | 比例 | |
| 327 | |------|------|------| | 191 | |------|------|------| |
| 328 | -| 布局(flex、grid、absolute) | TailwindCSS | 80% | | 192 | +| 布局、间距、排版、颜色 | TailwindCSS | 80% | |
| 329 | -| 间距(padding、margin、gap) | TailwindCSS | | | 193 | +| 组件特定样式、深度选择器 | Less | 20% | |
| 330 | -| 排版(font-size、text-align) | TailwindCSS | | | ||
| 331 | -| 颜色(bg-*、text-*、border-*) | TailwindCSS | | | ||
| 332 | -| 响应式设计(sm:、md:、lg:) | TailwindCSS | | | ||
| 333 | -| 组件特定样式(需要 scoped) | Less | 20% | | ||
| 334 | -| 深度选择器(`:deep()`) | Less | | | ||
| 335 | -| 动画和过渡 | Less | | | ||
| 336 | -| 伪元素(`::before`、`::after`) | Less | | | ||
| 337 | 194 | ||
| 338 | -[详见样式处理策略](docs/lessons-learned.md#✅-tailwindcss-vs-less-使用指南) | 195 | +### 5. 响应式优化 |
| 339 | - | ||
| 340 | -### 7. 响应式优化 | ||
| 341 | 196 | ||
| 342 | **处理组件对象响应式**: | 197 | **处理组件对象响应式**: |
| 343 | 198 | ||
| 344 | ```javascript | 199 | ```javascript |
| 345 | // ❌ BAD - 深度响应式导致性能问题 | 200 | // ❌ BAD - 深度响应式导致性能问题 |
| 346 | const menuItems = ref([ | 201 | const menuItems = ref([ |
| 347 | - { icon: IconFont, name: 'heart' } // Vue 会深度代理组件对象 | 202 | + { icon: IconFont, name: 'heart' } |
| 348 | ]) | 203 | ]) |
| 349 | 204 | ||
| 350 | // ✅ GOOD - 使用 shallowRef + markRaw | 205 | // ✅ GOOD - 使用 shallowRef + markRaw |
| 351 | import { shallowRef, markRaw } from 'vue' | 206 | import { shallowRef, markRaw } from 'vue' |
| 352 | - | ||
| 353 | const menuItems = shallowRef([ | 207 | const menuItems = shallowRef([ |
| 354 | - { icon: markRaw(IconFont), name: 'heart' } // 避免深度代理 | 208 | + { icon: markRaw(IconFont), name: 'heart' } |
| 355 | ]) | 209 | ]) |
| 356 | ``` | 210 | ``` |
| 357 | 211 | ||
| 358 | -[详见性能优化](docs/lessons-learned.md#✅-1-响应式数据优化) | 212 | +--- |
| 359 | - | ||
| 360 | -### 页面结构 | ||
| 361 | - | ||
| 362 | -所有页面遵循以下目录结构: | ||
| 363 | -``` | ||
| 364 | -src/pages/your-page/ | ||
| 365 | -├── index.vue # 页面组件(必须使用 <script setup>) | ||
| 366 | -├── index.config.js # 页面配置(navigationBarTitleText 等) | ||
| 367 | -└── assets/ # 页面特定资源(可选) | ||
| 368 | -``` | ||
| 369 | - | ||
| 370 | -### 当前页面 | ||
| 371 | - | ||
| 372 | -所有页面按 `src/app.config.js` 注册顺序排列: | ||
| 373 | - | ||
| 374 | -**核心页面**: | ||
| 375 | -1. `pages/index/index` - 首页(产品展示、搜索、网格导航) | ||
| 376 | - - 热门产品的"产品资料"按钮跳转到 `product-detail` 页面,带产品 ID | ||
| 377 | - - 热门资料的"查看更多"跳转到 `material-list` 页面 | ||
| 378 | - - 网格导航图标跳转到各个业务页面 | ||
| 379 | - - 使用可复用组件(MaterialCard、ProductCard) | ||
| 380 | -2. `pages/search/index` - 产品和资料搜索页 | ||
| 381 | - - 支持实时搜索(输入关键字自动调用 searchAPI) | ||
| 382 | - - 双Tab切换(产品、资料) | ||
| 383 | - - 支持分页加载和触底加载更多 | ||
| 384 | - - 使用可复用组件(MaterialCard、ProductCard) | ||
| 385 | -3. `pages/webview/index` - 外部 URL 的 WebView 包装器 | ||
| 386 | -4. `pages/document-preview/index` - 文档预览页 | ||
| 387 | -5. `pages/document-demo/index` - 文档预览演示页 | ||
| 388 | -6. `pages/onboarding/index` - 新用户引导 | ||
| 389 | - | ||
| 390 | -**业务页面**: | ||
| 391 | -7. `pages/family-office/index` - 家族办公室服务 | ||
| 392 | -8. `pages/product-center/index` - 产品中心页 | ||
| 393 | -9. `pages/category-list/index` - 分类列表页 | ||
| 394 | -10. `pages/product-detail/index` - 产品详情页 | ||
| 395 | - - 通过 Taro 的 `useLoad` hook 接收 `id` 参数 | ||
| 396 | - - 导航示例:`go('/pages/product-detail/index', { id: 1 })` | ||
| 397 | - - 参数可用于从 API 获取产品详情 | ||
| 398 | -11. `pages/material-list/index` - 资料/文档列表页 | ||
| 399 | -12. `pages/week-hot-material/index` - 周热门资料页 | ||
| 400 | - - 使用 MaterialCard 组件展示热门资料 | ||
| 401 | - - 支持分页加载和触底加载更多 | ||
| 402 | -13. `pages/signing/index` - 签约 | ||
| 403 | -14. `pages/mine/index` - 用户资料 | ||
| 404 | -15. `pages/plan/index` - 业务计划管理 | ||
| 405 | - - 使用 PlanSchemes 和 PlanPopup 组件 | ||
| 406 | - - 支持嵌套弹窗交互(provide/inject 模式) | ||
| 407 | - - 支持滚动加载和分页 | ||
| 408 | -16. `pages/plan-submit-result/index` - 计划提交结果页 | ||
| 409 | - - 导航按钮:返回上一页(非首页) | ||
| 410 | - | ||
| 411 | -**用户相关页面**: | ||
| 412 | -17. `pages/favorites/index` - 用户收藏 | ||
| 413 | -18. `pages/avatar/index` - 头像设置 | ||
| 414 | -19. `pages/message/index` - 消息列表页 | ||
| 415 | -20. `pages/message-detail/index` - 消息详情页 | ||
| 416 | -21. `pages/feedback-list/index` - 反馈列表 | ||
| 417 | -22. `pages/feedback/index` - 用户反馈 | ||
| 418 | -23. `pages/login/index` - 登录页 | ||
| 419 | -24. `pages/help-center/index` - 帮助中心和常见问题页 | ||
| 420 | - | ||
| 421 | -**开发测试页面**(仅开发环境): | ||
| 422 | -- `pages/test-tabs/index` - 标签页测试 | ||
| 423 | - | ||
| 424 | -### 组件库 | ||
| 425 | - | ||
| 426 | -**导航与布局组件**: | ||
| 427 | -- `TabBar.vue` - 底部导航栏 | ||
| 428 | -- `NavHeader.vue` - 自定义导航头 | ||
| 429 | -- `indexNav.vue` - 首页网格导航 | ||
| 430 | - | ||
| 431 | -**图标与媒体组件**: | ||
| 432 | -- `IconFont.vue` - 图标字体包装器 | ||
| 433 | -- `qrCode.vue` - 二维码显示 | ||
| 434 | -- `qrCodeSearch.vue` - 二维码扫描 | ||
| 435 | - | ||
| 436 | -**列表与展示组件**: | ||
| 437 | -- `SectionCard.vue` - 分组卡片组件 | ||
| 438 | -- `SectionItem.vue` - 分组列表项组件 | ||
| 439 | -- `ListItemActions/` - 列表项操作按钮 | ||
| 440 | -- `MaterialCard.vue` - 资料卡片组件(可复用) | ||
| 441 | - - 自包含业务逻辑:查看、收藏 | ||
| 442 | - - 支持动态标签、文件大小格式化、学习人数显示 | ||
| 443 | - - 支持图片文件预览(使用 Taro.previewImage) | ||
| 444 | - - 使用页面:首页、搜索页、周热门资料页 | ||
| 445 | - - 使用 composable:useCollectOperation、useListItemClick | ||
| 446 | -- `ProductCard.vue` - 产品卡片组件(可复用) | ||
| 447 | - - 自定义样式:动态标签、封面图 | ||
| 448 | - - 支持产品详情查看和计划书功能 | ||
| 449 | - - 使用页面:首页、搜索页、产品中心页 | ||
| 450 | - | ||
| 451 | -**表单与输入组件**: | ||
| 452 | -- `FilterTabs.vue` - 过滤标签组件 | ||
| 453 | -- `SearchBar.vue` - 搜索栏组件 | ||
| 454 | - | ||
| 455 | -**文档预览组件**: | ||
| 456 | -- `DocumentPreview/` - 文档预览组件 | ||
| 457 | -- `PdfPreview.vue` - PDF 预览组件 | ||
| 458 | -- `OfficeViewer.vue` - Office 文档查看器 | ||
| 459 | - | ||
| 460 | -**业务组件**: | ||
| 461 | -- `PlanSchemes/` - 计划方案组件(SchemeA, SchemeB) | ||
| 462 | -- `PlanPopup/` - 计划弹窗组件 | ||
| 463 | - - 使用 provide/inject 模式实现父子弹窗通信 | ||
| 464 | - - 子弹窗打开时自动隐藏父级 footer | ||
| 465 | - - 支持 AgePicker、DatePicker、SelectPicker 等字段组件 | ||
| 466 | -- `PlanFields/` - 计划书表单字段组件 | ||
| 467 | - - `AgePicker.vue` - 年龄选择器 | ||
| 468 | - - `AmountInput.vue` - 金额输入框 | ||
| 469 | - - `DatePicker.vue` - 日期选择器 | ||
| 470 | - - `SelectPicker.vue` - 下拉选择器 | ||
| 471 | - - `RadioGroup.vue` - 单选按钮组 | ||
| 472 | -- `PlanTemplates/` - 计划模板组件 | ||
| 473 | - | ||
| 474 | -**工具组件**: | ||
| 475 | -- `PosterBuilder/` - 海报生成器 | ||
| 476 | -- `time-picker-data/` - 时间选择器数据 | ||
| 477 | - | ||
| 478 | -### 路径别名 | ||
| 479 | -全部配置在 `config/index.js:30-38`: | ||
| 480 | -```javascript | ||
| 481 | -@/utils → src/utils | ||
| 482 | -@/components → src/components | ||
| 483 | -@/images → src/assets/images | ||
| 484 | -@/assets → src/assets | ||
| 485 | -@/composables→ src/composables | ||
| 486 | -@/api → src/api | ||
| 487 | -@/stores → src/stores | ||
| 488 | -@/hooks → src/hooks | ||
| 489 | -``` | ||
| 490 | - | ||
| 491 | -### 配置管理 | ||
| 492 | - | ||
| 493 | -**环境配置**(`src/utils/config.js`): | ||
| 494 | -**⚠️ 使用前必须修改**: | ||
| 495 | -- `BASE_URL` - 设置开发/生产域名 | ||
| 496 | -- `REQUEST_DEFAULT_PARAMS.f` - 设置业务模块标识符 | ||
| 497 | -- `REQUEST_DEFAULT_PARAMS.client_name` - 设置应用名称 | ||
| 498 | - | ||
| 499 | -**构建配置**(`config/index.js`): | ||
| 500 | -- 路径别名 | ||
| 501 | -- 设计宽度规则 | ||
| 502 | -- NutUI 自动导入 | ||
| 503 | -- 平台特定设置 | ||
| 504 | - | ||
| 505 | -**应用配置**(`src/app.config.js`): | ||
| 506 | -- 页面路由注册 | ||
| 507 | -- 窗口配置 | ||
| 508 | -- 标签栏配置(可选) | ||
| 509 | -- 分包(如需要) | ||
| 510 | - | ||
| 511 | -## 🔧 重要实现细节 | ||
| 512 | - | ||
| 513 | -### 会话管理 | ||
| 514 | -- **会话 ID 存储**:`localStorage.sessionid` | ||
| 515 | -- **静默登录**:`openid.js` 的 `miniProgramAuth()` 函数 | ||
| 516 | - - 调用 `wx.login()` 获取 code | ||
| 517 | - - 调用 `/srv/?a=openid` 接口换取 sessionid | ||
| 518 | - - 自动写入 localStorage | ||
| 519 | -- **请求注入**:`request.js` 拦截器自动读取并注入到请求头的 `cookie` 字段 | ||
| 520 | -- **会话清除**:登出或 401 失败时清除 | ||
| 521 | -- **401 刷新**:拦截器捕获 401 → 调用 `miniProgramAuth()` → 重试原始请求 | ||
| 522 | - | ||
| 523 | -### 请求超时处理 | ||
| 524 | -- 默认超时:5 秒(`src/utils/request.js:79`) | ||
| 525 | -- 通过 `is_timeout_error()` 辅助函数检测超时 | ||
| 526 | -- 通过 `is_network_error()` 辅助函数检测网络错误 | ||
| 527 | -- 两者都会触发弱网络降级流程 | ||
| 528 | - | ||
| 529 | -### NutUI 自动导入 | ||
| 530 | -NutUI 组件通过 unplugin-vue-components 自动导入(`config/index.js:91-93`)。 | ||
| 531 | -**无需手动导入** - 直接在模板中使用组件即可。 | ||
| 532 | - | ||
| 533 | -### TailwindCSS 集成 | ||
| 534 | -- 为小程序兼容性禁用了 Preflight | ||
| 535 | -- 启用了 `rem2rpx` 转换 | ||
| 536 | -- 内容路径配置在 `tailwind.config.js` 中 | ||
| 537 | -- [详见样式处理策略](docs/lessons-learned.md#✅-tailwindcss-vs-less-使用指南) | ||
| 538 | - | ||
| 539 | -## 📦 可选功能 | ||
| 540 | - | ||
| 541 | -如果不需要,可以移除以下功能: | ||
| 542 | -- **微信支付**:`src/utils/wechatPay.js`、`src/api/wx/pay.js` | ||
| 543 | -- **二维码**:`src/components/qrCode.vue`、`src/components/qrCodeSearch.vue` | ||
| 544 | -- **海报生成器**:`src/components/PosterBuilder/` | ||
| 545 | -- **时间选择器**:`src/components/time-picker-data/` | ||
| 546 | - | ||
| 547 | -## 开发工作流 | ||
| 548 | - | ||
| 549 | -### 添加新页面 | ||
| 550 | - | ||
| 551 | -1. **创建页面目录**: | ||
| 552 | - ```bash | ||
| 553 | - src/pages/your-page/ | ||
| 554 | - ├── index.vue | ||
| 555 | - └── index.config.js | ||
| 556 | - ``` | ||
| 557 | - | ||
| 558 | -2. **配置页面**(`index.config.js`): | ||
| 559 | - ```javascript | ||
| 560 | - export default { | ||
| 561 | - navigationBarTitleText: '您的页面标题', | ||
| 562 | - enablePullDownRefresh: true, | ||
| 563 | - backgroundColor: '#f5f5f5' | ||
| 564 | - } | ||
| 565 | - ``` | ||
| 566 | - | ||
| 567 | -3. **在 `src/app.config.js` 中注册路由**: | ||
| 568 | - ```javascript | ||
| 569 | - pages: [ | ||
| 570 | - 'pages/your-page/index', | ||
| 571 | - // ... | ||
| 572 | - ] | ||
| 573 | - ``` | ||
| 574 | - | ||
| 575 | -4. **在 `index.vue` 中使用 composition API**: | ||
| 576 | - ```vue | ||
| 577 | - <script setup> | ||
| 578 | - import { ref } from 'vue' | ||
| 579 | - import { useLoad, useShow } from '@tarojs/taro' | ||
| 580 | - | ||
| 581 | - const pageId = ref(null) | ||
| 582 | - | ||
| 583 | - useLoad((options) => { | ||
| 584 | - console.log('页面加载,参数:', options) | ||
| 585 | - // 接收导航参数 | ||
| 586 | - if (options.id) { | ||
| 587 | - pageId.value = options.id | ||
| 588 | - // 根据 ID 获取数据 | ||
| 589 | - } | ||
| 590 | - }) | ||
| 591 | - | ||
| 592 | - useShow(() => { | ||
| 593 | - console.log('页面显示') | ||
| 594 | - }) | ||
| 595 | - | ||
| 596 | - // 您的组件逻辑 | ||
| 597 | - </script> | ||
| 598 | - ``` | ||
| 599 | - | ||
| 600 | - **带参数导航**: | ||
| 601 | - ```javascript | ||
| 602 | - // 从另一个页面 | ||
| 603 | - import { useGo } from '@/hooks/useGo' | ||
| 604 | - const go = useGo() | ||
| 605 | - | ||
| 606 | - // 带查询参数导航 | ||
| 607 | - go('/pages/product-detail/index', { id: 123, type: 'insurance' }) | ||
| 608 | - ``` | ||
| 609 | - | ||
| 610 | -5. **添加导航**(可选的 TabBar 集成): | ||
| 611 | - - 导入并使用 `TabBar` 组件 | ||
| 612 | - - 根据路由配置激活状态 | ||
| 613 | - | ||
| 614 | -### 添加 API 调用 | ||
| 615 | - | ||
| 616 | -1. **在 `src/api/index.js` 中定义 API**: | ||
| 617 | - ```javascript | ||
| 618 | - export const getProductListAPI = (params) => { | ||
| 619 | - return buildApiUrl('product_list', params) | ||
| 620 | - } | ||
| 621 | - ``` | ||
| 622 | - | ||
| 623 | -2. **在页面中使用**: | ||
| 624 | - ```javascript | ||
| 625 | - import { getProductListAPI } from '@/api' | ||
| 626 | - import { fn } from '@/api/fn' | ||
| 627 | - | ||
| 628 | - const fetchProducts = async () => { | ||
| 629 | - try { | ||
| 630 | - const res = await fn(getProductListAPI({ page: 1 })) | ||
| 631 | - if (res.code === 1) { | ||
| 632 | - products.value = res.data | ||
| 633 | - } | ||
| 634 | - } catch (err) { | ||
| 635 | - console.error('获取产品失败:', err) | ||
| 636 | - } | ||
| 637 | - } | ||
| 638 | - ``` | ||
| 639 | - | ||
| 640 | -### 使用导航 | ||
| 641 | - | ||
| 642 | -**推荐**:使用 `useGo` hook 进行增强导航: | ||
| 643 | -```javascript | ||
| 644 | -import { useGo } from '@/hooks/useGo' | ||
| 645 | - | ||
| 646 | -const go = useGo() | ||
| 647 | - | ||
| 648 | -// 导航到页面 | ||
| 649 | -go('/pages/detail/index') | ||
| 650 | - | ||
| 651 | -// 带参数导航(例如产品 ID) | ||
| 652 | -go('/pages/product-detail/index', { id: 123 }) | ||
| 653 | - | ||
| 654 | -// 带多个参数导航 | ||
| 655 | -go('/pages/material-list/index', { category: 'insurance', page: 1 }) | ||
| 656 | - | ||
| 657 | -// 返回 | ||
| 658 | -go.back() | ||
| 659 | -``` | ||
| 660 | - | ||
| 661 | -**在目标页面接收参数**: | ||
| 662 | -```javascript | ||
| 663 | -import { useLoad } from '@tarojs/taro' | ||
| 664 | -import { ref } from 'vue' | ||
| 665 | - | ||
| 666 | -const productId = ref(null) | ||
| 667 | - | ||
| 668 | -useLoad((options) => { | ||
| 669 | - // 访问导航参数 | ||
| 670 | - console.log('接收到的参数:', options) | ||
| 671 | - productId.value = options.id | ||
| 672 | - | ||
| 673 | - // 根据参数获取数据 | ||
| 674 | - fetchProductDetail(options.id) | ||
| 675 | -}) | ||
| 676 | -``` | ||
| 677 | - | ||
| 678 | -**替代方案**:使用 Taro 内置导航: | ||
| 679 | -```javascript | ||
| 680 | -import Taro from '@tarojs/taro' | ||
| 681 | - | ||
| 682 | -Taro.navigateTo({ | ||
| 683 | - url: '/pages/detail/index?id=123' | ||
| 684 | -}) | ||
| 685 | -``` | ||
| 686 | - | ||
| 687 | -### 使用 Pinia 状态管理 | ||
| 688 | - | ||
| 689 | -**创建 store**(`src/stores/yourStore.js`): | ||
| 690 | -```javascript | ||
| 691 | -import { defineStore } from 'pinia' | ||
| 692 | -import { ref } from 'vue' | ||
| 693 | - | ||
| 694 | -export const useYourStore = defineStore('yourStore', () => { | ||
| 695 | - const state = ref(null) | ||
| 696 | - | ||
| 697 | - function setState(newState) { | ||
| 698 | - state.value = newState | ||
| 699 | - } | ||
| 700 | - | ||
| 701 | - return { state, setState } | ||
| 702 | -}) | ||
| 703 | -``` | ||
| 704 | - | ||
| 705 | -**在组件中使用**: | ||
| 706 | -```javascript | ||
| 707 | -import { useYourStore } from '@/stores/yourStore' | ||
| 708 | - | ||
| 709 | -const store = useYourStore() | ||
| 710 | -store.setState('新值') | ||
| 711 | -``` | ||
| 712 | - | ||
| 713 | -### 使用 Mock 数据工具 | ||
| 714 | - | ||
| 715 | -项目提供了统一的 Mock 数据工具(`src/utils/mockData.js`),用于开发阶段测试分页加载等功能。 | ||
| 716 | - | ||
| 717 | -**支持的 Mock API**: | ||
| 718 | - | ||
| 719 | -| API 名称 | 功能说明 | 使用页面 | | ||
| 720 | -|---------|---------|---------| | ||
| 721 | -| `mockWeekHotAPI` | 周热门资料 | 周热门资料页 | | ||
| 722 | -| `mockFileListAPI` | 资料列表 | 资料列表页 | | ||
| 723 | -| `mockProductListAPI` | 产品列表 | 产品中心页 | | ||
| 724 | -| `mockSearchAPI` | 搜索(产品+资料) | 搜索页 | | ||
| 725 | -| `mockMessageListAPI` | 消息列表 | 消息列表页 | | ||
| 726 | -| `mockFavoriteListAPI` | 收藏列表 | 收藏页 | | ||
| 727 | -| `mockFeedbackListAPI` | 意见反馈列表 | 意见反馈页 | | ||
| 728 | - | ||
| 729 | -**使用方式**: | ||
| 730 | - | ||
| 731 | -1. **在页面中导入 Mock 函数**: | ||
| 732 | -```javascript | ||
| 733 | -import { mockWeekHotAPI } from '@/utils/mockData' | ||
| 734 | -``` | ||
| 735 | - | ||
| 736 | -2. **设置 Mock 开关**: | ||
| 737 | -```javascript | ||
| 738 | -// 开发环境使用 Mock,生产环境使用真实 API | ||
| 739 | -const USE_MOCK_DATA = process.env.NODE_ENV === 'development' | ||
| 740 | -``` | ||
| 741 | - | ||
| 742 | -3. **在数据获取函数中使用**: | ||
| 743 | -```javascript | ||
| 744 | -const fetchWeekHot = async (page = 0) => { | ||
| 745 | - loading.value = true | ||
| 746 | - | ||
| 747 | - try { | ||
| 748 | - // 根据 USE_MOCK_DATA 开关决定使用 Mock 数据还是真实 API | ||
| 749 | - const res = USE_MOCK_DATA | ||
| 750 | - ? await mockWeekHotAPI({ page, limit: 20 }) | ||
| 751 | - : await fn(weekHotAPI({ page, limit: 20 })) | ||
| 752 | - | ||
| 753 | - if (res.code === 1) { | ||
| 754 | - weekHotList.value.push(...res.data.list) | ||
| 755 | - hasMore.value = res.data.list.length >= 20 | ||
| 756 | - } | ||
| 757 | - } catch (err) { | ||
| 758 | - console.error('获取周热门资料失败:', err) | ||
| 759 | - } finally { | ||
| 760 | - loading.value = false | ||
| 761 | - } | ||
| 762 | -} | ||
| 763 | -``` | ||
| 764 | - | ||
| 765 | -**Mock 数据特性**: | ||
| 766 | 213 | ||
| 767 | -- ✅ **模拟网络延迟**:100-300ms 随机延迟 | 214 | +## 📁 详细文档 |
| 768 | -- ✅ **支持分页加载**:可配置 `page` 和 `limit` 参数 | ||
| 769 | -- ✅ **随机数据生成**:文件大小、学习人数、收藏状态等 | ||
| 770 | -- ✅ **支持关键词搜索**:搜索 API 会根据关键词过滤数据 | ||
| 771 | -- ✅ **真实数据结构**:返回的数据结构与真实 API 一致 | ||
| 772 | -- ✅ **控制台日志**:每次请求都会打印 `[Mock]` 日志便于调试 | ||
| 773 | 215 | ||
| 774 | -**统一调用器**(可选): | 216 | +### 开发指南 |
| 217 | +- **[页面开发指南](docs/guides/page-development.md)** - 如何添加新页面 | ||
| 218 | +- **[API 集成指南](docs/guides/api-integration.md)** - API 调用详解 | ||
| 219 | +- **[导航系统指南](docs/guides/navigation.md)** - 导航和参数传递 | ||
| 775 | 220 | ||
| 776 | -也可以使用 `mockAPI()` 统一调用器: | 221 | +### 参考文档 |
| 777 | -```javascript | 222 | +- **[页面参考](docs/reference/pages.md)** - 所有页面详细说明 |
| 778 | -import { mockAPI } from '@/utils/mockData' | 223 | +- **[组件参考](docs/reference/components.md)** - 组件库文档 |
| 224 | +- **[Composables 参考](docs/reference/composables.md)** - 可复用逻辑 | ||
| 779 | 225 | ||
| 780 | -// 调用指定的 Mock API | 226 | +### 最佳实践 |
| 781 | -const res = await mockAPI('weekHotAPI', { page: 0, limit: 20 }) | 227 | +- **[最佳实践](docs/best-practices.md)** - 组件开发、API 集成、性能优化 |
| 782 | -``` | 228 | +- **[调试指南](docs/debugging.md)** - 调试技巧和工具 |
| 783 | 229 | ||
| 784 | -**注意事项**: | 230 | +--- |
| 785 | -- ⚠️ **生产环境关闭**:部署时确保 `USE_MOCK_DATA = false` | ||
| 786 | -- ⚠️ **数据结构对齐**:Mock 数据结构必须与真实 API 保持一致 | ||
| 787 | -- ⚠️ **分页逻辑**:Mock API 返回的 `list` 为空时表示没有更多数据 | ||
| 788 | 231 | ||
| 789 | -## 关键文件总结 | 232 | +## 🔧 关键文件 |
| 790 | 233 | ||
| 791 | ### 修改前必须理解 | 234 | ### 修改前必须理解 |
| 792 | 1. **`src/utils/openid.js`** - 微信授权和会话管理逻辑 | 235 | 1. **`src/utils/openid.js`** - 微信授权和会话管理逻辑 |
| ... | @@ -801,105 +244,53 @@ const res = await mockAPI('weekHotAPI', { page: 0, limit: 20 }) | ... | @@ -801,105 +244,53 @@ const res = await mockAPI('weekHotAPI', { page: 0, limit: 20 }) |
| 801 | 4. **`src/stores/user.js`** - 用户状态管理 | 244 | 4. **`src/stores/user.js`** - 用户状态管理 |
| 802 | 5. **`src/stores/router.js`** - 路由状态管理 | 245 | 5. **`src/stores/router.js`** - 路由状态管理 |
| 803 | 246 | ||
| 804 | -### 认证与会话 | ||
| 805 | -1. **`src/utils/openid.js`** - `miniProgramAuth()` 静默登录 | ||
| 806 | -2. **`src/utils/request.js`** - 401 拦截器和 sessionid 管理 | ||
| 807 | -3. **`src/pages/login/index.vue`** - 登录页 | ||
| 808 | - | ||
| 809 | ### 可复用组件 | 247 | ### 可复用组件 |
| 810 | 1. **`src/components/TabBar.vue`** - 底部导航栏 | 248 | 1. **`src/components/TabBar.vue`** - 底部导航栏 |
| 811 | 2. **`src/components/NavHeader.vue`** - 自定义导航头 | 249 | 2. **`src/components/NavHeader.vue`** - 自定义导航头 |
| 812 | -3. **`src/components/SectionCard.vue`** - 分组卡片 | 250 | +3. **`src/components/MaterialCard.vue`** - 资料卡片(可复用) |
| 813 | -4. **`src/components/DocumentPreview/`** - 文档预览 | 251 | +4. **`src/components/ProductCard.vue`** - 产品卡片(可复用) |
| 814 | -5. **`src/components/MaterialCard.vue`** - 资料卡片(可复用) | ||
| 815 | - - 自包含业务逻辑:查看、收藏 | ||
| 816 | - - 支持动态标签、文件大小格式化、学习人数显示 | ||
| 817 | - - 使用页面:首页、搜索页、周热门资料页 | ||
| 818 | -6. **`src/components/ProductCard.vue`** - 产品卡片(可复用) | ||
| 819 | - - 自定义样式:动态标签、封面图 | ||
| 820 | - - 支持产品详情查看和计划书功能 | ||
| 821 | - - 使用页面:首页、搜索页 | ||
| 822 | 252 | ||
| 823 | ### Composables | 253 | ### Composables |
| 824 | 1. **`src/composables/useSectionList.js`** - 分组列表管理 | 254 | 1. **`src/composables/useSectionList.js`** - 分组列表管理 |
| 825 | 2. **`src/composables/useFileOperation.js`** - 文件操作 | 255 | 2. **`src/composables/useFileOperation.js`** - 文件操作 |
| 826 | 3. **`src/composables/useListItemClick.js`** - 列表点击处理 | 256 | 3. **`src/composables/useListItemClick.js`** - 列表点击处理 |
| 827 | 257 | ||
| 828 | -### 工具函数 | 258 | +--- |
| 829 | -1. **`src/utils/mockData.js`** - Mock 数据生成工具(开发测试用) | 259 | + |
| 830 | - - 支持多个 API 的 Mock:周热门资料、资料列表、产品列表、搜索、消息列表、收藏列表、意见反馈 | 260 | +## ⚠️ 重要配置 |
| 831 | - - 统一调用器:`mockAPI(apiName, params)` | 261 | + |
| 832 | - - 支持分页加载、模拟网络延迟、随机数据生成 | 262 | +**环境配置**(`src/utils/config.js`): |
| 833 | -2. **`src/utils/documentIcons.js`** - 文档类型图标识别 | 263 | +- `BASE_URL` - 设置开发/生产域名 |
| 834 | -3. **`src/utils/tools.js`** - 通用工具函数集合 | 264 | +- `REQUEST_DEFAULT_PARAMS.f` - 设置业务模块标识符 |
| 835 | -4. **`src/utils/network.js`** - 网络状态工具 | 265 | +- `REQUEST_DEFAULT_PARAMS.client_name` - 设置应用名称 |
| 836 | -5. **`src/hooks/useGo.js`** - 增强导航 hook | 266 | + |
| 837 | - | 267 | +--- |
| 838 | -## 调试技巧 | 268 | + |
| 839 | - | 269 | +## 🚀 快速开始 |
| 840 | -调试问题时: | 270 | + |
| 841 | - | 271 | +### 1. 安装依赖 |
| 842 | -1. **检查环境配置**: | 272 | +```bash |
| 843 | - - 验证 `src/utils/config.js` 中的 `BASE_URL` | 273 | +pnpm install |
| 844 | - - 检查业务模块标识符 `f` 和 `client_name` | 274 | +``` |
| 845 | - | 275 | + |
| 846 | -2. **验证身份认证**: | 276 | +### 2. 配置环境 |
| 847 | - - 检查 localStorage 中的 `sessionid` | 277 | +编辑 `src/utils/config.js`,设置 `BASE_URL` 和业务参数。 |
| 848 | - - 在 `src/utils/request.js` 拦截器中启用详细日志 | 278 | + |
| 849 | - - 测试 401 刷新流程 | 279 | +### 3. 启动开发服务器 |
| 850 | - | 280 | +```bash |
| 851 | -3. **网络问题**: | 281 | +pnpm dev:weapp |
| 852 | - - 使用 Taro 内置的网络状态监控 | 282 | +``` |
| 853 | - - 检查弱网络降级场景 | 283 | + |
| 854 | - - 验证离线缓存交互 | 284 | +### 4. 打开微信开发者工具 |
| 855 | - | 285 | +导入项目目录:`dist/` |
| 856 | -4. **样式问题**: | 286 | + |
| 857 | - - 确认设计宽度(375px vs 750px) | 287 | +--- |
| 858 | - - 检查是 NutUI 组件还是自定义组件 | 288 | + |
| 859 | - - 验证 TailwindCSS 类是否已应用 | 289 | +## 📖 更多文档 |
| 860 | - | 290 | + |
| 861 | -5. **导航问题**: | 291 | +- **[页面参考](docs/reference/pages.md)** - 查看所有页面 |
| 862 | - - 检查路由是否在 `src/app.config.js` 中注册 | 292 | +- **[组件参考](docs/reference/components.md)** - 查看所有组件 |
| 863 | - - 验证页面目录结构与路由匹配 | 293 | +- **[Composables 参考](docs/reference/composables.md)** - 查看所有 Composables |
| 864 | - - 使用 `useGo` hook 进行一致的导航 | 294 | +- **[最佳实践](docs/best-practices.md)** - 代码质量和性能优化 |
| 865 | - | 295 | +- **[调试指南](docs/debugging.md)** - 调试技巧 |
| 866 | -## ✨ 最佳实践 | 296 | +- **[经验教训总结](docs/lessons-learned.md)** - 常见陷阱和解决方案 |
| 867 | - | ||
| 868 | -### 组件开发 | ||
| 869 | -- ✅ 使用 `<script setup>` 语法 | ||
| 870 | -- ✅ 使用 Composables 处理可复用逻辑 | ||
| 871 | -- ✅ Props 应该有类型定义 | ||
| 872 | -- ✅ 使用 `emit` 进行子到父通信 | ||
| 873 | -- ✅ 优先使用 TailwindCSS 进行样式设计 | ||
| 874 | - | ||
| 875 | -### API 集成 | ||
| 876 | -- ✅ 始终检查 `res.code === 1` 判断成功 | ||
| 877 | -- ✅ 使用 `try/catch` 进行错误处理 | ||
| 878 | -- ✅ 请求期间显示加载状态 | ||
| 879 | -- ✅ 优雅地处理网络错误 | ||
| 880 | - | ||
| 881 | -### 性能 | ||
| 882 | -- ✅ 使用页面懒加载(分包) | ||
| 883 | -- ✅ 使用 CDN 参数优化图片 | ||
| 884 | -- ✅ 避免无分页的大数据集 | ||
| 885 | -- ✅ 在 `onUnmounted` 中清理定时器和监听器 | ||
| 886 | -- ✅ 使用 `shallowRef` + `markRaw` 处理组件对象 | ||
| 887 | - | ||
| 888 | -### 代码质量 | ||
| 889 | -- ✅ 遵循 Vue 3 Composition API 模式 | ||
| 890 | -- ✅ 使用描述性变量名 | ||
| 891 | -- ✅ 保持函数聚焦且简短(< 50 行) | ||
| 892 | -- ✅ **所有函数必须有 JSDoc 注释** [详见代码注释规范](~/.claude/rules/code-commenting.md) | ||
| 893 | -- ✅ 提交前运行 `pnpm lint` | ||
| 894 | - | ||
| 895 | -### 代码复用 | ||
| 896 | -- ✅ 遵循"第 3 次出现原则" - 代码重复 3 次时必须抽取 | ||
| 897 | -- ✅ 优先使用 Composables 而非 Mixins | ||
| 898 | -- ✅ 组件职责单一,避免过度复杂 | ||
| 899 | -- ✅ **组件自包含业务逻辑** - 当UI结构重复出现时,抽取为可复用组件(如 MaterialCard、ProductCard) | ||
| 900 | - - 组件内部处理业务逻辑(查看、收藏等) | ||
| 901 | - - 通过事件与父组件通信 | ||
| 902 | - - 减少父组件的重复代码 | ||
| 903 | - - 示例:MaterialCard 组件在首页、搜索页、周热门资料页复用 | ||
| 904 | - | ||
| 905 | -[更多最佳实践详见经验教训总结](docs/lessons-learned.md) | ... | ... |
docs/best-practices.md
0 → 100644
| 1 | +# 最佳实践 | ||
| 2 | + | ||
| 3 | +本文档列出项目开发中的最佳实践。 | ||
| 4 | + | ||
| 5 | +## 组件开发 | ||
| 6 | + | ||
| 7 | +### ✅ 推荐做法 | ||
| 8 | + | ||
| 9 | +```vue | ||
| 10 | +<script setup> | ||
| 11 | +// 1. 使用 <script setup> 语法 | ||
| 12 | +import { ref, computed } from 'vue' | ||
| 13 | + | ||
| 14 | +// 2. Props 应该有类型定义 | ||
| 15 | +const props = defineProps({ | ||
| 16 | + title: { | ||
| 17 | + type: String, | ||
| 18 | + required: true | ||
| 19 | + }, | ||
| 20 | + count: { | ||
| 21 | + type: Number, | ||
| 22 | + default: 0 | ||
| 23 | + } | ||
| 24 | +}) | ||
| 25 | + | ||
| 26 | +// 3. 使用 emit 进行子到父通信 | ||
| 27 | +const emit = defineEmits(['update', 'delete']) | ||
| 28 | + | ||
| 29 | +// 4. 使用 Composables 处理可复用逻辑 | ||
| 30 | +const { data, loading } = use fetchData() | ||
| 31 | +</script> | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +### ❌ 避免做法 | ||
| 35 | + | ||
| 36 | +```vue | ||
| 37 | +<script> | ||
| 38 | +// ❌ 不要使用 Options API | ||
| 39 | +export default { | ||
| 40 | + data() { | ||
| 41 | + return { } | ||
| 42 | + } | ||
| 43 | +} | ||
| 44 | +</script> | ||
| 45 | + | ||
| 46 | +<script setup> | ||
| 47 | +// ❌ 不要省略 Props 类型 | ||
| 48 | +const props = defineProps(['title']) | ||
| 49 | + | ||
| 50 | +// ❌ 不要直接修改 props | ||
| 51 | +props.title = 'new title' | ||
| 52 | +</script> | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +--- | ||
| 56 | + | ||
| 57 | +## 样式开发 | ||
| 58 | + | ||
| 59 | +### TailwindCSS vs Less | ||
| 60 | + | ||
| 61 | +| 场景 | 使用 | 比例 | | ||
| 62 | +|------|------|------| | ||
| 63 | +| 布局(flex、grid、absolute) | TailwindCSS | 80% | | ||
| 64 | +| 间距(padding、margin、gap) | TailwindCSS | | | ||
| 65 | +| 排版(font-size、text-align) | TailwindCSS | | | ||
| 66 | +| 颜色(bg-*、text-*、border-*) | TailwindCSS | | | ||
| 67 | +| 响应式设计(sm:、md:、lg:) | TailwindCSS | | | ||
| 68 | +| 组件特定样式(需要 scoped) | Less | 20% | | ||
| 69 | +| 深度选择器(`:deep()`) | Less | | | ||
| 70 | +| 动画和过渡 | Less | | | ||
| 71 | +| 伪元素(`::before`、`::after`) | Less | | | ||
| 72 | + | ||
| 73 | +### ✅ 优先使用 TailwindCSS | ||
| 74 | + | ||
| 75 | +```vue | ||
| 76 | +<template> | ||
| 77 | + <!-- ✅ 使用 TailwindCSS --> | ||
| 78 | + <view class="flex items-center gap-4 p-4 bg-white rounded-lg"> | ||
| 79 | + <text class="text-lg font-semibold">标题</text> | ||
| 80 | + </view> | ||
| 81 | +</template> | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +### ❌ 避免过度使用 Less | ||
| 85 | + | ||
| 86 | +```vue | ||
| 87 | +<style lang="less" scoped> | ||
| 88 | +// ❌ 能用 TailwindCSS 的就不要用 Less | ||
| 89 | +.custom-container { | ||
| 90 | + display: flex; | ||
| 91 | + align-items: center; | ||
| 92 | + padding: 16px; | ||
| 93 | +} | ||
| 94 | +</style> | ||
| 95 | +``` | ||
| 96 | + | ||
| 97 | +--- | ||
| 98 | + | ||
| 99 | +## API 集成 | ||
| 100 | + | ||
| 101 | +### ✅ 推荐做法 | ||
| 102 | + | ||
| 103 | +```javascript | ||
| 104 | +// 1. 始终检查 res.code === 1 | ||
| 105 | +const res = await fn(yourAPI(params)) | ||
| 106 | +if (res.code === 1) { | ||
| 107 | + // 成功处理 | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +// 2. 使用 try/catch 进行错误处理 | ||
| 111 | +try { | ||
| 112 | + const res = await fn(yourAPI(params)) | ||
| 113 | +} catch (err) { | ||
| 114 | + console.error('API 错误:', err) | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +// 3. 请求期间显示加载状态 | ||
| 118 | +loading.value = true | ||
| 119 | +await fn(yourAPI(params)) | ||
| 120 | +loading.value = false | ||
| 121 | + | ||
| 122 | +// 4. 优雅地处理网络错误 | ||
| 123 | +try { | ||
| 124 | + await fn(yourAPI(params)) | ||
| 125 | +} catch (err) { | ||
| 126 | + if (is_network_error(err)) { | ||
| 127 | + Taro.showToast({ title: '网络错误,请重试', icon: 'none' }) | ||
| 128 | + } | ||
| 129 | +} | ||
| 130 | +``` | ||
| 131 | + | ||
| 132 | +### ❌ 避免做法 | ||
| 133 | + | ||
| 134 | +```javascript | ||
| 135 | +// ❌ 不要只检查 res.code | ||
| 136 | +if (res.code) { } | ||
| 137 | + | ||
| 138 | +// ❌ 不要忽略错误处理 | ||
| 139 | +await fn(yourAPI(params)) | ||
| 140 | + | ||
| 141 | +// ❌ 不要不显示加载状态 | ||
| 142 | +await fn(yourAPI(params)) // 用户不知道发生了什么 | ||
| 143 | +``` | ||
| 144 | + | ||
| 145 | +--- | ||
| 146 | + | ||
| 147 | +## 性能优化 | ||
| 148 | + | ||
| 149 | +### ✅ 推荐做法 | ||
| 150 | + | ||
| 151 | +```javascript | ||
| 152 | +// 1. 使用页面懒加载(分包) | ||
| 153 | +// 在 app.config.js 中配置分包 | ||
| 154 | +subPackages: [ | ||
| 155 | + { | ||
| 156 | + root: 'pages/business', | ||
| 157 | + pages: ['/*'] | ||
| 158 | + } | ||
| 159 | +] | ||
| 160 | + | ||
| 161 | +// 2. 使用 CDN 参数优化图片 | ||
| 162 | +const imageUrl = 'https://cdn.example.com/image.jpg?w=750&q=80' | ||
| 163 | + | ||
| 164 | +// 3. 避免无分页的大数据集 | ||
| 165 | +const res = await fn(getListAPI({ page: 1, limit: 20 })) | ||
| 166 | + | ||
| 167 | +// 4. 在 onUnmounted 中清理 | ||
| 168 | +onUnmounted(() => { | ||
| 169 | + timer && clearInterval(timer) | ||
| 170 | +}) | ||
| 171 | + | ||
| 172 | +// 5. 使用 shallowRef + markRaw 处理组件对象 | ||
| 173 | +import { shallowRef, markRaw } from 'vue' | ||
| 174 | + | ||
| 175 | +const menuItems = shallowRef([ | ||
| 176 | + { icon: markRaw(IconFont), name: 'heart' } | ||
| 177 | +]) | ||
| 178 | +``` | ||
| 179 | + | ||
| 180 | +### ❌ 避免做法 | ||
| 181 | + | ||
| 182 | +```javascript | ||
| 183 | +// ❌ 不要一次性加载所有数据 | ||
| 184 | +const allData = await fn(getAllDataAPI()) | ||
| 185 | + | ||
| 186 | +// ❌ 不要忘记清理定时器 | ||
| 187 | +const timer = setInterval(() => { }, 1000) | ||
| 188 | +// 没有清理 | ||
| 189 | + | ||
| 190 | +// ❌ 不要深度代理组件对象 | ||
| 191 | +const menuItems = ref([ | ||
| 192 | + { icon: IconFont, name: 'heart' } // Vue 会深度代理 | ||
| 193 | +]) | ||
| 194 | +``` | ||
| 195 | + | ||
| 196 | +--- | ||
| 197 | + | ||
| 198 | +## 代码质量 | ||
| 199 | + | ||
| 200 | +### ✅ 推荐做法 | ||
| 201 | + | ||
| 202 | +```javascript | ||
| 203 | +// 1. 遵循 Vue 3 Composition API 模式 | ||
| 204 | +<script setup> | ||
| 205 | +// ... | ||
| 206 | +</script> | ||
| 207 | + | ||
| 208 | +// 2. 使用描述性变量名 | ||
| 209 | +const isLoadingUserFavorites = ref(false) | ||
| 210 | + | ||
| 211 | +// 3. 保持函数聚焦且简短(< 50 行) | ||
| 212 | +const fetchUserData = async () => { | ||
| 213 | + // 单一职责 | ||
| 214 | +} | ||
| 215 | + | ||
| 216 | +// 4. 所有函数必须有 JSDoc 注释 | ||
| 217 | +/** | ||
| 218 | + * 获取用户数据 | ||
| 219 | + * @param {number} userId - 用户 ID | ||
| 220 | + * @returns {Promise<User>} 用户数据 | ||
| 221 | + */ | ||
| 222 | +async function getUserData(userId) { } | ||
| 223 | + | ||
| 224 | +// 5. 提交前运行 pnpm lint | ||
| 225 | +``` | ||
| 226 | + | ||
| 227 | +### ❌ 避免做法 | ||
| 228 | + | ||
| 229 | +```javascript | ||
| 230 | +// ❌ 不要使用无意义的变量名 | ||
| 231 | +const a = ref(false) | ||
| 232 | +const temp = ref(null) | ||
| 233 | + | ||
| 234 | +// ❌ 不要写超长函数(> 50 行) | ||
| 235 | +const doEverything = async () => { | ||
| 236 | + // 100+ 行代码 | ||
| 237 | +} | ||
| 238 | + | ||
| 239 | +// ❌ 不要省略函数注释 | ||
| 240 | +function process(data) { } | ||
| 241 | +``` | ||
| 242 | + | ||
| 243 | +--- | ||
| 244 | + | ||
| 245 | +## 代码复用 | ||
| 246 | + | ||
| 247 | +### 第 3 次出现原则 | ||
| 248 | + | ||
| 249 | +当相同代码模式出现 3 次时,**必须**抽取为 Composable 或组件。 | ||
| 250 | + | ||
| 251 | +### ✅ 推荐做法 | ||
| 252 | + | ||
| 253 | +```javascript | ||
| 254 | +// 抽取为 Composable | ||
| 255 | +// src/composables/useUserData.js | ||
| 256 | +export function useUserData() { | ||
| 257 | + const user = ref(null) | ||
| 258 | + const loading = ref(false) | ||
| 259 | + | ||
| 260 | + const fetchUser = async () => { | ||
| 261 | + loading.value = true | ||
| 262 | + // ... | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + return { user, loading, fetchUser } | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +// 在组件中使用 | ||
| 269 | +const { user, loading, fetchUser } = useUserData() | ||
| 270 | +``` | ||
| 271 | + | ||
| 272 | +### 组件自包含原则 | ||
| 273 | + | ||
| 274 | +对于重复的UI结构,抽取为可复用组件: | ||
| 275 | + | ||
| 276 | +```vue | ||
| 277 | +<!-- MaterialCard.vue - 自包含业务逻辑 --> | ||
| 278 | +<script setup> | ||
| 279 | +const props = defineProps(['item']) | ||
| 280 | +const emit = defineEmits(['view', 'collect']) | ||
| 281 | + | ||
| 282 | +// 组件内部处理业务逻辑 | ||
| 283 | +const handleView = () => { | ||
| 284 | + emit('view', props.item.id) | ||
| 285 | +} | ||
| 286 | +</script> | ||
| 287 | + | ||
| 288 | +<!-- 父组件只需要传递数据 --> | ||
| 289 | +<MaterialCard :item="material" @view="handleView" /> | ||
| 290 | +``` | ||
| 291 | + | ||
| 292 | +--- | ||
| 293 | + | ||
| 294 | +## 安全性 | ||
| 295 | + | ||
| 296 | +### ✅ 推荐做法 | ||
| 297 | + | ||
| 298 | +```javascript | ||
| 299 | +// 1. 用户输入验证 | ||
| 300 | +const validateInput = (input) => { | ||
| 301 | + if (!input || input.length > 100) { | ||
| 302 | + return false | ||
| 303 | + } | ||
| 304 | + return true | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +// 2. XSS 防护(使用 v-html 时净化) | ||
| 308 | +import DOMPurify from 'dompurify' | ||
| 309 | +const sanitizedHtml = DOMPurify.sanitize(rawHtml) | ||
| 310 | + | ||
| 311 | +// 3. 敏感数据不存储在 localStorage | ||
| 312 | +// ❌ 不要存储 | ||
| 313 | +localStorage.setItem('password', password) | ||
| 314 | + | ||
| 315 | +// ✅ 使用 Pinia(内存存储) | ||
| 316 | +const authStore = useAuthStore() | ||
| 317 | +authStore.setToken(token) | ||
| 318 | +``` | ||
| 319 | + | ||
| 320 | +--- | ||
| 321 | + | ||
| 322 | +## 相关文档 | ||
| 323 | + | ||
| 324 | +- **[经验教训总结](lessons-learned.md)** - 常见陷阱和解决方案 | ||
| 325 | +- **[调试指南](debugging.md)** - 调试技巧 |
docs/debugging.md
0 → 100644
| 1 | +# 调试指南 | ||
| 2 | + | ||
| 3 | +本文档介绍项目调试的常用技巧和工具。 | ||
| 4 | + | ||
| 5 | +## 检查环境配置 | ||
| 6 | + | ||
| 7 | +### 验证 BASE_URL | ||
| 8 | + | ||
| 9 | +```bash | ||
| 10 | +# 查看 src/utils/config.js 中的 BASE_URL 配置 | ||
| 11 | +cat src/utils/config.js | grep BASE_URL | ||
| 12 | +``` | ||
| 13 | + | ||
| 14 | +确保: | ||
| 15 | +- 开发环境指向测试服务器 | ||
| 16 | +- 生产环境指向正式服务器 | ||
| 17 | + | ||
| 18 | +### 检查业务参数 | ||
| 19 | + | ||
| 20 | +```javascript | ||
| 21 | +// src/utils/config.js | ||
| 22 | +REQUEST_DEFAULT_PARAMS: { | ||
| 23 | + f: 'your_module', // 业务模块标识符 | ||
| 24 | + client_name: 'your_app' // 应用名称 | ||
| 25 | +} | ||
| 26 | +``` | ||
| 27 | + | ||
| 28 | +--- | ||
| 29 | + | ||
| 30 | +## 验证身份认证 | ||
| 31 | + | ||
| 32 | +### 检查 sessionid | ||
| 33 | + | ||
| 34 | +```javascript | ||
| 35 | +// 在浏览器控制台或微信开发者工具中执行 | ||
| 36 | +console.log(localStorage.sessionid) | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +### 启用详细日志 | ||
| 40 | + | ||
| 41 | +在 `src/utils/request.js` 拦截器中启用详细日志: | ||
| 42 | + | ||
| 43 | +```javascript | ||
| 44 | +// 请求拦截器 | ||
| 45 | +config.headers = { | ||
| 46 | + 'cookie': `sessionid=${localStorage.sessionid}`, | ||
| 47 | + // 添加调试日志 | ||
| 48 | + 'X-Debug': 'true' | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +console.log('[Request]', config.url, config.data) | ||
| 52 | +``` | ||
| 53 | + | ||
| 54 | +### 测试 401 刷新流程 | ||
| 55 | + | ||
| 56 | +```javascript | ||
| 57 | +// 1. 清除 sessionid 模拟过期 | ||
| 58 | +localStorage.removeItem('sessionid') | ||
| 59 | + | ||
| 60 | +// 2. 发送请求触发 401 | ||
| 61 | +await fn(someAPI()) | ||
| 62 | + | ||
| 63 | +// 3. 观察是否自动刷新并重试 | ||
| 64 | +``` | ||
| 65 | + | ||
| 66 | +--- | ||
| 67 | + | ||
| 68 | +## 网络问题调试 | ||
| 69 | + | ||
| 70 | +### 检查网络状态 | ||
| 71 | + | ||
| 72 | +```javascript | ||
| 73 | +import Taro from '@tarojs/taro' | ||
| 74 | + | ||
| 75 | +// 获取网络类型 | ||
| 76 | +Taro.getNetworkType({ | ||
| 77 | + success: (res) => { | ||
| 78 | + console.log('网络类型:', res.networkType) | ||
| 79 | + // wifi, 4g, 5g, none | ||
| 80 | + } | ||
| 81 | +}) | ||
| 82 | + | ||
| 83 | +// 监听网络变化 | ||
| 84 | +Taro.onNetworkStatusChange((result) => { | ||
| 85 | + console.log('网络状态变化:', result) | ||
| 86 | +}) | ||
| 87 | +``` | ||
| 88 | + | ||
| 89 | +### 检查弱网络场景 | ||
| 90 | + | ||
| 91 | +项目支持弱网络降级,通过以下方式检测: | ||
| 92 | + | ||
| 93 | +```javascript | ||
| 94 | +// src/utils/request.js | ||
| 95 | +import { is_timeout_error, is_network_error } from '@/utils/network' | ||
| 96 | + | ||
| 97 | +try { | ||
| 98 | + const res = await fn(yourAPI()) | ||
| 99 | +} catch (err) { | ||
| 100 | + if (is_timeout_error(err)) { | ||
| 101 | + console.log('请求超时') | ||
| 102 | + } else if (is_network_error(err)) { | ||
| 103 | + console.log('网络错误') | ||
| 104 | + } | ||
| 105 | +} | ||
| 106 | +``` | ||
| 107 | + | ||
| 108 | +--- | ||
| 109 | + | ||
| 110 | +## 样式问题调试 | ||
| 111 | + | ||
| 112 | +### 确认设计宽度 | ||
| 113 | + | ||
| 114 | +项目中使用双设计宽度系统: | ||
| 115 | +- **NutUI 组件**:375px | ||
| 116 | +- **其他组件**:750px | ||
| 117 | + | ||
| 118 | +```javascript | ||
| 119 | +// config/index.js | ||
| 120 | +designWidth: 750, // 默认 | ||
| 121 | + | ||
| 122 | +// NutUI 特殊配置 | ||
| 123 | +nutui: { | ||
| 124 | + designWidth: 375 | ||
| 125 | +} | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +### 检查样式应用 | ||
| 129 | + | ||
| 130 | +```vue | ||
| 131 | +<template> | ||
| 132 | + <!-- 添加调试类名 --> | ||
| 133 | + <view class="debug-component"> | ||
| 134 | + 内容 | ||
| 135 | + </view> | ||
| 136 | +</template> | ||
| 137 | + | ||
| 138 | +<style lang="less" scoped> | ||
| 139 | +.debug-component { | ||
| 140 | + /* 调试边框 */ | ||
| 141 | + border: 1px solid red; | ||
| 142 | +} | ||
| 143 | +</style> | ||
| 144 | +``` | ||
| 145 | + | ||
| 146 | +### 验证 TailwindCSS 类 | ||
| 147 | + | ||
| 148 | +```bash | ||
| 149 | +# 检查 TailwindCSS 配置 | ||
| 150 | +cat tailwind.config.js | ||
| 151 | + | ||
| 152 | +# 检查类是否生效 | ||
| 153 | +# 在微信开发者工具中查看元素的 computed styles | ||
| 154 | +``` | ||
| 155 | + | ||
| 156 | +--- | ||
| 157 | + | ||
| 158 | +## 导航问题调试 | ||
| 159 | + | ||
| 160 | +### 检查路由注册 | ||
| 161 | + | ||
| 162 | +```javascript | ||
| 163 | +// src/app.config.js | ||
| 164 | +export default { | ||
| 165 | + pages: [ | ||
| 166 | + 'pages/your-page/index', // 确认已注册 | ||
| 167 | + ] | ||
| 168 | +} | ||
| 169 | +``` | ||
| 170 | + | ||
| 171 | +### 验证页面目录结构 | ||
| 172 | + | ||
| 173 | +```bash | ||
| 174 | +# 确认页面文件存在 | ||
| 175 | +ls -la src/pages/your-page/ | ||
| 176 | + | ||
| 177 | +# 应该包含: | ||
| 178 | +# - index.vue | ||
| 179 | +# - index.config.js | ||
| 180 | +``` | ||
| 181 | + | ||
| 182 | +### 调试导航参数 | ||
| 183 | + | ||
| 184 | +```javascript | ||
| 185 | +// 发送页面 | ||
| 186 | +go('/pages/detail/index', { id: 123 }) | ||
| 187 | + | ||
| 188 | +// 接收页面 | ||
| 189 | +useLoad((options) => { | ||
| 190 | + console.log('接收到的参数:', options) | ||
| 191 | + console.log('ID:', options.id) // 应该输出 123 | ||
| 192 | +}) | ||
| 193 | +``` | ||
| 194 | + | ||
| 195 | +--- | ||
| 196 | + | ||
| 197 | +## API 问题调试 | ||
| 198 | + | ||
| 199 | +### 查看 API 请求 | ||
| 200 | + | ||
| 201 | +```javascript | ||
| 202 | +// src/utils/request.js | ||
| 203 | +// 在拦截器中添加日志 | ||
| 204 | +interceptors.request.use((config) => { | ||
| 205 | + console.log('[API Request]', { | ||
| 206 | + url: config.url, | ||
| 207 | + method: config.method, | ||
| 208 | + data: config.data, | ||
| 209 | + headers: config.headers | ||
| 210 | + }) | ||
| 211 | + return config | ||
| 212 | +}) | ||
| 213 | + | ||
| 214 | +interceptors.response.use((response) => { | ||
| 215 | + console.log('[API Response]', { | ||
| 216 | + status: response.status, | ||
| 217 | + data: response.data | ||
| 218 | + }) | ||
| 219 | + return response | ||
| 220 | +}) | ||
| 221 | +``` | ||
| 222 | + | ||
| 223 | +### 测试 API 调用 | ||
| 224 | + | ||
| 225 | +```javascript | ||
| 226 | +// 独立测试 API | ||
| 227 | +import { yourAPI } from '@/api' | ||
| 228 | +import { fn } from '@/api/fn' | ||
| 229 | + | ||
| 230 | +async function testAPI() { | ||
| 231 | + try { | ||
| 232 | + const url = yourAPI({ id: 123 }) | ||
| 233 | + console.log('API URL:', url) | ||
| 234 | + | ||
| 235 | + const res = await fn(url) | ||
| 236 | + console.log('API Response:', res) | ||
| 237 | + | ||
| 238 | + if (res.code === 1) { | ||
| 239 | + console.log('API 调用成功') | ||
| 240 | + } else { | ||
| 241 | + console.log('API 业务错误:', res.msg) | ||
| 242 | + } | ||
| 243 | + } catch (err) { | ||
| 244 | + console.error('API 网络错误:', err) | ||
| 245 | + } | ||
| 246 | +} | ||
| 247 | + | ||
| 248 | +testAPI() | ||
| 249 | +``` | ||
| 250 | + | ||
| 251 | +--- | ||
| 252 | + | ||
| 253 | +## 常见问题 | ||
| 254 | + | ||
| 255 | +### Q: NutUI 组件样式不生效 | ||
| 256 | + | ||
| 257 | +A: 检查设计宽度配置,NutUI 使用 375px 设计宽度: | ||
| 258 | + | ||
| 259 | +```javascript | ||
| 260 | +// config/index.js | ||
| 261 | +nutui: { | ||
| 262 | + designWidth: 375 // 确认配置正确 | ||
| 263 | +} | ||
| 264 | +``` | ||
| 265 | + | ||
| 266 | +### Q: 页面参数接收不到 | ||
| 267 | + | ||
| 268 | +A: 检查以下几点: | ||
| 269 | + | ||
| 270 | +1. 确认使用 `useLoad` 接收参数 | ||
| 271 | +2. 确认参数名称正确 | ||
| 272 | +3. 确认参数类型(数字 vs 字符串) | ||
| 273 | + | ||
| 274 | +```javascript | ||
| 275 | +useLoad((options) => { | ||
| 276 | + console.log(options) // 先打印看看有什么 | ||
| 277 | +}) | ||
| 278 | +``` | ||
| 279 | + | ||
| 280 | +### Q: API 请求不发送 | ||
| 281 | + | ||
| 282 | +A: 检查以下几点: | ||
| 283 | + | ||
| 284 | +1. 确认 BASE_URL 配置正确 | ||
| 285 | +2. 确认网络权限 | ||
| 286 | +3. 查看控制台是否有错误 | ||
| 287 | +4. 检查 request.js 拦截器 | ||
| 288 | + | ||
| 289 | +### Q: 组件不更新 | ||
| 290 | + | ||
| 291 | +A: 可能的原因: | ||
| 292 | + | ||
| 293 | +1. 响应式数据未正确声明 | ||
| 294 | +2. 使用了深度嵌套的对象 | ||
| 295 | +3. Key 值未正确设置 | ||
| 296 | + | ||
| 297 | +```javascript | ||
| 298 | +// 确保使用 ref 或 reactive | ||
| 299 | +const data = ref(null) // ✅ | ||
| 300 | +const data = null // ❌ | ||
| 301 | + | ||
| 302 | +// 列表使用 key | ||
| 303 | +<view v-for="item in list" :key="item.id"> // ✅ | ||
| 304 | +<view v-for="item in list"> // ❌ | ||
| 305 | +``` | ||
| 306 | + | ||
| 307 | +--- | ||
| 308 | + | ||
| 309 | +## 调试工具 | ||
| 310 | + | ||
| 311 | +### 微信开发者工具 | ||
| 312 | + | ||
| 313 | +- **Console** - 查看日志输出 | ||
| 314 | +- **Network** - 查看网络请求 | ||
| 315 | +- **AppData** - 查看 AppData 数据 | ||
| 316 | +- **Storage** - 查看 localStorage | ||
| 317 | +- **Wxml** - 查看 DOM 结构 | ||
| 318 | + | ||
| 319 | +### Vue DevTools | ||
| 320 | + | ||
| 321 | +Taro 支持 Vue DevTools,可以: | ||
| 322 | +- 查看组件树 | ||
| 323 | +- 查看 Vuex/Pinia 状态 | ||
| 324 | +- 查看事件监听器 | ||
| 325 | + | ||
| 326 | +--- | ||
| 327 | + | ||
| 328 | +## 相关文档 | ||
| 329 | + | ||
| 330 | +- **[API 集成指南](guides/api-integration.md)** - API 调用详解 | ||
| 331 | +- **[最佳实践](best-practices.md)** - 代码质量建议 |
docs/guides/api-integration.md
0 → 100644
| 1 | +# API 集成指南 | ||
| 2 | + | ||
| 3 | +本文档介绍如何在项目中添加和调用 API。 | ||
| 4 | + | ||
| 5 | +## API 定义模式 | ||
| 6 | + | ||
| 7 | +### 步骤 1:在 src/api/index.js 中定义 API | ||
| 8 | + | ||
| 9 | +```javascript | ||
| 10 | +export const getProductListAPI = (params) => { | ||
| 11 | + return buildApiUrl('product_list', params) | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +export const getProductDetailAPI = (params) => { | ||
| 15 | + return buildApiUrl('product_detail', params) | ||
| 16 | +} | ||
| 17 | +``` | ||
| 18 | + | ||
| 19 | +### 步骤 2:在页面中使用 | ||
| 20 | + | ||
| 21 | +```javascript | ||
| 22 | +import { getProductListAPI } from '@/api' | ||
| 23 | +import { fn } from '@/api/fn' | ||
| 24 | + | ||
| 25 | +const fetchProducts = async () => { | ||
| 26 | + try { | ||
| 27 | + const res = await fn(getProductListAPI({ page: 1 })) | ||
| 28 | + if (res.code === 1) { | ||
| 29 | + products.value = res.data | ||
| 30 | + } | ||
| 31 | + } catch (err) { | ||
| 32 | + console.error('获取产品失败:', err) | ||
| 33 | + } | ||
| 34 | +} | ||
| 35 | +``` | ||
| 36 | + | ||
| 37 | +## 请求包装器(fn.js) | ||
| 38 | + | ||
| 39 | +所有 API 调用都应通过 `src/api/fn.js` 的包装器: | ||
| 40 | + | ||
| 41 | +- ✅ 处理常见错误场景 | ||
| 42 | +- ✅ 统一错误提示 | ||
| 43 | +- ✅ **始终检查 `res.code === 1` 判断成功** | ||
| 44 | + | ||
| 45 | +## 完整示例 | ||
| 46 | + | ||
| 47 | +### 带加载状态的 API 调用 | ||
| 48 | + | ||
| 49 | +```javascript | ||
| 50 | +import { ref } from 'vue' | ||
| 51 | +import { getProductListAPI } from '@/api' | ||
| 52 | +import { fn } from '@/api/fn' | ||
| 53 | + | ||
| 54 | +const products = ref([]) | ||
| 55 | +const loading = ref(false) | ||
| 56 | +const error = ref(null) | ||
| 57 | + | ||
| 58 | +const fetchProducts = async (page = 1) => { | ||
| 59 | + loading.value = true | ||
| 60 | + error.value = null | ||
| 61 | + | ||
| 62 | + try { | ||
| 63 | + const res = await fn(getProductListAPI({ page, limit: 20 })) | ||
| 64 | + | ||
| 65 | + if (res.code === 1) { | ||
| 66 | + products.value = res.data.list | ||
| 67 | + } else { | ||
| 68 | + error.value = res.msg || '获取失败' | ||
| 69 | + } | ||
| 70 | + } catch (err) { | ||
| 71 | + console.error('API 错误:', err) | ||
| 72 | + error.value = '网络错误,请重试' | ||
| 73 | + } finally { | ||
| 74 | + loading.value = false | ||
| 75 | + } | ||
| 76 | +} | ||
| 77 | +``` | ||
| 78 | + | ||
| 79 | +### 带分页的 API 调用 | ||
| 80 | + | ||
| 81 | +```javascript | ||
| 82 | +const page = ref(0) | ||
| 83 | +const hasMore = ref(true) | ||
| 84 | +const loading = ref(false) | ||
| 85 | + | ||
| 86 | +const loadMore = async () => { | ||
| 87 | + if (loading.value || !hasMore.value) return | ||
| 88 | + | ||
| 89 | + loading.value = true | ||
| 90 | + try { | ||
| 91 | + const res = await fn(getProductListAPI({ page: page.value, limit: 20 })) | ||
| 92 | + | ||
| 93 | + if (res.code === 1) { | ||
| 94 | + products.value.push(...res.data.list) | ||
| 95 | + page.value++ | ||
| 96 | + hasMore.value = res.data.list.length >= 20 | ||
| 97 | + } | ||
| 98 | + } catch (err) { | ||
| 99 | + console.error('加载更多失败:', err) | ||
| 100 | + } finally { | ||
| 101 | + loading.value = false | ||
| 102 | + } | ||
| 103 | +} | ||
| 104 | +``` | ||
| 105 | + | ||
| 106 | +## 错误处理 | ||
| 107 | + | ||
| 108 | +### 网络错误 | ||
| 109 | + | ||
| 110 | +```javascript | ||
| 111 | +try { | ||
| 112 | + const res = await fn(yourAPI(params)) | ||
| 113 | + // ... | ||
| 114 | +} catch (err) { | ||
| 115 | + if (is_network_error(err)) { | ||
| 116 | + // 网络错误 | ||
| 117 | + Taro.showToast({ title: '网络错误', icon: 'none' }) | ||
| 118 | + } else if (is_timeout_error(err)) { | ||
| 119 | + // 超时 | ||
| 120 | + Taro.showToast({ title: '请求超时', icon: 'none' }) | ||
| 121 | + } else { | ||
| 122 | + // 其他错误 | ||
| 123 | + Taro.showToast({ title: '请求失败', icon: 'none' }) | ||
| 124 | + } | ||
| 125 | +} | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +### 业务错误 | ||
| 129 | + | ||
| 130 | +```javascript | ||
| 131 | +const res = await fn(yourAPI(params)) | ||
| 132 | + | ||
| 133 | +if (res.code === 1) { | ||
| 134 | + // 成功 | ||
| 135 | +} else if (res.code === 401) { | ||
| 136 | + // 未登录(通常会被拦截器自动处理) | ||
| 137 | +} else { | ||
| 138 | + // 业务错误 | ||
| 139 | + Taro.showToast({ title: res.msg || '操作失败', icon: 'none' }) | ||
| 140 | +} | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +## API 规范 | ||
| 144 | + | ||
| 145 | +### 请求格式 | ||
| 146 | + | ||
| 147 | +```javascript | ||
| 148 | +// 查询列表 | ||
| 149 | +yourAPI({ page: 1, limit: 20, keyword: '搜索词' }) | ||
| 150 | + | ||
| 151 | +// 获取详情 | ||
| 152 | +detailAPI({ id: 123 }) | ||
| 153 | + | ||
| 154 | +// 提交表单 | ||
| 155 | +submitAPI({ field1: 'value1', field2: 'value2' }) | ||
| 156 | +``` | ||
| 157 | + | ||
| 158 | +### 响应格式 | ||
| 159 | + | ||
| 160 | +```javascript | ||
| 161 | +// 成功 | ||
| 162 | +{ | ||
| 163 | + code: 1, | ||
| 164 | + msg: 'success', | ||
| 165 | + data: { /* 业务数据 */ } | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +// 失败 | ||
| 169 | +{ | ||
| 170 | + code: 0, // 或其他错误码 | ||
| 171 | + msg: '错误信息', | ||
| 172 | + data: null | ||
| 173 | +} | ||
| 174 | +``` | ||
| 175 | + | ||
| 176 | +## 最佳实践 | ||
| 177 | + | ||
| 178 | +### ✅ 推荐做法 | ||
| 179 | + | ||
| 180 | +```javascript | ||
| 181 | +// 1. 使用 async/await | ||
| 182 | +const res = await fn(yourAPI(params)) | ||
| 183 | + | ||
| 184 | +// 2. 检查 res.code === 1 | ||
| 185 | +if (res.code === 1) { | ||
| 186 | + // 成功处理 | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +// 3. 使用 try/catch | ||
| 190 | +try { | ||
| 191 | + const res = await fn(yourAPI(params)) | ||
| 192 | +} catch (err) { | ||
| 193 | + // 错误处理 | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +// 4. 显示加载状态 | ||
| 197 | +loading.value = true | ||
| 198 | +// ... API 调用 | ||
| 199 | +loading.value = false | ||
| 200 | +``` | ||
| 201 | + | ||
| 202 | +### ❌ 避免做法 | ||
| 203 | + | ||
| 204 | +```javascript | ||
| 205 | +// 1. 不要只检查 res.code | ||
| 206 | +if (res.code) { } // ❌ | ||
| 207 | + | ||
| 208 | +// 2. 不要忽略错误 | ||
| 209 | +const res = await fn(yourAPI(params)) // ❌ 无 try/catch | ||
| 210 | + | ||
| 211 | +// 3. 不要硬编码 API URL | ||
| 212 | +fetch('/srv/?a=your_action') // ❌ | ||
| 213 | +``` | ||
| 214 | + | ||
| 215 | +## 参考文档 | ||
| 216 | + | ||
| 217 | +- **[API 接口文档](docs/api-specs/)** - 完整的 API 接口规范 | ||
| 218 | +- **[接口联调日志](docs/api-integration-log.md)** - API 集成状态 |
docs/guides/navigation.md
0 → 100644
| 1 | +# 导航系统指南 | ||
| 2 | + | ||
| 3 | +本文档介绍项目中的导航系统使用方法。 | ||
| 4 | + | ||
| 5 | +## useGo Hook(推荐) | ||
| 6 | + | ||
| 7 | +`useGo` 是增强的导航 Hook,提供自动路径补全和便捷方法。 | ||
| 8 | + | ||
| 9 | +### 基础用法 | ||
| 10 | + | ||
| 11 | +```javascript | ||
| 12 | +import { useGo } from '@/hooks/useGo' | ||
| 13 | + | ||
| 14 | +const go = useGo() | ||
| 15 | + | ||
| 16 | +// 导航到页面 | ||
| 17 | +go('/pages/detail/index') | ||
| 18 | + | ||
| 19 | +// 带参数导航(例如产品 ID) | ||
| 20 | +go('/pages/product-detail/index', { id: 123 }) | ||
| 21 | + | ||
| 22 | +// 带多个参数导航 | ||
| 23 | +go('/pages/material-list/index', { category: 'insurance', page: 1 }) | ||
| 24 | + | ||
| 25 | +// 返回上一页 | ||
| 26 | +go.back() | ||
| 27 | +``` | ||
| 28 | + | ||
| 29 | +### 路径自动补全 | ||
| 30 | + | ||
| 31 | +`useGo` 会自动补全相对路径为绝对路径: | ||
| 32 | + | ||
| 33 | +```javascript | ||
| 34 | +go('detail') // → /pages/detail/index | ||
| 35 | +go('product-detail') // → /pages/product-detail/index | ||
| 36 | +``` | ||
| 37 | + | ||
| 38 | +### 返回导航 | ||
| 39 | + | ||
| 40 | +```javascript | ||
| 41 | +// 返回上一页 | ||
| 42 | +go.back() | ||
| 43 | + | ||
| 44 | +// 返回多页 | ||
| 45 | +go.back(2) | ||
| 46 | + | ||
| 47 | +// 返回首页 | ||
| 48 | +go('/pages/index/index') | ||
| 49 | +``` | ||
| 50 | + | ||
| 51 | +## 在目标页面接收参数 | ||
| 52 | + | ||
| 53 | +```javascript | ||
| 54 | +import { useLoad } from '@tarojs/taro' | ||
| 55 | +import { ref } from 'vue' | ||
| 56 | + | ||
| 57 | +const productId = ref(null) | ||
| 58 | + | ||
| 59 | +useLoad((options) => { | ||
| 60 | + // 访问导航参数 | ||
| 61 | + console.log('接收到的参数:', options) | ||
| 62 | + productId.value = options.id | ||
| 63 | + | ||
| 64 | + // 根据参数获取数据 | ||
| 65 | + fetchProductDetail(options.id) | ||
| 66 | +}) | ||
| 67 | +``` | ||
| 68 | + | ||
| 69 | +## Taro 内置导航(备选方案) | ||
| 70 | + | ||
| 71 | +如果需要更底层的控制,可以直接使用 Taro 导航 API: | ||
| 72 | + | ||
| 73 | +### navigateTo - 保留当前页 | ||
| 74 | + | ||
| 75 | +```javascript | ||
| 76 | +import Taro from '@tarojs/taro' | ||
| 77 | + | ||
| 78 | +Taro.navigateTo({ | ||
| 79 | + url: '/pages/detail/index?id=123' | ||
| 80 | +}) | ||
| 81 | +``` | ||
| 82 | + | ||
| 83 | +### redirectTo - 不保留当前页 | ||
| 84 | + | ||
| 85 | +```javascript | ||
| 86 | +Taro.redirectTo({ | ||
| 87 | + url: '/pages/login/index' | ||
| 88 | +}) | ||
| 89 | +``` | ||
| 90 | + | ||
| 91 | +### switchTab - 切换 Tab | ||
| 92 | + | ||
| 93 | +```javascript | ||
| 94 | +Taro.switchTab({ | ||
| 95 | + url: '/pages/index/index' | ||
| 96 | +}) | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +### reLaunch - 重启应用 | ||
| 100 | + | ||
| 101 | +```javascript | ||
| 102 | +Taro.reLaunch({ | ||
| 103 | + url: '/pages/index/index' | ||
| 104 | +}) | ||
| 105 | +``` | ||
| 106 | + | ||
| 107 | +### navigateBack - 返回 | ||
| 108 | + | ||
| 109 | +```javascript | ||
| 110 | +// 返回上一页 | ||
| 111 | +Taro.navigateBack() | ||
| 112 | + | ||
| 113 | +// 返回多页 | ||
| 114 | +Taro.navigateBack({ delta: 2 }) | ||
| 115 | +``` | ||
| 116 | + | ||
| 117 | +## 路由状态管理 | ||
| 118 | + | ||
| 119 | +### router Store | ||
| 120 | + | ||
| 121 | +项目使用 `src/stores/router.js` 维护已访问路由的栈,主要用于认证回调导航。 | ||
| 122 | + | ||
| 123 | +```javascript | ||
| 124 | +import { useRouterStore } from '@/stores/router' | ||
| 125 | + | ||
| 126 | +const routerStore = useRouterStore() | ||
| 127 | + | ||
| 128 | +// 获取上一页路径 | ||
| 129 | +const previousPage = routerStore.previousRoute | ||
| 130 | +``` | ||
| 131 | + | ||
| 132 | +### 导航钩子 | ||
| 133 | + | ||
| 134 | +```javascript | ||
| 135 | +import { useLoad, useShow, useHide, useUnload } from '@tarojs/taro' | ||
| 136 | + | ||
| 137 | +// 页面加载时(只执行一次) | ||
| 138 | +useLoad((options) => { | ||
| 139 | + console.log('页面加载', options) | ||
| 140 | +}) | ||
| 141 | + | ||
| 142 | +// 页面显示时(每次返回都会执行) | ||
| 143 | +useShow(() => { | ||
| 144 | + console.log('页面显示') | ||
| 145 | +}) | ||
| 146 | + | ||
| 147 | +// 页面隐藏时 | ||
| 148 | +useHide(() => { | ||
| 149 | + console.log('页面隐藏') | ||
| 150 | +}) | ||
| 151 | + | ||
| 152 | +// 页面卸载时 | ||
| 153 | +useUnload(() => { | ||
| 154 | + console.log('页面卸载') | ||
| 155 | +}) | ||
| 156 | +``` | ||
| 157 | + | ||
| 158 | +## 认证场景导航 | ||
| 159 | + | ||
| 160 | +### 登录后返回原页面 | ||
| 161 | + | ||
| 162 | +```javascript | ||
| 163 | +import { useGo } from '@/hooks/useGo' | ||
| 164 | +import { useRouterStore } from '@/stores/router' | ||
| 165 | + | ||
| 166 | +const go = useGo() | ||
| 167 | +const routerStore = useRouterStore() | ||
| 168 | + | ||
| 169 | +// 1. 保存当前路径 | ||
| 170 | +routerStore.setRedirect('/pages/some-page/index') | ||
| 171 | + | ||
| 172 | +// 2. 跳转登录页 | ||
| 173 | +go('/pages/login/index') | ||
| 174 | + | ||
| 175 | +// 3. 登录成功后返回 | ||
| 176 | +const redirectUrl = routerStore.redirect | ||
| 177 | +if (redirectUrl) { | ||
| 178 | + go(redirectUrl) | ||
| 179 | + routerStore.setRedirect(null) | ||
| 180 | +} | ||
| 181 | +``` | ||
| 182 | + | ||
| 183 | +## 常见问题 | ||
| 184 | + | ||
| 185 | +### Q: 导航后页面不刷新? | ||
| 186 | + | ||
| 187 | +A: 使用 `useShow` 钩子,每次页面显示时都会执行: | ||
| 188 | + | ||
| 189 | +```javascript | ||
| 190 | +useShow(() => { | ||
| 191 | + // 重新加载数据 | ||
| 192 | + fetchData() | ||
| 193 | +}) | ||
| 194 | +``` | ||
| 195 | + | ||
| 196 | +### Q: 参数传递丢失? | ||
| 197 | + | ||
| 198 | +A: 确保参数类型正确: | ||
| 199 | + | ||
| 200 | +```javascript | ||
| 201 | +// ❌ 错误:对象作为参数 | ||
| 202 | +go('/pages/detail/index', { data: { id: 1 } }) | ||
| 203 | + | ||
| 204 | +// ✅ 正确:扁平参数 | ||
| 205 | +go('/pages/detail/index', { id: 1, type: 'insurance' }) | ||
| 206 | +``` | ||
| 207 | + | ||
| 208 | +### Q: 如何清除页面栈? | ||
| 209 | + | ||
| 210 | +A: 使用 `redirectTo` 或 `reLaunch`: | ||
| 211 | + | ||
| 212 | +```javascript | ||
| 213 | +// 不保留当前页 | ||
| 214 | +Taro.redirectTo({ url: '/pages/login/index' }) | ||
| 215 | + | ||
| 216 | +// 清空所有页面栈 | ||
| 217 | +Taro.reLaunch({ url: '/pages/index/index' }) | ||
| 218 | +``` | ||
| 219 | + | ||
| 220 | +## 参考文档 | ||
| 221 | + | ||
| 222 | +- **[Taro 导航文档](https://docs.taro.zone/docs/vue-navigation)** - 官方文档 | ||
| 223 | +- **[页面开发指南](guides/page-development.md)** - 页面创建和路由注册 |
docs/guides/page-development.md
0 → 100644
| 1 | +# 页面开发指南 | ||
| 2 | + | ||
| 3 | +本文档介绍如何在项目中添加新页面。 | ||
| 4 | + | ||
| 5 | +## 目录结构 | ||
| 6 | + | ||
| 7 | +所有页面遵循以下目录结构: | ||
| 8 | +``` | ||
| 9 | +src/pages/your-page/ | ||
| 10 | +├── index.vue # 页面组件(必须使用 <script setup>) | ||
| 11 | +├── index.config.js # 页面配置(navigationBarTitleText 等) | ||
| 12 | +└── assets/ # 页面特定资源(可选) | ||
| 13 | +``` | ||
| 14 | + | ||
| 15 | +## 步骤 1:创建页面目录和文件 | ||
| 16 | + | ||
| 17 | +```bash | ||
| 18 | +mkdir -p src/pages/your-page | ||
| 19 | +touch src/pages/your-page/index.vue | ||
| 20 | +touch src/pages/your-page/index.config.js | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +## 步骤 2:配置页面 | ||
| 24 | + | ||
| 25 | +**`index.config.js`**: | ||
| 26 | +```javascript | ||
| 27 | +export default { | ||
| 28 | + navigationBarTitleText: '您的页面标题', | ||
| 29 | + enablePullDownRefresh: true, | ||
| 30 | + backgroundColor: '#f5f5f5' | ||
| 31 | +} | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +## 步骤 3:编写页面组件 | ||
| 35 | + | ||
| 36 | +**`index.vue`**: | ||
| 37 | +```vue | ||
| 38 | +<script setup> | ||
| 39 | +import { ref } from 'vue' | ||
| 40 | +import { useLoad, useShow } from '@tarojs/taro' | ||
| 41 | + | ||
| 42 | +const pageId = ref(null) | ||
| 43 | + | ||
| 44 | +useLoad((options) => { | ||
| 45 | + console.log('页面加载,参数:', options) | ||
| 46 | + // 接收导航参数 | ||
| 47 | + if (options.id) { | ||
| 48 | + pageId.value = options.id | ||
| 49 | + // 根据 ID 获取数据 | ||
| 50 | + } | ||
| 51 | +}) | ||
| 52 | + | ||
| 53 | +useShow(() => { | ||
| 54 | + console.log('页面显示') | ||
| 55 | +}) | ||
| 56 | + | ||
| 57 | +// 您的组件逻辑 | ||
| 58 | +</script> | ||
| 59 | + | ||
| 60 | +<template> | ||
| 61 | + <view class="page"> | ||
| 62 | + <!-- 页面内容 --> | ||
| 63 | + </view> | ||
| 64 | +</template> | ||
| 65 | + | ||
| 66 | +<style lang="less" scoped> | ||
| 67 | +.page { | ||
| 68 | + padding: 20px; | ||
| 69 | +} | ||
| 70 | +</style> | ||
| 71 | +``` | ||
| 72 | + | ||
| 73 | +## 步骤 4:注册路由 | ||
| 74 | + | ||
| 75 | +在 **`src/app.config.js`** 中注册路由: | ||
| 76 | + | ||
| 77 | +```javascript | ||
| 78 | +export default { | ||
| 79 | + pages: [ | ||
| 80 | + 'pages/your-page/index', | ||
| 81 | + // ... 其他页面 | ||
| 82 | + ], | ||
| 83 | + // ... | ||
| 84 | +} | ||
| 85 | +``` | ||
| 86 | + | ||
| 87 | +## 步骤 5:添加导航(可选) | ||
| 88 | + | ||
| 89 | +### 使用 useGo Hook(推荐) | ||
| 90 | + | ||
| 91 | +```javascript | ||
| 92 | +import { useGo } from '@/hooks/useGo' | ||
| 93 | +const go = useGo() | ||
| 94 | + | ||
| 95 | +// 带查询参数导航 | ||
| 96 | +go('/pages/your-page/index', { id: 123, type: 'insurance' }) | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +### 使用 Taro 内置导航 | ||
| 100 | + | ||
| 101 | +```javascript | ||
| 102 | +import Taro from '@tarojs/taro' | ||
| 103 | + | ||
| 104 | +Taro.navigateTo({ | ||
| 105 | + url: '/pages/your-page/index?id=123' | ||
| 106 | +}) | ||
| 107 | +``` | ||
| 108 | + | ||
| 109 | +## 接收导航参数 | ||
| 110 | + | ||
| 111 | +在目标页面的 `useLoad` 中接收参数: | ||
| 112 | + | ||
| 113 | +```javascript | ||
| 114 | +useLoad((options) => { | ||
| 115 | + console.log('接收到的参数:', options) | ||
| 116 | + const { id, type } = options | ||
| 117 | + | ||
| 118 | + // 根据参数获取数据 | ||
| 119 | + fetchData(id, type) | ||
| 120 | +}) | ||
| 121 | +``` | ||
| 122 | + | ||
| 123 | +## TabBar 集成(可选) | ||
| 124 | + | ||
| 125 | +如果页面需要底部导航栏: | ||
| 126 | + | ||
| 127 | +1. 导入 `TabBar` 组件 | ||
| 128 | +2. 根据路由配置激活状态 | ||
| 129 | + | ||
| 130 | +```vue | ||
| 131 | +<script setup> | ||
| 132 | +import TabBar from '@/components/TabBar.vue' | ||
| 133 | +</script> | ||
| 134 | + | ||
| 135 | +<template> | ||
| 136 | + <view class="page"> | ||
| 137 | + <!-- 页面内容 --> | ||
| 138 | + <TabBar /> | ||
| 139 | + </view> | ||
| 140 | +</template> | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +## 常见问题 | ||
| 144 | + | ||
| 145 | +### Q: 页面注册后还是 404? | ||
| 146 | + | ||
| 147 | +A: 检查以下几点: | ||
| 148 | +1. 路由路径是否正确(`pages/your-page/index`) | ||
| 149 | +2. 是否重启了开发服务器 | ||
| 150 | +3. `index.config.js` 是否存在 | ||
| 151 | + | ||
| 152 | +### Q: 如何隐藏原生导航栏? | ||
| 153 | + | ||
| 154 | +A: 在 `index.config.js` 中设置: | ||
| 155 | +```javascript | ||
| 156 | +export default { | ||
| 157 | + navigationStyle: 'custom' | ||
| 158 | +} | ||
| 159 | +``` | ||
| 160 | + | ||
| 161 | +### Q: 页面参数丢失? | ||
| 162 | + | ||
| 163 | +A: 检查参数是否正确编码: | ||
| 164 | +```javascript | ||
| 165 | +// ❌ 错误 | ||
| 166 | +go('/pages/detail/index', { id: '123' }) | ||
| 167 | + | ||
| 168 | +// ✅ 正确 | ||
| 169 | +go('/pages/detail/index', { id: 123 }) // 数字类型 | ||
| 170 | +// 或 | ||
| 171 | +go('/pages/detail/index?id=123') // 字符串类型 | ||
| 172 | +``` |
docs/reference/components.md
0 → 100644
| 1 | +# 组件库参考文档 | ||
| 2 | + | ||
| 3 | +本文档列出项目中所有组件的详细说明。 | ||
| 4 | + | ||
| 5 | +## 导航与布局组件 | ||
| 6 | + | ||
| 7 | +### TabBar.vue - 底部导航栏 | ||
| 8 | + | ||
| 9 | +**位置**:`src/components/TabBar.vue` | ||
| 10 | + | ||
| 11 | +**功能**:固定底部导航栏,自动适配安全区域 | ||
| 12 | + | ||
| 13 | +**关键特性**: | ||
| 14 | +- 支持图标 + 文字布局 | ||
| 15 | +- 激活状态高亮 | ||
| 16 | + | ||
| 17 | +**使用页面**:首页、我的、家办、知识库、签单页面 | ||
| 18 | + | ||
| 19 | +--- | ||
| 20 | + | ||
| 21 | +### NavHeader.vue - 自定义导航头 | ||
| 22 | + | ||
| 23 | +**位置**:`src/components/NavHeader.vue` | ||
| 24 | + | ||
| 25 | +**功能**:带返回按钮的自定义导航头 | ||
| 26 | + | ||
| 27 | +**关键特性**: | ||
| 28 | +- 透明/背景变体 | ||
| 29 | +- 刘海屏设备的安全区域内边距 | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +### indexNav.vue - 首页网格导航 | ||
| 34 | + | ||
| 35 | +**位置**:`src/components/indexNav.vue` | ||
| 36 | + | ||
| 37 | +**功能**:首页网格导航 | ||
| 38 | + | ||
| 39 | +--- | ||
| 40 | + | ||
| 41 | +## 图标与媒体组件 | ||
| 42 | + | ||
| 43 | +### IconFont.vue - 图标字体包装器 | ||
| 44 | + | ||
| 45 | +**位置**:`src/components/IconFont.vue` | ||
| 46 | + | ||
| 47 | +**功能**:自定义图标的图标字体包装器 | ||
| 48 | + | ||
| 49 | +**⚠️ 注意**:动态切换时需添加 `:key="name"` | ||
| 50 | + | ||
| 51 | +```vue | ||
| 52 | +<IconFont :name="iconName" :key="iconName" /> | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +--- | ||
| 56 | + | ||
| 57 | +### qrCode.vue - 二维码显示 | ||
| 58 | + | ||
| 59 | +**位置**:`src/components/qrCode.vue` | ||
| 60 | + | ||
| 61 | +**功能**:二维码显示 | ||
| 62 | + | ||
| 63 | +--- | ||
| 64 | + | ||
| 65 | +### qrCodeSearch.vue - 二维码扫描 | ||
| 66 | + | ||
| 67 | +**位置**:`src/components/qrCodeSearch.vue` | ||
| 68 | + | ||
| 69 | +**功能**:二维码扫描 | ||
| 70 | + | ||
| 71 | +--- | ||
| 72 | + | ||
| 73 | +## 列表与展示组件 | ||
| 74 | + | ||
| 75 | +### SectionCard.vue - 分组卡片 | ||
| 76 | + | ||
| 77 | +**位置**:`src/components/SectionCard.vue` | ||
| 78 | + | ||
| 79 | +**功能**:分组卡片组件 | ||
| 80 | + | ||
| 81 | +--- | ||
| 82 | + | ||
| 83 | +### SectionItem.vue - 分组列表项 | ||
| 84 | + | ||
| 85 | +**位置**:`src/components/SectionItem.vue` | ||
| 86 | + | ||
| 87 | +**功能**:分组列表项组件 | ||
| 88 | + | ||
| 89 | +--- | ||
| 90 | + | ||
| 91 | +### ListItemActions/ - 列表项操作按钮 | ||
| 92 | + | ||
| 93 | +**位置**:`src/components/list/ListItemActions/` | ||
| 94 | + | ||
| 95 | +**功能**:列表项操作按钮组 | ||
| 96 | + | ||
| 97 | +**关键特性**: | ||
| 98 | +- 权限检查 | ||
| 99 | +- 自动埋点上报 | ||
| 100 | +- 查看按钮、收藏按钮等 | ||
| 101 | + | ||
| 102 | +--- | ||
| 103 | + | ||
| 104 | +### MaterialCard.vue - 资料卡片(可复用) | ||
| 105 | + | ||
| 106 | +**位置**:`src/components/MaterialCard.vue` | ||
| 107 | + | ||
| 108 | +**功能**:资料展示卡片 | ||
| 109 | + | ||
| 110 | +**关键特性**: | ||
| 111 | +- 自包含业务逻辑:查看、收藏 | ||
| 112 | +- 支持动态标签 | ||
| 113 | +- 文件大小格式化 | ||
| 114 | +- 学习人数显示 | ||
| 115 | +- 支持图片文件预览(使用 Taro.previewImage) | ||
| 116 | + | ||
| 117 | +**使用页面**:首页、搜索页、周热门资料页 | ||
| 118 | + | ||
| 119 | +**使用的 Composable**: | ||
| 120 | +- useCollectOperation | ||
| 121 | +- useListItemClick | ||
| 122 | + | ||
| 123 | +--- | ||
| 124 | + | ||
| 125 | +### ProductCard.vue - 产品卡片(可复用) | ||
| 126 | + | ||
| 127 | +**位置**:`src/components/ProductCard.vue` | ||
| 128 | + | ||
| 129 | +**功能**:产品展示卡片 | ||
| 130 | + | ||
| 131 | +**关键特性**: | ||
| 132 | +- 自定义样式:动态标签、封面图 | ||
| 133 | +- 支持产品详情查看 | ||
| 134 | +- 支持计划书功能 | ||
| 135 | + | ||
| 136 | +**使用页面**:首页、搜索页、产品中心页 | ||
| 137 | + | ||
| 138 | +--- | ||
| 139 | + | ||
| 140 | +## 表单与输入组件 | ||
| 141 | + | ||
| 142 | +### FilterTabs.vue - 过滤标签 | ||
| 143 | + | ||
| 144 | +**位置**:`src/components/FilterTabs.vue` | ||
| 145 | + | ||
| 146 | +**功能**:过滤标签组件 | ||
| 147 | + | ||
| 148 | +--- | ||
| 149 | + | ||
| 150 | +### SearchBar.vue - 搜索栏 | ||
| 151 | + | ||
| 152 | +**位置**:`src/components/SearchBar.vue` | ||
| 153 | + | ||
| 154 | +**功能**:搜索栏组件 | ||
| 155 | + | ||
| 156 | +--- | ||
| 157 | + | ||
| 158 | +## 文档预览组件 | ||
| 159 | + | ||
| 160 | +### DocumentPreview/ - 文档预览 | ||
| 161 | + | ||
| 162 | +**位置**:`src/components/DocumentPreview/` | ||
| 163 | + | ||
| 164 | +**功能**:文档预览组件 | ||
| 165 | + | ||
| 166 | +--- | ||
| 167 | + | ||
| 168 | +### PdfPreview.vue - PDF 预览 | ||
| 169 | + | ||
| 170 | +**位置**:`src/components/PdfPreview.vue` | ||
| 171 | + | ||
| 172 | +**功能**:PDF 文件预览 | ||
| 173 | + | ||
| 174 | +--- | ||
| 175 | + | ||
| 176 | +### OfficeViewer.vue - Office 文档查看器 | ||
| 177 | + | ||
| 178 | +**位置**:`src/components/OfficeViewer.vue` | ||
| 179 | + | ||
| 180 | +**功能**:Office 文档查看 | ||
| 181 | + | ||
| 182 | +--- | ||
| 183 | + | ||
| 184 | +## 业务组件 | ||
| 185 | + | ||
| 186 | +### PlanSchemes/ - 计划方案 | ||
| 187 | + | ||
| 188 | +**位置**:`src/components/plan/PlanSchemes/` | ||
| 189 | + | ||
| 190 | +**功能**:计划方案组件(SchemeA, SchemeB) | ||
| 191 | + | ||
| 192 | +--- | ||
| 193 | + | ||
| 194 | +### PlanPopup/ - 计划弹窗 | ||
| 195 | + | ||
| 196 | +**位置**:`src/components/plan/PlanPopup/` | ||
| 197 | + | ||
| 198 | +**功能**:计划弹窗组件 | ||
| 199 | + | ||
| 200 | +**关键特性**: | ||
| 201 | +- 使用 provide/inject 模式实现父子弹窗通信 | ||
| 202 | +- 子弹窗打开时自动隐藏父级 footer | ||
| 203 | +- 支持 AgePicker、DatePicker、SelectPicker 等字段组件 | ||
| 204 | + | ||
| 205 | +--- | ||
| 206 | + | ||
| 207 | +### PlanFields/ - 计划书表单字段 | ||
| 208 | + | ||
| 209 | +**位置**:`src/components/plan/PlanFields/` | ||
| 210 | + | ||
| 211 | +**功能**:计划书表单字段组件集 | ||
| 212 | + | ||
| 213 | +**子组件**: | ||
| 214 | +- `AgePicker.vue` - 年龄选择器 | ||
| 215 | +- `AmountInput.vue` - 金额输入框 | ||
| 216 | +- `DatePicker.vue` - 日期选择器 | ||
| 217 | +- `SelectPicker.vue` - 下拉选择器 | ||
| 218 | +- `RadioGroup.vue` - 单选按钮组 | ||
| 219 | + | ||
| 220 | +--- | ||
| 221 | + | ||
| 222 | +### PlanTemplates/ - 计划模板 | ||
| 223 | + | ||
| 224 | +**位置**:`src/components/plan/PlanTemplates/` | ||
| 225 | + | ||
| 226 | +**功能**:计划模板组件 | ||
| 227 | + | ||
| 228 | +--- | ||
| 229 | + | ||
| 230 | +## 工具组件 | ||
| 231 | + | ||
| 232 | +### PosterBuilder/ - 海报生成器 | ||
| 233 | + | ||
| 234 | +**位置**:`src/components/PosterBuilder/` | ||
| 235 | + | ||
| 236 | +**功能**:海报生成 | ||
| 237 | + | ||
| 238 | +--- | ||
| 239 | + | ||
| 240 | +### time-picker-data/ - 时间选择器数据 | ||
| 241 | + | ||
| 242 | +**位置**:`src/components/time-picker-data/` | ||
| 243 | + | ||
| 244 | +**功能**:时间选择器数据配置 | ||
| 245 | + | ||
| 246 | +--- | ||
| 247 | + | ||
| 248 | +## 可选功能组件 | ||
| 249 | + | ||
| 250 | +以下组件如果不需要,可以移除: | ||
| 251 | + | ||
| 252 | +- **微信支付**:`src/utils/wechatPay.js`、`src/api/wx/pay.js` | ||
| 253 | +- **二维码**:`src/components/qrCode.vue`、`src/components/qrCodeSearch.vue` | ||
| 254 | +- **海报生成器**:`src/components/PosterBuilder/` | ||
| 255 | +- **时间选择器**:`src/components/time-picker-data/` | ||
| 256 | + | ||
| 257 | +--- | ||
| 258 | + | ||
| 259 | +## 组件使用原则 | ||
| 260 | + | ||
| 261 | +### 第 3 次出现原则 | ||
| 262 | + | ||
| 263 | +当相同代码模式出现 3 次时,**必须**抽取为组件。 | ||
| 264 | + | ||
| 265 | +### 组件自包含原则 | ||
| 266 | + | ||
| 267 | +对于重复的UI结构,抽取为可复用组件(如 MaterialCard、ProductCard): | ||
| 268 | +- 组件应该自包含业务逻辑(查看、收藏等) | ||
| 269 | +- 通过事件与父组件通信 | ||
| 270 | +- 避免在父组件中重复编写相同的逻辑代码 | ||
| 271 | + | ||
| 272 | +## 相关文档 | ||
| 273 | + | ||
| 274 | +- **[Composables 参考](composables.md)** - 可复用逻辑 |
docs/reference/composables.md
0 → 100644
| 1 | +# Composables 参考文档 | ||
| 2 | + | ||
| 3 | +本文档列出项目中所有 Composables 的详细说明。 | ||
| 4 | + | ||
| 5 | +## 项目中的 Composables | ||
| 6 | + | ||
| 7 | +| Composable | 用途 | 文档 | | ||
| 8 | +|-----------|------|------| | ||
| 9 | +| `useSectionList` | 分组列表管理 | [详情](#usesectionlist) | | ||
| 10 | +| `useFileOperation` | 文件下载、预览、打开 | [详情](#usefileoperation) | | ||
| 11 | +| `useListItemClick` | 统一的列表点击处理 | [详情](#uselistitemclick) | | ||
| 12 | +| `useCollectOperation` | 收藏操作 | [详情](#usecollectoperation) | | ||
| 13 | +| `useEventTracking` | 事件埋点 | [详情](#useeventtracking) | | ||
| 14 | +| `useGo` | 增强导航 | [详情](#usego) | | ||
| 15 | +| `usePlanPermission` | 计划书权限检查 | [详情](#useplanpermission) | | ||
| 16 | + | ||
| 17 | +--- | ||
| 18 | + | ||
| 19 | +## useSectionList | ||
| 20 | + | ||
| 21 | +**位置**:`src/composables/useSectionList.js` | ||
| 22 | + | ||
| 23 | +**功能**:分组列表管理 | ||
| 24 | + | ||
| 25 | +**用途**:处理分组数据的展开/收起、过滤等逻辑 | ||
| 26 | + | ||
| 27 | +**示例**: | ||
| 28 | +```javascript | ||
| 29 | +import { useSectionList } from '@/composables/useSectionList' | ||
| 30 | + | ||
| 31 | +const { sections, toggleSection, isExpanded } = useSectionList(data) | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +--- | ||
| 35 | + | ||
| 36 | +## useFileOperation | ||
| 37 | + | ||
| 38 | +**位置**:`src/composables/useFileOperation.js` | ||
| 39 | + | ||
| 40 | +**功能**:文件操作(下载、预览、打开) | ||
| 41 | + | ||
| 42 | +**用途**:统一的文件操作逻辑,支持多种文件类型 | ||
| 43 | + | ||
| 44 | +**示例**: | ||
| 45 | +```javascript | ||
| 46 | +import { useFileOperation } from '@/composables/useFileOperation' | ||
| 47 | + | ||
| 48 | +const { downloadFile, previewFile, openFile } = useFileOperation() | ||
| 49 | + | ||
| 50 | +// 下载文件 | ||
| 51 | +await downloadFile(file) | ||
| 52 | + | ||
| 53 | +// 预览文件 | ||
| 54 | +await previewFile(file) | ||
| 55 | +``` | ||
| 56 | + | ||
| 57 | +--- | ||
| 58 | + | ||
| 59 | +## useListItemClick | ||
| 60 | + | ||
| 61 | +**位置**:`src/composables/useListItemClick.js` | ||
| 62 | + | ||
| 63 | +**功能**:统一的列表点击处理 | ||
| 64 | + | ||
| 65 | +**用途**:处理列表项的点击事件,包含权限检查和埋点 | ||
| 66 | + | ||
| 67 | +**示例**: | ||
| 68 | +```javascript | ||
| 69 | +import { useListItemClick } from '@/composables/useListItemClick' | ||
| 70 | + | ||
| 71 | +const { handleItemClick } = useListItemClick() | ||
| 72 | + | ||
| 73 | +// 处理点击 | ||
| 74 | +await handleItemClick(item, () => { | ||
| 75 | + // 点击后的操作 | ||
| 76 | +}) | ||
| 77 | +``` | ||
| 78 | + | ||
| 79 | +--- | ||
| 80 | + | ||
| 81 | +## useCollectOperation | ||
| 82 | + | ||
| 83 | +**位置**:`src/composables/useCollectOperation.js` | ||
| 84 | + | ||
| 85 | +**功能**:收藏/取消收藏操作 | ||
| 86 | + | ||
| 87 | +**用途**:处理收藏状态切换和 API 调用 | ||
| 88 | + | ||
| 89 | +**示例**: | ||
| 90 | +```javascript | ||
| 91 | +import { useCollectOperation } from '@/composables/useCollectOperation' | ||
| 92 | + | ||
| 93 | +const { isCollected, toggleCollect } = useCollectOperation(metaId) | ||
| 94 | + | ||
| 95 | +// 切换收藏状态 | ||
| 96 | +await toggleCollect() | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +--- | ||
| 100 | + | ||
| 101 | +## useEventTracking | ||
| 102 | + | ||
| 103 | +**位置**:`src/composables/useEventTracking.js` | ||
| 104 | + | ||
| 105 | +**功能**:事件埋点 | ||
| 106 | + | ||
| 107 | +**用途**:统一的事件埋点功能,支持多种埋点类型 | ||
| 108 | + | ||
| 109 | +**事件类型**: | ||
| 110 | +- `READ_FILE` - 阅读素材 | ||
| 111 | + | ||
| 112 | +**示例**: | ||
| 113 | +```javascript | ||
| 114 | +import { useEventTracking } from '@/composables/useEventTracking' | ||
| 115 | + | ||
| 116 | +const { trackEvent, trackFileRead } = useEventTracking() | ||
| 117 | + | ||
| 118 | +// 追踪阅读事件 | ||
| 119 | +await trackFileRead('file-id-123') | ||
| 120 | + | ||
| 121 | +// 追踪自定义事件 | ||
| 122 | +await trackEvent('CUSTOM_EVENT', 'object-id', { | ||
| 123 | + title: '文档标题', | ||
| 124 | + category: '分类' | ||
| 125 | +}) | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +--- | ||
| 129 | + | ||
| 130 | +## useGo | ||
| 131 | + | ||
| 132 | +**位置**:`src/hooks/useGo.js` | ||
| 133 | + | ||
| 134 | +**功能**:增强导航 | ||
| 135 | + | ||
| 136 | +**用途**:自动路径补全和便捷导航方法 | ||
| 137 | + | ||
| 138 | +**示例**: | ||
| 139 | +```javascript | ||
| 140 | +import { useGo } from '@/hooks/useGo' | ||
| 141 | + | ||
| 142 | +const go = useGo() | ||
| 143 | + | ||
| 144 | +// 导航到页面 | ||
| 145 | +go('/pages/detail/index') | ||
| 146 | + | ||
| 147 | +// 带参数导航 | ||
| 148 | +go('/pages/product-detail/index', { id: 123 }) | ||
| 149 | + | ||
| 150 | +// 返回 | ||
| 151 | +go.back() | ||
| 152 | +``` | ||
| 153 | + | ||
| 154 | +--- | ||
| 155 | + | ||
| 156 | +## usePlanPermission | ||
| 157 | + | ||
| 158 | +**位置**:`src/composables/usePlanPermission.js` | ||
| 159 | + | ||
| 160 | +**功能**:计划书权限检查 | ||
| 161 | + | ||
| 162 | +**用途**:检查用户是否有权限操作计划书 | ||
| 163 | + | ||
| 164 | +**示例**: | ||
| 165 | +```javascript | ||
| 166 | +import { usePlanPermission } from '@/composables/usePlanPermission' | ||
| 167 | + | ||
| 168 | +const { checkPlanPermission } = usePlanPermission() | ||
| 169 | + | ||
| 170 | +// 检查权限后执行操作 | ||
| 171 | +await checkPlanPermission(() => { | ||
| 172 | + // 有权限后的操作 | ||
| 173 | +}) | ||
| 174 | +``` | ||
| 175 | + | ||
| 176 | +--- | ||
| 177 | + | ||
| 178 | +## 抽取原则 | ||
| 179 | + | ||
| 180 | +**第 3 次出现原则** | ||
| 181 | + | ||
| 182 | +当相同代码模式出现 3 次时,**必须**抽取为 Composable。 | ||
| 183 | + | ||
| 184 | +**示例**: | ||
| 185 | + | ||
| 186 | +```javascript | ||
| 187 | +// ❌ BAD - 在多个组件中重复 | ||
| 188 | +const handleClick = async () => { | ||
| 189 | + if (!isLoggedIn()) { | ||
| 190 | + Taro.showToast({ title: '请先登录', icon: 'none' }) | ||
| 191 | + return | ||
| 192 | + } | ||
| 193 | + // ... 业务逻辑 | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +// ✅ GOOD - 抽取为 Composable | ||
| 197 | +const { requireLogin } = usePermission() | ||
| 198 | +await requireLogin(() => { | ||
| 199 | + // ... 业务逻辑 | ||
| 200 | +}) | ||
| 201 | +``` | ||
| 202 | + | ||
| 203 | +--- | ||
| 204 | + | ||
| 205 | +## 创建新的 Composable | ||
| 206 | + | ||
| 207 | +### 命名规范 | ||
| 208 | + | ||
| 209 | +- 使用 `use` 前缀 | ||
| 210 | +- 使用驼峰命名 | ||
| 211 | +- 名称应描述功能 | ||
| 212 | + | ||
| 213 | +**示例**: | ||
| 214 | +- ✅ `useUserData` | ||
| 215 | +- ✅ `useFormValidation` | ||
| 216 | +- ❌ `userData` | ||
| 217 | +- ❌ `validation` | ||
| 218 | + | ||
| 219 | +### 基本结构 | ||
| 220 | + | ||
| 221 | +```javascript | ||
| 222 | +/** | ||
| 223 | + * 使用 XXX 功能 | ||
| 224 | + * | ||
| 225 | + * @description 功能描述 | ||
| 226 | + * @returns {Object} 返回值描述 | ||
| 227 | + */ | ||
| 228 | +export function useXxx() { | ||
| 229 | + // 响应式状态 | ||
| 230 | + const state = ref(null) | ||
| 231 | + | ||
| 232 | + // 方法 | ||
| 233 | + const method = () => { | ||
| 234 | + // ... | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + // 返回公共 API | ||
| 238 | + return { | ||
| 239 | + state, | ||
| 240 | + method | ||
| 241 | + } | ||
| 242 | +} | ||
| 243 | +``` | ||
| 244 | + | ||
| 245 | +### 使用示例 | ||
| 246 | + | ||
| 247 | +```javascript | ||
| 248 | +// 在组件中使用 | ||
| 249 | +import { useXxx } from '@/composables/useXxx' | ||
| 250 | + | ||
| 251 | +const { state, method } = useXxx() | ||
| 252 | +``` | ||
| 253 | + | ||
| 254 | +--- | ||
| 255 | + | ||
| 256 | +## 相关文档 | ||
| 257 | + | ||
| 258 | +- **[组件参考](components.md)** - 可复用组件 | ||
| 259 | +- **[最佳实践](../best-practices.md)** - 代码复用原则 |
docs/reference/pages.md
0 → 100644
| 1 | +# 页面参考文档 | ||
| 2 | + | ||
| 3 | +本文档列出项目中所有页面的详细说明。 | ||
| 4 | + | ||
| 5 | +## 核心页面 | ||
| 6 | + | ||
| 7 | +### 1. pages/index/index - 首页 | ||
| 8 | + | ||
| 9 | +**功能**:产品展示、搜索、网格导航 | ||
| 10 | + | ||
| 11 | +**关键特性**: | ||
| 12 | +- 热门产品的"产品资料"按钮跳转到 `product-detail` 页面,带产品 ID | ||
| 13 | +- 热门资料的"查看更多"跳转到 `material-list` 页面 | ||
| 14 | +- 网格导航图标跳转到各个业务页面 | ||
| 15 | + | ||
| 16 | +**使用的组件**: | ||
| 17 | +- MaterialCard - 资料卡片 | ||
| 18 | +- ProductCard - 产品卡片 | ||
| 19 | + | ||
| 20 | +--- | ||
| 21 | + | ||
| 22 | +### 2. pages/search/index - 搜索页 | ||
| 23 | + | ||
| 24 | +**功能**:产品和资料搜索 | ||
| 25 | + | ||
| 26 | +**关键特性**: | ||
| 27 | +- 支持实时搜索(输入关键字自动调用 searchAPI) | ||
| 28 | +- 双Tab切换(产品、资料) | ||
| 29 | +- 支持分页加载和触底加载更多 | ||
| 30 | + | ||
| 31 | +**使用的组件**: | ||
| 32 | +- MaterialCard | ||
| 33 | +- ProductCard | ||
| 34 | + | ||
| 35 | +--- | ||
| 36 | + | ||
| 37 | +### 3. pages/webview/index - WebView 包装器 | ||
| 38 | + | ||
| 39 | +**功能**:外部 URL 的 WebView 包装器 | ||
| 40 | + | ||
| 41 | +--- | ||
| 42 | + | ||
| 43 | +### 4. pages/document-preview/index - 文档预览页 | ||
| 44 | + | ||
| 45 | +**功能**:文档预览 | ||
| 46 | + | ||
| 47 | +--- | ||
| 48 | + | ||
| 49 | +### 5. pages/document-demo/index - 文档预览演示页 | ||
| 50 | + | ||
| 51 | +**功能**:文档预览演示 | ||
| 52 | + | ||
| 53 | +--- | ||
| 54 | + | ||
| 55 | +### 6. pages/onboarding/index - 新用户引导 | ||
| 56 | + | ||
| 57 | +**功能**:新用户引导流程 | ||
| 58 | + | ||
| 59 | +--- | ||
| 60 | + | ||
| 61 | +## 业务页面 | ||
| 62 | + | ||
| 63 | +### 7. pages/family-office/index - 家族办公室 | ||
| 64 | + | ||
| 65 | +**功能**:家族办公室服务 | ||
| 66 | + | ||
| 67 | +--- | ||
| 68 | + | ||
| 69 | +### 8. pages/product-center/index - 产品中心 | ||
| 70 | + | ||
| 71 | +**功能**:产品列表展示 | ||
| 72 | + | ||
| 73 | +**关键特性**: | ||
| 74 | +- 分类筛选 | ||
| 75 | +- 分页加载 | ||
| 76 | + | ||
| 77 | +--- | ||
| 78 | + | ||
| 79 | +### 9. pages/category-list/index - 分类列表 | ||
| 80 | + | ||
| 81 | +**功能**:分类列表展示 | ||
| 82 | + | ||
| 83 | +--- | ||
| 84 | + | ||
| 85 | +### 10. pages/product-detail/index - 产品详情 | ||
| 86 | + | ||
| 87 | +**功能**:产品详情展示 | ||
| 88 | + | ||
| 89 | +**关键特性**: | ||
| 90 | +- 通过 Taro 的 `useLoad` hook 接收 `id` 参数 | ||
| 91 | +- 导航示例:`go('/pages/product-detail/index', { id: 1 })` | ||
| 92 | +- 参数可用于从 API 获取产品详情 | ||
| 93 | + | ||
| 94 | +--- | ||
| 95 | + | ||
| 96 | +### 11. pages/material-list/index - 资料列表 | ||
| 97 | + | ||
| 98 | +**功能**:资料/文档列表展示 | ||
| 99 | + | ||
| 100 | +**关键特性**: | ||
| 101 | +- 分类筛选 | ||
| 102 | +- 分页加载 | ||
| 103 | +- 触底加载更多 | ||
| 104 | + | ||
| 105 | +--- | ||
| 106 | + | ||
| 107 | +### 12. pages/week-hot-material/index - 周热门资料 | ||
| 108 | + | ||
| 109 | +**功能**:热门资料展示 | ||
| 110 | + | ||
| 111 | +**关键特性**: | ||
| 112 | +- 使用 MaterialCard 组件展示热门资料 | ||
| 113 | +- 支持分页加载和触底加载更多 | ||
| 114 | + | ||
| 115 | +--- | ||
| 116 | + | ||
| 117 | +### 13. pages/signing/index - 签约 | ||
| 118 | + | ||
| 119 | +**功能**:签约流程 | ||
| 120 | + | ||
| 121 | +--- | ||
| 122 | + | ||
| 123 | +### 14. pages/mine/index - 我的 | ||
| 124 | + | ||
| 125 | +**功能**:用户资料页面 | ||
| 126 | + | ||
| 127 | +**关键特性**: | ||
| 128 | +- 用户信息展示 | ||
| 129 | +- 设置入口 | ||
| 130 | + | ||
| 131 | +--- | ||
| 132 | + | ||
| 133 | +### 15. pages/plan/index - 计划书管理 | ||
| 134 | + | ||
| 135 | +**功能**:业务计划管理 | ||
| 136 | + | ||
| 137 | +**关键特性**: | ||
| 138 | +- 使用 PlanSchemes 和 PlanPopup 组件 | ||
| 139 | +- 支持嵌套弹窗交互(provide/inject 模式) | ||
| 140 | +- 支持滚动加载和分页 | ||
| 141 | + | ||
| 142 | +--- | ||
| 143 | + | ||
| 144 | +### 16. pages/plan-submit-result/index - 计划提交结果 | ||
| 145 | + | ||
| 146 | +**功能**:计划提交结果展示 | ||
| 147 | + | ||
| 148 | +**关键特性**: | ||
| 149 | +- 导航按钮:返回上一页(非首页) | ||
| 150 | + | ||
| 151 | +--- | ||
| 152 | + | ||
| 153 | +## 用户相关页面 | ||
| 154 | + | ||
| 155 | +### 17. pages/favorites/index - 收藏 | ||
| 156 | + | ||
| 157 | +**功能**:用户收藏列表 | ||
| 158 | + | ||
| 159 | +--- | ||
| 160 | + | ||
| 161 | +### 18. pages/avatar/index - 头像设置 | ||
| 162 | + | ||
| 163 | +**功能**:用户头像上传和设置 | ||
| 164 | + | ||
| 165 | +--- | ||
| 166 | + | ||
| 167 | +### 19. pages/message/index - 消息列表 | ||
| 168 | + | ||
| 169 | +**功能**:消息列表展示 | ||
| 170 | + | ||
| 171 | +**关键特性**: | ||
| 172 | +- 未读消息红点 | ||
| 173 | +- 消息分类 | ||
| 174 | + | ||
| 175 | +--- | ||
| 176 | + | ||
| 177 | +### 20. pages/message-detail/index - 消息详情 | ||
| 178 | + | ||
| 179 | +**功能**:消息详情展示 | ||
| 180 | + | ||
| 181 | +--- | ||
| 182 | + | ||
| 183 | +### 21. pages/feedback-list/index - 反馈列表 | ||
| 184 | + | ||
| 185 | +**功能**:意见反馈列表 | ||
| 186 | + | ||
| 187 | +--- | ||
| 188 | + | ||
| 189 | +### 22. pages/feedback/index - 用户反馈 | ||
| 190 | + | ||
| 191 | +**功能**:提交用户反馈 | ||
| 192 | + | ||
| 193 | +--- | ||
| 194 | + | ||
| 195 | +### 23. pages/login/index - 登录 | ||
| 196 | + | ||
| 197 | +**功能**:用户登录 | ||
| 198 | + | ||
| 199 | +**关键特性**: | ||
| 200 | +- 微信授权登录 | ||
| 201 | +- 登录状态检查 | ||
| 202 | + | ||
| 203 | +--- | ||
| 204 | + | ||
| 205 | +### 24. pages/help-center/index - 帮助中心 | ||
| 206 | + | ||
| 207 | +**功能**:帮助中心和常见问题 | ||
| 208 | + | ||
| 209 | +--- | ||
| 210 | + | ||
| 211 | +## 开发测试页面 | ||
| 212 | + | ||
| 213 | +### 25. pages/test-tabs/index - 标签页测试 | ||
| 214 | + | ||
| 215 | +**功能**:仅开发环境,用于测试标签页组件 | ||
| 216 | + | ||
| 217 | +--- | ||
| 218 | + | ||
| 219 | +## 页面注册 | ||
| 220 | + | ||
| 221 | +所有页面在 `src/app.config.js` 中注册: | ||
| 222 | + | ||
| 223 | +```javascript | ||
| 224 | +export default { | ||
| 225 | + pages: [ | ||
| 226 | + 'pages/index/index', | ||
| 227 | + 'pages/search/index', | ||
| 228 | + // ... 其他页面 | ||
| 229 | + ] | ||
| 230 | +} | ||
| 231 | +``` | ||
| 232 | + | ||
| 233 | +## 相关文档 | ||
| 234 | + | ||
| 235 | +- **[页面开发指南](guides/page-development.md)** - 如何添加新页面 | ||
| 236 | +- **[导航系统指南](guides/navigation.md)** - 导航和参数传递 |
-
Please register or login to post a comment