CLAUDE.md
本文件为 Claude Code (claude.ai/code) 在处理此仓库代码时提供指导。
📚 项目文档索引
🚀 开发命令
核心命令
pnpm dev:weapp # 启动微信小程序开发服务器
pnpm dev:h5 # 启动 H5 开发服务器
pnpm build:weapp # 构建生产版本(微信小程序)
pnpm lint # 运行 ESLint
其他平台构建
pnpm dev:alipay # 支付宝小程序开发
pnpm dev:swan # 百度小程序开发
pnpm dev:tt # 字节跳动小程序开发
📋 快速参考
⚡ 常见问题快速解决
| 问题 | 解决方案 | 参考文档 |
|---|---|---|
| NutUI textarea 样式无法覆盖 | 使用原生 <textarea>
|
经验教训 |
| IconFont 动态切换不响应 | 添加 :key="name"
|
经验教训 |
| SVG 图标加载失败(500 错误) | 使用 import 导入 |
经验教训 |
| 代码重复 3 次 | 抽取为 Composable | 经验教训 |
| 组件对象响应式警告 | 使用 shallowRef + markRaw
|
经验教训 |
🎯 核心架构模式
- 认证流程 - 静默认证 + 401 自动刷新
- 双设计宽度 - NutUI: 375px, 其他: 750px
- 样式策略 - TailwindCSS(80%) + Less(20%)
- 组件抽取 - "第 3 次出现原则"
📦 技术栈
- 框架: Taro 4.1.9 + Vue 3.3.0 + Composition API
- UI 库: NutUI 4.3.13(京东推出的 Taro UI 库)
- 状态管理: Pinia 3.0.3 + taro-plugin-pinia
- HTTP 客户端: axios-miniprogram
- 样式: Less + TailwindCSS 3.x(双设计宽度系统)
- 构建工具: Webpack 5
📖 项目概述
Manulife WeApp(臻奇智荟圈)是一个基于 Taro 4 + Vue 3 + NutUI 构建的财富管理微信小程序。
业务模块
- 产品展示 - 热门产品展示及详情页
- 资料库 - 培训材料和文档管理
- 家办 - 家族办公室服务
- 签单 - 签约流程
- 用户中心 - 个人资料、收藏、反馈、帮助中心
🏗️ 核心架构
1. 可复用的导航组件
TabBar 组件(src/components/TabBar.vue)
- 固定底部导航栏,自动适配安全区域
- 支持图标 + 文字布局,激活状态高亮
- 使用于:首页、我的、家办、知识库、签单页面
NavHeader 组件(src/components/NavHeader.vue)
- 带返回按钮的自定义导航头
- 透明/背景变体,刘海屏设备的安全区域内边距
IconFont 组件(src/components/IconFont.vue)
- 自定义图标的图标字体包装器
- ⚠️ 动态切换时需添加
:key="name"详见经验教训
2. 身份认证流程(必需)
项目具有完善的身份认证系统,支持自动会话管理。
核心文件:
-
src/utils/authRedirect.js- 所有认证逻辑(静默刷新、导航、状态) -
src/utils/request.js- 带 401 自动刷新拦截器的 HTTP 客户端 -
src/pages/auth/index.vue- 认证页(必须保留) -
src/pages/login/index.vue- 登录页
401 自动刷新流程:
- API 返回 401
- 拦截器保存当前页面路径
- 调用
refreshSession()通过微信登录获取新会话 - 使用新会话重试原始请求
- 如果刷新失败,跳转到认证页
重要:后端必须提供 /srv/?a=openid_wxapp 端点用于微信登录。
3. API 层架构
API 定义模式(src/api/index.js):
export const yourAPI = (params) => {
return buildApiUrl('your_action', params)
}
请求包装器(src/api/fn.js):
- 所有 API 调用都应通过此包装器
- 处理常见错误场景
- 始终检查
res.code === 1判断成功
4. 增强导航系统
useGo Hook(src/hooks/useGo.js):
import { useGo } from '@/hooks/useGo'
const go = useGo()
go('/pages/detail/index') // 自动补全路径
go('/pages/product-detail/index', { id: 123 }) // 带查询参数
go.back() // 返回上一页
路由存储(src/stores/router.js):
- 维护已访问路由的栈
- 用于认证回调导航
5. 可复用 Composables
项目中的 Composables:
| Composable | 用途 | 文档 |
|---|---|---|
useSectionList |
分组列表管理 | 经验教训 |
useFileOperation |
文件下载、预览、打开 | 经验教训 |
useListItemClick |
统一的列表点击处理 | 经验教训 |
抽取原则:"第 3 次出现原则" - 当相同代码模式出现 3 次时,必须抽取为 Composable。
6. 样式处理策略
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 |
7. 响应式优化
处理组件对象响应式:
// ❌ BAD - 深度响应式导致性能问题
const menuItems = ref([
{ icon: IconFont, name: 'heart' } // Vue 会深度代理组件对象
])
// ✅ GOOD - 使用 shallowRef + markRaw
import { shallowRef, markRaw } from 'vue'
const menuItems = shallowRef([
{ icon: markRaw(IconFont), name: 'heart' } // 避免深度代理
])
页面结构
所有页面遵循以下目录结构:
src/pages/your-page/
├── index.vue # 页面组件(必须使用 <script setup>)
├── index.config.js # 页面配置(navigationBarTitleText 等)
└── assets/ # 页面特定资源(可选)
当前页面
核心页面:
-
pages/index/index- 首页(产品展示、搜索、网格导航)- 热门产品的"产品资料"按钮跳转到
product-detail页面,带产品 ID - 热门资料的"查看更多"跳转到
material-list页面 - 网格导航图标跳转到各个业务页面
- 热门产品的"产品资料"按钮跳转到
-
pages/auth/index- 认证页 -
pages/login/index- 登录页
业务页面:
-
pages/family-office/index- 家族办公室服务 -
pages/knowledge-base/index- 知识库(培训材料、案例) -
pages/signing/index- 签约 -
pages/plan/index- 业务计划管理 -
pages/favorites/index- 用户收藏 -
pages/feedback/index- 用户反馈 -
pages/avatar/index- 头像设置 -
pages/mine/index- 用户资料
产品与内容页面:
-
pages/product-detail/index- 产品详情页- 通过 Taro 的
useLoadhook 接收id参数 - 导航示例:
go('/pages/product-detail/index', { id: 1 }) - 参数可用于从 API 获取产品详情
- 通过 Taro 的
-
pages/material-list/index- 资料/文档列表页 -
pages/help-center/index- 帮助中心和常见问题页 -
pages/search/index- 产品和资料搜索页
工具页面:
-
pages/onboarding/index- 新用户引导 -
pages/webview/index- 外部 URL 的 WebView 包装器 -
pages/document-demo/index- 文档预览演示页 -
pages/document-preview/index- 文档预览页
组件库
可复用组件:
-
TabBar.vue- 底部导航栏 -
NavHeader.vue- 自定义导航头 -
IconFont.vue- 图标字体包装器
功能组件:
-
qrCode.vue- 二维码显示 -
qrCodeSearch.vue- 二维码扫描 -
PosterBuilder/- 海报生成 -
time-picker-data/- 时间选择器数据
路径别名
全部配置在 config/index.js:30-38:
@/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 - 微信登录成功后由
authRedirect.refreshSession()设置 - 由
request.js拦截器自动注入到请求头中 - 登出或显式手动操作时清除
Promise 单例模式
认证系统使用 Promise 单例防止并发登录尝试:
-
authRedirect.js中的auth_promise确保一次只刷新一个 - 所有并发的 401 共享同一个刷新 Promise
-
.finally()确保无论成功/失败都执行清理
请求超时处理
- 默认超时: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中 - 详见样式处理策略
📦 可选功能
如果不需要,可以移除以下功能:
-
微信支付:
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/
开发工作流
添加新页面
-
创建页面目录:
src/pages/your-page/ ├── index.vue └── index.config.js -
配置页面(
index.config.js):export default { navigationBarTitleText: '您的页面标题', enablePullDownRefresh: true, backgroundColor: '#f5f5f5' } -
在
src/app.config.js中注册路由:pages: [ 'pages/your-page/index', // ... ] -
在
index.vue中使用 composition API:<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('页面显示') })
// 您的组件逻辑
**带参数导航**:
```javascript
// 从另一个页面
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 带查询参数导航
go('/pages/product-detail/index', { id: 123, type: 'insurance' })
-
添加导航(可选的 TabBar 集成):
- 导入并使用
TabBar组件 - 根据路由配置激活状态
- 导入并使用
添加 API 调用
-
在
src/api/index.js中定义 API:export const getProductListAPI = (params) => { return buildApiUrl('product_list', params) } -
在页面中使用:
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()
在目标页面接收参数:
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 内置导航:
import Taro from '@tarojs/taro'
Taro.navigateTo({
url: '/pages/detail/index?id=123'
})
使用 Pinia 状态管理
创建 store(src/stores/yourStore.js):
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 }
})
在组件中使用:
import { useYourStore } from '@/stores/yourStore'
const store = useYourStore()
store.setState('新值')
关键文件总结
修改前必须理解
-
src/utils/authRedirect.js- 完整的认证流程逻辑 -
src/utils/request.js- 带拦截器的 HTTP 客户端 -
src/app.js- 应用启动序列和网络处理 -
src/utils/config.js- 服务器配置(需要修改)
核心业务逻辑
-
src/api/index.js- API 定义 -
src/api/fn.js- 请求包装器 -
src/stores/main.js- 主要状态管理 -
src/stores/router.js- 认证回调的路由状态
可复用组件
-
src/components/TabBar.vue- 底部导航栏 -
src/components/NavHeader.vue- 自定义导航头 -
src/components/IconFont.vue- 图标包装器
UI/UX 工具
-
src/utils/uiText.js- 集中式文案管理 -
src/utils/network.js- 网络状态工具 -
src/hooks/useGo.js- 增强导航 hook
调试技巧
调试问题时:
-
检查环境配置:
- 验证
src/utils/config.js中的BASE_URL - 检查业务模块标识符
f和client_name
- 验证
-
验证身份认证:
- 检查 localStorage 中的
sessionid - 在
src/utils/request.js拦截器中启用详细日志 - 测试 401 刷新流程
- 检查 localStorage 中的
-
网络问题:
- 使用 Taro 内置的网络状态监控
- 检查弱网络降级场景
- 验证离线缓存交互
-
样式问题:
- 确认设计宽度(375px vs 750px)
- 检查是 NutUI 组件还是自定义组件
- 验证 TailwindCSS 类是否已应用
-
导航问题:
- 检查路由是否在
src/app.config.js中注册 - 验证页面目录结构与路由匹配
- 使用
useGohook 进行一致的导航
- 检查路由是否在
✨ 最佳实践
组件开发
- ✅ 使用
<script setup>语法 - ✅ 使用 Composables 处理可复用逻辑
- ✅ Props 应该有类型定义
- ✅ 使用
emit进行子到父通信 - ✅ 优先使用 TailwindCSS 进行样式设计
API 集成
- ✅ 始终检查
res.code === 1判断成功 - ✅ 使用
try/catch进行错误处理 - ✅ 请求期间显示加载状态
- ✅ 优雅地处理网络错误
性能
- ✅ 使用页面懒加载(分包)
- ✅ 使用 CDN 参数优化图片
- ✅ 避免无分页的大数据集
- ✅ 在
onUnmounted中清理定时器和监听器 - ✅ 使用
shallowRef+markRaw处理组件对象
代码质量
- ✅ 遵循 Vue 3 Composition API 模式
- ✅ 使用描述性变量名
- ✅ 保持函数聚焦且简短(< 50 行)
- ✅ 所有函数必须有 JSDoc 注释 详见代码注释规范
- ✅ 提交前运行
pnpm lint
代码复用
- ✅ 遵循"第 3 次出现原则" - 代码重复 3 次时必须抽取
- ✅ 优先使用 Composables 而非 Mixins
- ✅ 组件职责单一,避免过度复杂