CLAUDE.md 16.7 KB

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 经验教训

🎯 核心架构模式

  1. 认证流程 - 静默认证 + 401 自动刷新
  2. 双设计宽度 - NutUI: 375px, 其他: 750px
  3. 样式策略 - TailwindCSS(80%) + Less(20%)
  4. 组件抽取 - "第 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 自动刷新流程

  1. API 返回 401
  2. 拦截器保存当前页面路径
  3. 调用 refreshSession() 通过微信登录获取新会话
  4. 使用新会话重试原始请求
  5. 如果刷新失败,跳转到认证页

重要:后端必须提供 /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 Hooksrc/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/             # 页面特定资源(可选)

当前页面

核心页面

  1. pages/index/index - 首页(产品展示、搜索、网格导航)
    • 热门产品的"产品资料"按钮跳转到 product-detail 页面,带产品 ID
    • 热门资料的"查看更多"跳转到 material-list 页面
    • 网格导航图标跳转到各个业务页面
  2. pages/auth/index - 认证页
  3. pages/login/index - 登录页

业务页面

  1. pages/family-office/index - 家族办公室服务
  2. pages/knowledge-base/index - 知识库(培训材料、案例)
  3. pages/signing/index - 签约
  4. pages/plan/index - 业务计划管理
  5. pages/favorites/index - 用户收藏
  6. pages/feedback/index - 用户反馈
  7. pages/avatar/index - 头像设置
  8. pages/mine/index - 用户资料

产品与内容页面

  1. pages/product-detail/index - 产品详情页
    • 通过 Taro 的 useLoad hook 接收 id 参数
    • 导航示例:go('/pages/product-detail/index', { id: 1 })
    • 参数可用于从 API 获取产品详情
  2. pages/material-list/index - 资料/文档列表页
  3. pages/help-center/index - 帮助中心和常见问题页
  4. pages/search/index - 产品和资料搜索页

工具页面

  1. pages/onboarding/index - 新用户引导
  2. pages/webview/index - 外部 URL 的 WebView 包装器
  3. pages/document-demo/index - 文档预览演示页
  4. 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.jssrc/api/wx/pay.js
  • 二维码src/components/qrCode.vuesrc/components/qrCodeSearch.vue
  • 海报生成器src/components/PosterBuilder/
  • 时间选择器src/components/time-picker-data/

开发工作流

添加新页面

  1. 创建页面目录

    src/pages/your-page/
    ├── index.vue
    └── index.config.js
    
  2. 配置页面index.config.js):

    export default {
     navigationBarTitleText: '您的页面标题',
     enablePullDownRefresh: true,
     backgroundColor: '#f5f5f5'
    }
    
  3. src/app.config.js 中注册路由

    pages: [
     'pages/your-page/index',
     // ...
    ]
    
  4. 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' })
  1. 添加导航(可选的 TabBar 集成):
    • 导入并使用 TabBar 组件
    • 根据路由配置激活状态

添加 API 调用

  1. src/api/index.js 中定义 API

    export const getProductListAPI = (params) => {
       return buildApiUrl('product_list', params)
    }
    
  2. 在页面中使用

    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 状态管理

创建 storesrc/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('新值')

关键文件总结

修改前必须理解

  1. src/utils/authRedirect.js - 完整的认证流程逻辑
  2. src/utils/request.js - 带拦截器的 HTTP 客户端
  3. src/app.js - 应用启动序列和网络处理
  4. src/utils/config.js - 服务器配置(需要修改)

核心业务逻辑

  1. src/api/index.js - API 定义
  2. src/api/fn.js - 请求包装器
  3. src/stores/main.js - 主要状态管理
  4. src/stores/router.js - 认证回调的路由状态

可复用组件

  1. src/components/TabBar.vue - 底部导航栏
  2. src/components/NavHeader.vue - 自定义导航头
  3. src/components/IconFont.vue - 图标包装器

UI/UX 工具

  1. src/utils/uiText.js - 集中式文案管理
  2. src/utils/network.js - 网络状态工具
  3. src/hooks/useGo.js - 增强导航 hook

调试技巧

调试问题时:

  1. 检查环境配置

    • 验证 src/utils/config.js 中的 BASE_URL
    • 检查业务模块标识符 fclient_name
  2. 验证身份认证

    • 检查 localStorage 中的 sessionid
    • src/utils/request.js 拦截器中启用详细日志
    • 测试 401 刷新流程
  3. 网络问题

    • 使用 Taro 内置的网络状态监控
    • 检查弱网络降级场景
    • 验证离线缓存交互
  4. 样式问题

    • 确认设计宽度(375px vs 750px)
    • 检查是 NutUI 组件还是自定义组件
    • 验证 TailwindCSS 类是否已应用
  5. 导航问题

    • 检查路由是否在 src/app.config.js 中注册
    • 验证页面目录结构与路由匹配
    • 使用 useGo hook 进行一致的导航

✨ 最佳实践

组件开发

  • ✅ 使用 <script setup> 语法
  • ✅ 使用 Composables 处理可复用逻辑
  • ✅ Props 应该有类型定义
  • ✅ 使用 emit 进行子到父通信
  • ✅ 优先使用 TailwindCSS 进行样式设计

API 集成

  • ✅ 始终检查 res.code === 1 判断成功
  • ✅ 使用 try/catch 进行错误处理
  • ✅ 请求期间显示加载状态
  • ✅ 优雅地处理网络错误

性能

  • ✅ 使用页面懒加载(分包)
  • ✅ 使用 CDN 参数优化图片
  • ✅ 避免无分页的大数据集
  • ✅ 在 onUnmounted 中清理定时器和监听器
  • ✅ 使用 shallowRef + markRaw 处理组件对象

代码质量

  • ✅ 遵循 Vue 3 Composition API 模式
  • ✅ 使用描述性变量名
  • ✅ 保持函数聚焦且简短(< 50 行)
  • 所有函数必须有 JSDoc 注释 详见代码注释规范
  • ✅ 提交前运行 pnpm lint

代码复用

  • ✅ 遵循"第 3 次出现原则" - 代码重复 3 次时必须抽取
  • ✅ 优先使用 Composables 而非 Mixins
  • ✅ 组件职责单一,避免过度复杂

更多最佳实践详见经验教训总结