hookehuyr

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

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

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