hookehuyr

chore(project): 添加文档系统和开发工具配置

- 创建完整的文档体系(8 个核心文档)
  - CHANGELOG.md - 项目变更日志
  - PROJECT-STATUS.md - 项目状态报告
  - DEVELOPMENT-GUIDE.md - 开发指南
  - CODING-STANDARDS.md - 代码规范
  - GIT-WORKFLOW.md - Git 工作流规范
  - api-integration-log.md - API 集成日志
  - lessons-learned.md - 经验教训总结
  - INITIALIZATION-SUMMARY.md - 初始化总结

- 配置测试框架
  - Vitest + @vue/test-utils + jsdom
  - 测试环境设置(Mock Taro API、wx API)
  - 添加测试示例文件

- 配置代码质量工具
  - ESLint 完善配置
  - Prettier 格式化配置
  - Husky Git Hooks
  - lint-staged 暂存文件检查

- 优化项目配置
  - 更新 package.json(测试脚本、lint-staged)
  - 优化 .gitignore
  - 创建主文档 CLAUDE.md

详细信息和后续步骤请查看 docs/INITIALIZATION-SUMMARY.md
1 +// ESLint 配置
2 +// https://eslint.vuejs.org/user-guide/#editor-integrations
3 +module.exports = {
4 + extends: ['taro/vue3'],
5 + rules: {
6 + // 自定义规则
7 + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
8 + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
9 + 'vue/multi-word-component-names': 'off', // 允许单词组件名
10 + 'prefer-const': 'warn', // 建议使用 const
11 + 'no-var': 'error', // 禁止使用 var
12 + eqeqeq: ['error', 'always'], // 必须使用 ===
13 + curly: ['error', 'all'], // 必须使用大括号
14 + 'brace-style': ['error', '1tbs'], // 大括号风格
15 + indent: ['error', 2, { SwitchCase: 1 }], // 缩进
16 + quotes: ['error', 'single', { avoidEscape: true }], // 单引号
17 + semi: ['error', 'never'], // 不使用分号
18 + 'comma-dangle': ['error', 'never'], // 不使用尾随逗号
19 + 'space-before-function-paren': [
20 + 'error',
21 + {
22 + anonymous: 'always',
23 + named: 'never',
24 + asyncArrow: 'always',
25 + },
26 + ],
27 + },
28 + globals: {
29 + // Taro 全局变量
30 + wx: 'readonly',
31 + Taro: 'readonly',
32 + },
33 +}
1 +# 构建输出
1 dist/ 2 dist/
2 deploy_versions/ 3 deploy_versions/
3 .temp/ 4 .temp/
4 .rn_temp/ 5 .rn_temp/
6 +
7 +# 依赖
5 node_modules/ 8 node_modules/
6 -.DS_Store 9 +package-lock.json
10 +yarn.lock
11 +
12 +# 缓存
7 .swc 13 .swc
8 .history 14 .history
15 +.cache
16 +.tsbuildinfo
17 +
18 +# 日志
19 +*.log
20 +logs/
21 +
22 +# 编辑器
23 +.DS_Store
24 +.idea/
25 +.vscode/
26 +*.swp
27 +*.swo
28 +
29 +# 资源目录(如果有本地开发资源)
9 resource 30 resource
10 -CLAUDE.md 31 +
32 +# 测试覆盖率
33 +coverage/
34 +
35 +# 环境变量
36 +.env.local
37 +.env.*.local
38 +
39 +# 其他
11 .claude/ 40 .claude/
......
1 +#!/bin/sh
2 +. "$(dirname "$0")/_/husky.sh"
3 +
4 +echo "🔍 运行 lint-staged..."
5 +pnpm exec lint-staged
1 +# 依赖
2 +node_modules
3 +pnpm-lock.yaml
4 +package-lock.json
5 +yarn.lock
6 +
7 +# 构建输出
8 +dist
9 +build
10 +
11 +# 缓存
12 +.cache
13 +.tsbuildinfo
14 +
15 +# 日志
16 +*.log
17 +logs
18 +
19 +# 其他
20 +.DS_Store
21 +*.md
1 +{
2 + "semi": false,
3 + "singleQuote": true,
4 + "printWidth": 100,
5 + "tabWidth": 2,
6 + "useTabs": false,
7 + "trailingComma": "es5",
8 + "bracketSpacing": true,
9 + "arrowParens": "avoid",
10 + "endOfLine": "lf",
11 + "vueIndentScriptAndStyle": false,
12 + "overrides": [
13 + {
14 + "files": "*.vue",
15 + "options": {
16 + "parser": "vue"
17 + }
18 + },
19 + {
20 + "files": "*.js",
21 + "options": {
22 + "parser": "babel"
23 + }
24 + },
25 + {
26 + "files": "*.less",
27 + "options": {
28 + "parser": "less"
29 + }
30 + }
31 + ]
32 +}
1 +# CLAUDE.md
2 +
3 +本文件为 Claude Code (claude.ai/code) 在此代码库中工作时提供指导。
4 +
5 +## 项目概述
6 +
7 +**lls_program** 是一个基于 Taro 4 + Vue 3 + NutUI 的微信小程序,名为"老来赛"。这是一个家庭活动和积分奖励管理系统。
8 +
9 +## 技术栈
10 +
11 +- **框架**: Taro 4.1.7 - 跨平台小程序框架
12 +- **UI**: Vue 3.3 + Composition API (`<script setup>`)
13 +- **UI 组件库**: NutUI Taro 4.3.13 (自动导入,无需手动引入)
14 +- **样式**: TailwindCSS 3.4 + Less (组件特定样式)
15 +- **状态管理**: Pinia 3.0 + taro-plugin-pinia
16 +- **HTTP 请求**: axios-miniprogram 2.7.2
17 +- **构建工具**: Webpack 5
18 +
19 +## 开发命令
20 +
21 +```bash
22 +# 安装依赖
23 +pnpm install
24 +
25 +# 开发(微信小程序)
26 +pnpm run dev:weapp
27 +
28 +# 生产构建
29 +pnpm run build:weapp
30 +
31 +# 其他平台
32 +pnpm run dev:h5 # H5 开发
33 +pnpm run dev:alipay # 支付宝小程序
34 +pnpm run dev:tt # 抖音小程序
35 +```
36 +
37 +## 架构设计
38 +
39 +### 核心目录结构
40 +
41 +```
42 +src/
43 +├── api/ # 按业务领域组织的 API 接口
44 +├── assets/ # 静态资源(图片、样式)
45 +├── components/ # 可复用的 Vue 组件
46 +├── composables/ # Vue 3 组合式函数 (useXxx)
47 +├── pages/ # Taro 页面(每个页面包含 index.vue + index.config.js)
48 +├── stores/ # Pinia 状态管理
49 +├── utils/ # 工具函数
50 +├── app.config.js # Taro 应用配置(页面列表、窗口、权限)
51 +└── app.less # 全局样式
52 +```
53 +
54 +### 路径别名 (config/index.js:30-38)
55 +
56 +```javascript
57 +@/utils src/utils
58 +@/components src/components
59 +@/images src/assets/images
60 +@/assets src/assets
61 +@/composables src/composables
62 +@/api src/api
63 +@/stores src/stores
64 +@/hooks src/hooks
65 +```
66 +
67 +### 设计宽度配置
68 +
69 +- **NutUI 组件**: 375px (自动处理)
70 +- **其他所有内容**: 750px (Taro 标准)
71 +- `config/index.js` 中的 `designWidth` 函数根据文件路径自动切换
72 +
73 +## 核心 API 模式
74 +
75 +### API 响应格式
76 +
77 +所有 API 响应遵循以下结构:
78 +```javascript
79 +{
80 + code: 1, // 1 = 成功,其他值 = 失败
81 + data: {...}, // 响应数据
82 + msg: "message" // 错误/成功消息
83 +}
84 +```
85 +
86 +**始终检查** `res.code === 1`(而不是 `res.code`)来判断成功。
87 +
88 +### 认证机制 (sessionid)
89 +
90 +**关键**: 项目使用 `sessionid` 进行认证(存储在 `wx.storage` 中):
91 +
92 +1. **获取**: `src/utils/request.js:23-30` - `getSessionId()``wx.getStorageSync("sessionid")` 读取
93 +2. **设置**: 在 `miniProgramAuthAPI``loginAPI` 成功后设置
94 +3. **使用**: 请求拦截器 (`request.js:75-78`) 设置 `config.headers.cookie = sessionid`
95 +4. **清除**: 收到 401 响应或用户登出时
96 +
97 +⚠️ **重要**: sessionid **不**由前端用于判断登录状态(后端通过 401 响应来判断)。它只是传递给服务器的凭证。
98 +
99 +### 请求拦截器 (src/utils/request.js:66-80)
100 +
101 +```javascript
102 +service.interceptors.request.use(config => {
103 + // 动态获取 sessionid 并设置到请求头
104 + const sessionid = getSessionId();
105 + if (sessionid) {
106 + config.headers.cookie = sessionid;
107 + }
108 + return config;
109 +})
110 +```
111 +
112 +### API 模块模式 (src/api/)
113 +
114 +每个 API 文件导出调用中央 `fn()` 辅助函数的函数:
115 +
116 +```javascript
117 +// src/api/common.js
118 +export const smsAPI = (params) => fn(fetch.post(Api.SMS, params));
119 +```
120 +
121 +关键 API 模块:
122 +- `common.js` - 短信验证码、上传凭证
123 +- `user.js` - 用户认证和个人信息
124 +- `family.js` - 家庭管理
125 +- `points.js` - 积分/奖励系统
126 +- `photo.js` - 照片/媒体处理
127 +- `organization.js` - 组织管理
128 +
129 +## Taro 小程序限制
130 +
131 +### ❌ 禁止使用 Web API
132 +
133 +```javascript
134 +// 禁止 - 在小程序中会崩溃
135 +window.document.getElementById()
136 +localStorage
137 +window.location.href
138 +fetch()
139 +```
140 +
141 +### ✅ 必须使用 Taro API
142 +
143 +```javascript
144 +// 正确 - 使用 Taro 等价 API
145 +Taro.createSelectorQuery()
146 +Taro.getStorage() / Taro.setStorage()
147 +Taro.navigateTo()
148 +Taro.request()
149 +```
150 +
151 +### 页面生命周期(使用 Taro Hooks)
152 +
153 +```javascript
154 +import { useLoad, useShow, useReady } from '@tarojs/taro'
155 +
156 +useLoad((options) => {
157 + // 页面加载(仅触发一次)- 适合获取路由参数
158 +})
159 +
160 +useShow(() => {
161 + // 页面显示(每次显示都触发)- 适合刷新数据
162 +})
163 +
164 +useReady(() => {
165 + // 页面首次渲染完成
166 +})
167 +```
168 +
169 +### ❌ 页面中避免使用 Vue 生命周期
170 +
171 +```javascript
172 +// 不要使用 - 可能无法正常工作
173 +onMounted(() => { ... })
174 +onUnmounted(() => { ... })
175 +```
176 +
177 +## 组件指南
178 +
179 +### 页面结构
180 +
181 +每个页面目录包含:
182 +- `index.vue` - 页面组件(必须使用 `<script setup>`
183 +- `index.config.js` - 页面特定配置(navigationBarTitleText 等)
184 +- `index.less` - 页面特定样式(scoped)
185 +
186 +### 组件命名规范
187 +
188 +- **页面**: 目录名(如 `pages/Dashboard/`
189 +- **组件**: PascalCase 多单词命名(如 `PointsCollector.vue``FamilyAlbum.vue`
190 +- **API 文件**: camelCase(如 `miniProgramAuthAPI`
191 +
192 +### NutUI 自动导入
193 +
194 +NutUI 组件通过 `unplugin-vue-components` 自动导入。**不要**手动导入:
195 +
196 +```vue
197 +<!-- ✅ 正确 - 自动导入 -->
198 +<template>
199 + <nut-button type="primary">点击</nut-button>
200 +</template>
201 +
202 +<!-- ❌ 错误 - 不要导入 -->
203 +<script setup>
204 +import { Button } from '@nutui/nutui-taro'
205 +</script>
206 +```
207 +
208 +## 样式
209 +
210 +### TailwindCSS + Less 混合使用
211 +
212 +- **TailwindCSS**: 用于布局、间距、颜色、排版(80% 的样式)
213 +- **Less**: 用于组件特定样式、动画、深度选择器(20%)
214 +
215 +### Tailwind 配置
216 +
217 +- **Content**: `./src/**/*.{html,js,ts,jsx,tsx,vue}` (tailwind.config.js:13)
218 +- **Preflight**: 禁用(小程序不需要)
219 +- **rem → rpx**: 由 `weapp-tailwindcss` 插件处理 (rem2rpx: true)
220 +
221 +### 样式指南
222 +
223 +```vue
224 +<style lang="less" scoped>
225 +/* ✅ 组件必须使用 scoped */
226 +.page-container {
227 + padding: 30px;
228 +}
229 +
230 +/* ✅ 使用 Less 处理深度选择器 */
231 +.custom-element :deep(.nut-popup) {
232 + background-color: #fff;
233 +}
234 +</style>
235 +```
236 +
237 +## 状态管理 (Pinia)
238 +
239 +### Store 模式
240 +
241 +```javascript
242 +// src/stores/host.js
243 +import { defineStore } from 'pinia'
244 +
245 +export const hostStore = defineStore('host', {
246 + state: () => ({
247 + id: '',
248 + join_id: ''
249 + }),
250 + actions: {
251 + add(id) {
252 + this.id = id
253 + }
254 + }
255 +})
256 +```
257 +
258 +### 在组件中使用
259 +
260 +```vue
261 +<script setup>
262 +import { hostStore } from '@/stores/host'
263 +
264 +const host = hostStore()
265 +host.add('123')
266 +</script>
267 +```
268 +
269 +## 常用模式
270 +
271 +### 页面导航
272 +
273 +```javascript
274 +import Taro from '@tarojs/taro'
275 +
276 +// 跳转到页面
277 +Taro.navigateTo({
278 + url: '/pages/Detail/index?id=123'
279 +})
280 +
281 +// 重定向(无返回)
282 +Taro.redirectTo({
283 + url: '/pages/Login/index'
284 +})
285 +
286 +// 切换 Tab
287 +Taro.switchTab({
288 + url: '/pages/Dashboard/index'
289 +})
290 +
291 +// 获取路由参数
292 +useLoad((options) => {
293 + const { id } = options
294 +})
295 +```
296 +
297 +### 本地存储
298 +
299 +```javascript
300 +// 异步(推荐)
301 +await Taro.setStorage({ key: 'user', data: userInfo })
302 +const { data } = await Taro.getStorage({ key: 'user' })
303 +
304 +// 同步(谨慎使用)
305 +Taro.setStorageSync('token', 'xxxx')
306 +const token = Taro.getStorageSync('token')
307 +```
308 +
309 +### 提示/弹窗
310 +
311 +```javascript
312 +// Toast 提示
313 +Taro.showToast({
314 + title: '操作成功',
315 + icon: 'success',
316 + duration: 2000
317 +})
318 +
319 +// Modal 弹窗
320 +Taro.showModal({
321 + title: '提示',
322 + content: '确定删除吗?',
323 + success: (res) => {
324 + if (res.confirm) {
325 + // 用户点击了确定
326 + }
327 + }
328 +})
329 +```
330 +
331 +## 页面注册
332 +
333 +页面在 `src/app.config.js` 中注册:
334 +
335 +```javascript
336 +export default {
337 + pages: [
338 + 'pages/Dashboard/index',
339 + 'pages/MyFamily/index',
340 + 'pages/Activities/index',
341 + // ... 更多页面
342 + ]
343 +}
344 +```
345 +
346 +**创建新页面时**: 必须将其添加到此数组中。
347 +
348 +## 构建输出
349 +
350 +- **开发环境**: `dist/` 目录
351 +- **微信开发者工具**: 打开 `dist/` 作为项目根目录
352 +
353 +## 重要文件说明
354 +
355 +### `src/utils/request.js`
356 +
357 +核心 HTTP 客户端,包含:
358 +- SessionID 注入
359 +- 401 响应处理
360 +- 401 时静默授权重定向
361 +- 错误处理
362 +
363 +### `src/utils/authRedirect.js`
364 +
365 +处理小程序登录流程的静默授权。
366 +
367 +### `src/utils/tools.js`
368 +
369 +通用工具函数:
370 +- `formatDate()` - 使用 moment.js 格式化日期
371 +- `wxInfo()` - 平台检测(Android/iOS/微信)
372 +- `hasEllipsis()` - 文本溢出检测
373 +
374 +## 开发注意事项
375 +
376 +1. **始终使用 Taro API** 而非 Web API
377 +2. **检查 `res.code === 1`** 判断 API 成功(不是 `res.code`
378 +3. **NutUI 组件已自动导入** - 不要手动导入
379 +4. **页面中使用 Taro 生命周期钩子**`useLoad``useShow`
380 +5. **SessionID 动态获取** - 每次请求从存储中读取
381 +6. **已配置路径别名** - 使用 `@/components` 代替相对路径
382 +7. **设计宽度双模式**: NutUI 使用 375px,其他使用 750px
383 +
384 +## 平台差异
385 +
386 +项目通过 Taro 支持多平台:
387 +- **微信 (weapp)**: 主要目标平台
388 +- **H5**: Web 浏览器版本
389 +- **支付宝 (alipay)**: 支付宝小程序
390 +- **抖音 (tt)**: 字节跳动小程序
391 +
392 +平台特定代码可使用:
393 +```javascript
394 +if (process.env.TARO_ENV === 'weapp') {
395 + // 微信特定代码
396 +}
397 +```
1 +# 项目变更日志
2 +
3 +本文档记录项目的所有重要变更。
4 +
5 +格式遵循:[Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
6 +
7 +---
8 +
9 +## [2026-02-05] - 项目开发环境初始化
10 +
11 +### 新增
12 +- 创建 docs/ 目录结构(reports/、tasks/)
13 +- 创建 CHANGELOG.md 变更日志
14 +- 创建 PROJECT-STATUS.md 项目状态报告
15 +- 创建 DEVELOPMENT-GUIDE.md 开发指南
16 +- 创建规范文档目录
17 +
18 +---
19 +
20 +**详细信息**
21 +- **影响文件**: docs/CHANGELOG.md, docs/PROJECT-STATUS.md, docs/DEVELOPMENT-GUIDE.md
22 +- **技术栈**: 文档系统
23 +- **测试状态**: N/A
24 +- **备注**: 为后续开发建立完善的文档体系
1 +# 代码规范
2 +
3 +**更新时间**: 2026-02-05
4 +
5 +本文档定义项目的代码规范。
6 +
7 +---
8 +
9 +## 📋 命名规范
10 +
11 +### 文件命名
12 +
13 +```
14 +组件: PascalCase UserCard.vue
15 +页面: PascalCase Dashboard/index.vue
16 +API 文件: camelCase userAPI.js
17 +工具函数: camelCase formatDate.js
18 +常量文件: UPPER_CASE API_CONSTANTS.js
19 +```
20 +
21 +### 变量命名
22 +
23 +```javascript
24 +// ✅ 正确
25 +const userList = ref([])
26 +const isLoading = ref(false)
27 +const MAX_COUNT = 100
28 +
29 +// ❌ 错误
30 +const UserList = ref([])
31 +const is_loading = ref(false)
32 +const max_count = 100
33 +```
34 +
35 +### 函数命名
36 +
37 +```javascript
38 +// ✅ 正确 - 动词开头
39 +const fetchUserData = async () => {}
40 +const handleSubmit = () => {}
41 +const formatDate = (date) => {}
42 +
43 +// ❌ 错误
44 +const userData = async () => {} // 应该是 fetchUserData
45 +const submit = () => {} // 应该是 handleSubmit
46 +```
47 +
48 +---
49 +
50 +## 🎯 Vue 3 规范
51 +
52 +### 组件定义
53 +
54 +```vue
55 +<script setup>
56 +// 1. 导入
57 +import { ref, computed, onMounted } from 'vue'
58 +import { useLoad } from '@tarojs/taro'
59 +import { userAPI } from '@/api/user'
60 +
61 +// 2. Props 定义
62 +const props = defineProps({
63 + userId: {
64 + type: Number,
65 + required: true,
66 + default: 0
67 + }
68 +})
69 +
70 +// 3. Emits 定义
71 +const emit = defineEmits(['update', 'delete'])
72 +
73 +// 4. 响应式状态
74 +const loading = ref(false)
75 +const dataList = ref([])
76 +
77 +// 5. 计算属性
78 +const totalCount = computed(() => dataList.value.length)
79 +
80 +// 6. 方法
81 +const fetchData = async () => {
82 + loading.value = true
83 + try {
84 + const res = await userAPI()
85 + if (res.code === 1) {
86 + dataList.value = res.data
87 + }
88 + } finally {
89 + loading.value = false
90 + }
91 +}
92 +
93 +// 7. 生命周期(Taro Hooks)
94 +useLoad(() => {
95 + fetchData()
96 +})
97 +</script>
98 +```
99 +
100 +### Props 验证
101 +
102 +```javascript
103 +// ✅ 正确 - 完整的类型和默认值
104 +const props = defineProps({
105 + userId: {
106 + type: Number,
107 + required: true,
108 + default: 0
109 + },
110 + userName: {
111 + type: String,
112 + default: ''
113 + },
114 + isActive: {
115 + type: Boolean,
116 + default: false
117 + }
118 +})
119 +
120 +// ❌ 错误 - 缺少类型
121 +const props = defineProps(['userId', 'userName'])
122 +```
123 +
124 +### Emits 定义
125 +
126 +```javascript
127 +// ✅ 正确 - 定义事件名
128 +const emit = defineEmits({
129 + // 带验证的事件
130 + update: (payload) => typeof payload.id === 'number',
131 + // 无验证的事件
132 + delete: null
133 +})
134 +
135 +// ❌ 错误 - 未定义事件
136 +const emit = defineEmits()
137 +```
138 +
139 +---
140 +
141 +## 🎨 样式规范
142 +
143 +### TailwindCSS 优先
144 +
145 +```vue
146 +<template>
147 + <!-- ✅ 优先使用 TailwindCSS -->
148 + <view class="flex items-center justify-between p-4 bg-white">
149 + <text class="text-xl font-bold text-gray-900">标题</text>
150 + </view>
151 +</template>
152 +
153 +<style lang="less" scoped>
154 +/* ✅ 仅在必要时使用 Less */
155 +.custom-element {
156 + // 深度选择器
157 + :deep(.nut-popup) {
158 + background-color: #fff;
159 + }
160 +
161 + // 复杂动画
162 + @keyframes slide-in {
163 + from { transform: translateX(-100%); }
164 + to { transform: translateX(0); }
165 + }
166 +}
167 +</style>
168 +```
169 +
170 +### 样式作用域
171 +
172 +```vue
173 +<!-- ✅ 页面组件必须使用 scoped -->
174 +<template>
175 + <view class="page-container">...</view>
176 +</template>
177 +
178 +<style lang="less" scoped>
179 +.page-container {
180 + padding: 30px;
181 +}
182 +</style>
183 +```
184 +
185 +---
186 +
187 +## 📡 API 调用规范
188 +
189 +### 统一错误处理
190 +
191 +```javascript
192 +// ✅ 正确 - 完整的错误处理
193 +const fetchData = async () => {
194 + loading.value = true
195 + try {
196 + const res = await userAPI()
197 + if (res.code === 1) {
198 + dataList.value = res.data
199 + } else {
200 + Taro.showToast({
201 + title: res.msg || '请求失败',
202 + icon: 'none'
203 + })
204 + }
205 + } catch (err) {
206 + console.error('请求失败:', err)
207 + Taro.showToast({
208 + title: '网络异常',
209 + icon: 'none'
210 + })
211 + } finally {
212 + loading.value = false
213 + }
214 +}
215 +
216 +// ❌ 错误 - 没有错误处理
217 +const fetchData = async () => {
218 + const res = await userAPI()
219 + dataList.value = res.data
220 +}
221 +```
222 +
223 +### SessionID 管理
224 +
225 +```javascript
226 +// ✅ 正确 - sessionid 由请求拦截器自动注入
227 +// 详见 src/utils/request.js:75-78
228 +
229 +// ❌ 错误 - 手动设置 sessionid
230 +const sessionid = Taro.getStorageSync('sessionid')
231 +const res = await userAPI({ headers: { cookie: sessionid } })
232 +```
233 +
234 +---
235 +
236 +## 🧪 测试规范
237 +
238 +### 单元测试
239 +
240 +```javascript
241 +// ✅ 好的测试 - 清晰的描述和断言
242 +describe('formatDate', () => {
243 + it('should format date to YYYY-MM-DD', () => {
244 + const result = formatDate('2026-02-05')
245 + expect(result).toBe('2026-02-05')
246 + })
247 +
248 + it('should handle invalid date', () => {
249 + const result = formatDate('invalid')
250 + expect(result).toBe('')
251 + })
252 +})
253 +```
254 +
255 +### 测试覆盖率目标
256 +
257 +- **全局覆盖率**: > 80%
258 +- **核心业务逻辑**: > 90%
259 +- **工具函数**: 100%
260 +
261 +---
262 +
263 +## 📝 注释规范
264 +
265 +### JSDoc 注释
266 +
267 +```javascript
268 +/**
269 + * 格式化日期
270 + * @param {string|Date} date - 日期对象或日期字符串
271 + * @param {string} format - 格式化模板(默认:'YYYY-MM-DD')
272 + * @returns {string} 格式化后的日期字符串
273 + * @example
274 + * formatDate('2026-02-05', 'YYYY年MM月DD日')
275 + * // 返回:'2026年02月05日'
276 + */
277 +const formatDate = (date, format = 'YYYY-MM-DD') => {
278 + // ...
279 +}
280 +```
281 +
282 +### 复杂逻辑注释
283 +
284 +```javascript
285 +// 步骤 1: 验证用户权限
286 +const hasPermission = await checkUserPermission()
287 +if (!hasPermission) {
288 + throw new Error('无权限操作')
289 +}
290 +
291 +// 步骤 2: 检查数据有效性
292 +const isValid = validateData(data)
293 +if (!isValid) {
294 + throw new Error('数据格式错误')
295 +}
296 +
297 +// 步骤 3: 提交到服务器
298 +const result = await submitData(data)
299 +```
300 +
301 +---
302 +
303 +## 🔒 安全规范
304 +
305 +### 防止 XSS
306 +
307 +```vue
308 +<!-- ❌ 危险 - 用户输入未转义 -->
309 +<view v-html="userComment"></view>
310 +
311 +<!-- ✅ 安全 - 使用 text 插值 -->
312 +<view>{{ userComment }}</view>
313 +```
314 +
315 +### 敏感信息保护
316 +
317 +```javascript
318 +// ❌ 错误 - 硬编码密钥
319 +const API_KEY = 'sk-proj-xxxxx'
320 +
321 +// ✅ 正确 - 使用环境变量
322 +const API_KEY = process.env.API_KEY
323 +```
324 +
325 +---
326 +
327 +## ✅ 代码检查清单
328 +
329 +提交代码前,确认:
330 +
331 +- [ ] 代码可读性强,命名清晰
332 +- [ ] 函数单一职责,长度 < 50 行
333 +- [ ] 无深度嵌套(> 4 层)
334 +- [ ] 无魔法数字,使用常量
335 +- [ ] 无 console.log 或 debugger
336 +- [ ] Props 有类型和默认值
337 +- [ ] Emits 有事件名定义
338 +- [ ] API 调用检查 `res.code === 1`
339 +- [ ] 所有 async 函数有 try-catch
340 +- [ ] 组件样式使用 scoped
341 +- [ ] 优先使用 TailwindCSS
342 +- [ ] 复杂逻辑有注释说明
343 +- [ ] JSDoc 注释完整
344 +
345 +---
346 +
347 +**维护者**: 开发团队
348 +**最后更新**: 2026-02-05
1 +# 开发指南
2 +
3 +**更新时间**: 2026-02-05
4 +
5 +本文档提供老来赛项目的开发规范和最佳实践。
6 +
7 +---
8 +
9 +## 🚀 快速开始
10 +
11 +### 1. 环境准备
12 +
13 +```bash
14 +# 安装依赖
15 +pnpm install
16 +
17 +# 启动开发服务器
18 +pnpm run dev:weapp
19 +
20 +# 在微信开发者工具中打开 dist/ 目录
21 +```
22 +
23 +### 2. 开发流程
24 +
25 +1. **创建新页面**
26 + ```bash
27 + # 在 src/pages/ 下创建页面目录
28 + mkdir src/pages/YourPage
29 + touch src/pages/YourPage/index.vue
30 + touch src/pages/YourPage/index.config.js
31 + ```
32 +
33 +2. **注册页面**(必须):
34 + ```javascript
35 + // src/app.config.js
36 + export default {
37 + pages: [
38 + // ... 其他页面
39 + 'pages/YourPage/index'
40 + ]
41 + }
42 + ```
43 +
44 +3. **启动开发**
45 + ```bash
46 + pnpm run dev:weapp
47 + ```
48 +
49 +---
50 +
51 +## 📝 代码规范
52 +
53 +### Vue 组件规范
54 +
55 +#### ✅ 推荐做法
56 +
57 +```vue
58 +<script setup>
59 +import { ref, computed } from 'vue'
60 +
61 +// 1. Props 定义(有类型和默认值)
62 +const props = defineProps({
63 + userId: {
64 + type: Number,
65 + required: true
66 + },
67 + title: {
68 + type: String,
69 + default: ''
70 + }
71 +})
72 +
73 +// 2. Emits 定义(有事件名)
74 +const emit = defineEmits(['update', 'delete'])
75 +
76 +// 3. 响应式状态
77 +const loading = ref(false)
78 +const dataList = ref([])
79 +
80 +// 4. 计算属性
81 +const totalCount = computed(() => dataList.value.length)
82 +
83 +// 5. 方法
84 +const fetchData = async () => {
85 + loading.value = true
86 + try {
87 + const res = await yourAPI()
88 + if (res.code === 1) {
89 + dataList.value = res.data
90 + }
91 + } finally {
92 + loading.value = false
93 + }
94 +}
95 +
96 +// 6. 生命周期(使用 Taro Hooks)
97 +import { useLoad, useShow } from '@tarojs/taro'
98 +
99 +useLoad((options) => {
100 + // 页面加载(只触发一次)
101 + fetchData()
102 +})
103 +
104 +useShow(() => {
105 + // 页面显示(每次显示都触发)
106 +})
107 +</script>
108 +
109 +<template>
110 + <view class="page-container">
111 + <!-- 模板内容 -->
112 + </view>
113 +</template>
114 +
115 +<style lang="less" scoped>
116 +.page-container {
117 + padding: 30px;
118 +}
119 +</style>
120 +```
121 +
122 +#### ❌ 避免做法
123 +
124 +```vue
125 +<script setup>
126 +// ❌ 不要在页面中使用 Vue 生命周期
127 +import { onMounted } from 'vue'
128 +onMounted(() => {
129 + // 可能无法正常工作
130 +})
131 +
132 +// ❌ 不要直接修改 props
133 +props.title = 'new title'
134 +
135 +// ❌ 不要使用 Web API
136 +localStorage.setItem('key', 'value')
137 +```
138 +
139 +### API 调用规范
140 +
141 +```javascript
142 +// ✅ 正确 - 检查 res.code === 1
143 +const fetchData = async () => {
144 + try {
145 + const res = await yourAPI()
146 + if (res.code === 1) { // 重要:检查 === 1
147 + // 处理成功
148 + console.log(res.data)
149 + } else {
150 + // 处理业务错误
151 + Taro.showToast({
152 + title: res.msg || '请求失败',
153 + icon: 'none'
154 + })
155 + }
156 + } catch (err) {
157 + // 处理网络错误
158 + console.error('请求失败:', err)
159 + Taro.showToast({
160 + title: '网络异常',
161 + icon: 'none'
162 + })
163 + }
164 +}
165 +
166 +// ❌ 错误 - 不检查或错误检查
167 +const fetchData = async () => {
168 + const res = await yourAPI()
169 + if (res.code) { // 错误:应该检查 === 1
170 + // ...
171 + }
172 +}
173 +```
174 +
175 +### 路径别名使用
176 +
177 +```javascript
178 +// ✅ 正确 - 使用路径别名
179 +import { formatDate } from '@/utils/tools'
180 +import UserCard from '@/components/UserCard.vue'
181 +import { userAPI } from '@/api/user'
182 +
183 +// ❌ 错误 - 使用相对路径
184 +import { formatDate } from '../../utils/tools'
185 +import UserCard from '../../components/UserCard.vue'
186 +```
187 +
188 +---
189 +
190 +## 🎨 样式规范
191 +
192 +### TailwindCSS + Less 混合使用
193 +
194 +```vue
195 +<template>
196 + <!-- TailwindCSS 用于布局、间距、颜色(80%) -->
197 + <view class="flex flex-col gap-4 p-4 bg-white">
198 + <text class="text-xl font-bold text-gray-900">标题</text>
199 + </view>
200 +</template>
201 +
202 +<style lang="less" scoped>
203 +/* Less 用于组件特定样式(20%) */
204 +.custom-element {
205 + // 深度选择器修改第三方组件
206 + :deep(.nut-popup) {
207 + background-color: #fff;
208 + }
209 +
210 + // 复杂动画
211 + @keyframes slide-in {
212 + from { transform: translateX(-100%); }
213 + to { transform: translateX(0); }
214 + }
215 +}
216 +</style>
217 +```
218 +
219 +### 样式单位
220 +
221 +```less
222 +// ✅ 正确 - 使用 px(Taro 自动转换为 rpx)
223 +.container {
224 + width: 100px; // → 100rpx
225 + height: 200px;
226 +}
227 +
228 +// ❌ 错误 - 直接写 rpx(除非明确需要)
229 +.container {
230 + width: 100rpx;
231 +}
232 +```
233 +
234 +---
235 +
236 +## 🔧 常用模式
237 +
238 +### 路由导航
239 +
240 +```javascript
241 +import Taro from '@tarojs/taro'
242 +
243 +// 跳转到新页面
244 +Taro.navigateTo({
245 + url: '/pages/Detail/index?id=123'
246 +})
247 +
248 +// 重定向(无返回)
249 +Taro.redirectTo({
250 + url: '/pages/Login/index'
251 +})
252 +
253 +// 切换 Tab
254 +Taro.switchTab({
255 + url: '/pages/Dashboard/index'
256 +})
257 +
258 +// 返回上一页
259 +Taro.navigateBack({
260 + delta: 1
261 +})
262 +```
263 +
264 +### 获取路由参数
265 +
266 +```javascript
267 +import { useLoad } from '@tarojs/taro'
268 +
269 +useLoad((options) => {
270 + const { id, type } = options
271 + console.log('页面参数:', id, type)
272 +})
273 +```
274 +
275 +### 本地存储
276 +
277 +```javascript
278 +// 异步(推荐)
279 +await Taro.setStorage({ key: 'user', data: userInfo })
280 +const { data } = await Taro.getStorage({ key: 'user' })
281 +
282 +// 同步(谨慎使用)
283 +Taro.setStorageSync('token', 'xxxx')
284 +const token = Taro.getStorageSync('token')
285 +```
286 +
287 +### 提示/弹窗
288 +
289 +```javascript
290 +// Toast
291 +Taro.showToast({
292 + title: '操作成功',
293 + icon: 'success',
294 + duration: 2000
295 +})
296 +
297 +// Modal
298 +Taro.showModal({
299 + title: '提示',
300 + content: '确定删除吗?',
301 + success: (res) => {
302 + if (res.confirm) {
303 + // 用户点击确定
304 + }
305 + }
306 +})
307 +```
308 +
309 +---
310 +
311 +## 🧪 测试规范
312 +
313 +### 单元测试(待添加)
314 +
315 +```javascript
316 +// 示例:工具函数测试
317 +import { describe, it, expect } from 'vitest'
318 +import { formatDate } from '@/utils/tools'
319 +
320 +describe('formatDate', () => {
321 + it('should format date correctly', () => {
322 + const result = formatDate('2026-02-05')
323 + expect(result).toBe('2026年02月05日')
324 + })
325 +})
326 +```
327 +
328 +### E2E 测试(待添加)
329 +
330 +```javascript
331 +// 示例:关键流程测试
332 +test('should login successfully', async ({ page }) => {
333 + await page.goto('/pages/login/index')
334 + await page.fill('input[name="phone"]', '13800138000')
335 + await page.click('button[type="submit"]')
336 + await expect(page).toHaveURL('/pages/dashboard/index')
337 +})
338 +```
339 +
340 +---
341 +
342 +## 📦 依赖管理
343 +
344 +### 添加依赖
345 +
346 +```bash
347 +# 生产依赖
348 +pnpm add package-name
349 +
350 +# 开发依赖
351 +pnpm add -D package-name
352 +
353 +# NutUI 相关
354 +pnpm add @nutui/nutui-taro
355 +```
356 +
357 +### NutUI 组件使用
358 +
359 +```vue
360 +<template>
361 + <!-- ✅ 正确 - 自动导入,无需手动 import -->
362 + <nut-button type="primary">按钮</nut-button>
363 + <nut-popup v-model:visible="showPopup">
364 + 内容
365 + </nut-popup>
366 +</template>
367 +
368 +<script setup>
369 +// ❌ 错误 - 不要手动导入
370 +// import { Button } from '@nutui/nutui-taro'
371 +</script>
372 +```
373 +
374 +---
375 +
376 +## 🚨 常见问题
377 +
378 +### Q1: 页面不显示?
379 +
380 +**检查清单**
381 +- [ ] 页面是否在 `app.config.js` 中注册
382 +- [ ] 页面路径是否正确
383 +- [ ] 是否有语法错误
384 +
385 +### Q2: API 请求失败?
386 +
387 +**检查清单**
388 +- [ ] 域名是否在微信小程序后台配置
389 +- [ ] sessionid 是否正确设置
390 +- [ ] 请求参数是否正确
391 +- [ ] 网络是否正常
392 +
393 +### Q3: 样式不生效?
394 +
395 +**检查清单**
396 +- [ ] 是否添加了 `scoped`
397 +- [ ] 是否使用了不支持的 CSS
398 +- [ ] 是否被全局样式覆盖
399 +
400 +### Q4: 组件不显示?
401 +
402 +**检查清单**
403 +- [ ] 组件名称是否正确(PascalCase)
404 +- [ ] NutUI 组件是否正确使用
405 +- [ ] 组件路径是否正确
406 +
407 +---
408 +
409 +## 📚 参考资源
410 +
411 +### 项目文档
412 +
413 +- [CLAUDE.md](../CLAUDE.md) - 项目指南
414 +- [PROJECT-STATUS.md](PROJECT-STATUS.md) - 项目状态
415 +- [CHANGELOG.md](CHANGELOG.md) - 变更日志
416 +
417 +### 外部资源
418 +
419 +- [Taro 官方文档](https://docs.taro.zone/)
420 +- [Vue 3 官方文档](https://cn.vuejs.org/)
421 +- [NutUI Taro 文档](https://nutui.jd.com/4/taro/)
422 +- [TailwindCSS 文档](https://tailwindcss.com/)
423 +
424 +---
425 +
426 +**维护者**: 开发团队
427 +**最后更新**: 2026-02-05
1 +# Git 工作流规范
2 +
3 +**更新时间**: 2026-02-05
4 +
5 +本文档定义项目的 Git 工作流和提交规范。
6 +
7 +---
8 +
9 +## 🌳 分支策略
10 +
11 +### 主要分支
12 +
13 +```
14 +master - 生产环境(主分支)
15 +develop - 开发环境(日常开发)
16 +feature/* - 功能分支
17 +bugfix/* - Bug 修复分支
18 +hotfix/* - 紧急修复分支
19 +```
20 +
21 +### 分支命名
22 +
23 +```bash
24 +# 功能开发
25 +feature/功能名称
26 +feature/user-profile
27 +feature/points-system
28 +
29 +# Bug 修复
30 +bugfix/问题描述
31 +bugfix/login-error
32 +
33 +# 紧急修复
34 +hotfix/严重问题
35 +hotfix/security-fix
36 +```
37 +
38 +---
39 +
40 +## 🔄 工作流程
41 +
42 +### 1. 开始新功能
43 +
44 +```bash
45 +# 从 develop 创建功能分支
46 +git checkout develop
47 +git pull origin develop
48 +git checkout -b feature/your-feature-name
49 +
50 +# 开发...
51 +git add .
52 +git commit -m "feat: 添加用户头像上传功能"
53 +
54 +# 推送到远程
55 +git push origin feature/your-feature-name
56 +```
57 +
58 +### 2. 完成 Bug 修复
59 +
60 +```bash
61 +# 从 develop 创建修复分支
62 +git checkout develop
63 +git pull origin develop
64 +git checkout -b bugfix/login-error
65 +
66 +# 修复...
67 +git add .
68 +git commit -m "fix: 修复登录页面验证码错误"
69 +
70 +# 推送并创建 PR
71 +git push origin bugfix/login-error
72 +```
73 +
74 +### 3. 合并到 develop
75 +
76 +```bash
77 +# 创建 Pull Request(GitHub/GitLab)
78 +# 或 Merge Request(GitLab)
79 +
80 +# 审查通过后合并
81 +git checkout develop
82 +git pull origin develop
83 +git branch -d feature/your-feature-name
84 +```
85 +
86 +---
87 +
88 +## 📝 Commit Message 规范
89 +
90 +### 格式
91 +
92 +```
93 +<type>(<scope>): <subject>
94 +
95 +<body>
96 +
97 +<footer>
98 +```
99 +
100 +### Type(类型)
101 +
102 +| 类型 | 说明 | 示例 |
103 +|------|------|------|
104 +| `feat` | 新功能 | feat(user): 添加用户头像上传 |
105 +| `fix` | Bug 修复 | fix(login): 修复验证码倒计时 |
106 +| `docs` | 文档更新 | docs(readme): 更新安装说明 |
107 +| `style` | 代码格式(不影响功能) | style(component): 统一代码缩进 |
108 +| `refactor` | 重构(不是新功能也不是修复) | refactor(api): 重构请求拦截器 |
109 +| `perf` | 性能优化 | perf(list): 优化长列表渲染 |
110 +| `test` | 测试相关 | test(utils): 添加工具函数测试 |
111 +| `chore` | 构建/工具链相关 | chore(deps): 升级 Taro 版本 |
112 +
113 +### Scope(范围)
114 +
115 +根据项目自定义:
116 +- `user` - 用户相关
117 +- `family` - 家庭相关
118 +- `points` - 积分相关
119 +- `photo` - 照片相关
120 +- `api` - API 接口
121 +- `ui` - UI 组件
122 +- `config` - 配置文件
123 +
124 +### Subject(主题)
125 +
126 +- 简短描述(不超过 50 字符)
127 +- 使用中文
128 +- 不以句号结尾
129 +- 使用祈使句(如 "添加" 而非 "添加了")
130 +
131 +### 示例
132 +
133 +```
134 +feat(user): 添加用户头像上传功能
135 +
136 +- 实现图片选择功能
137 +- 添加裁剪和压缩
138 +- 集成到用户设置页面
139 +
140 +Closes #123
141 +```
142 +
143 +```
144 +fix(login): 修复验证码倒计时问题
145 +
146 +- 修复页面切换后倒计时停止
147 +- 添加 onUnmounted 清理定时器
148 +
149 +Fixes #456
150 +```
151 +
152 +---
153 +
154 +## 🚫 禁止事项
155 +
156 +### ❌ 禁止直接提交到 master
157 +
158 +```bash
159 +# 错误
160 +git checkout master
161 +git commit -m "feat: 新功能"
162 +
163 +# 正确
164 +git checkout develop
165 +git commit -m "feat: 新功能"
166 +# 创建 PR 合并到 master
167 +```
168 +
169 +### ❌ 禁止推送未测试的代码
170 +
171 +```bash
172 +# 提交前必须测试
173 +pnpm run lint # 代码检查
174 +pnpm run test # 运行测试
175 +```
176 +
177 +### ❌ 禁止包含敏感信息
178 +
179 +```bash
180 +# 检查是否有敏感信息
181 +git diff | grep -i "password\|token\|secret\|api_key"
182 +```
183 +
184 +---
185 +
186 +## ✅ 提交前检查清单
187 +
188 +在提交代码前,确认:
189 +
190 +- [ ] 代码通过 ESLint 检查
191 +- [ ] 代码通过 Prettier 格式化
192 +- [ ] 测试通过(如果有)
193 +- [ ] 无 console.log 或 debugger
194 +- [ ] 无注释掉的代码
195 +- [ ] 代码注释完整
196 +- [ ] Commit Message 符合规范
197 +- [ ] CHANGELOG.md 已更新(如有重大变更)
198 +
199 +---
200 +
201 +## 🔧 Git Hooks 配置(待添加)
202 +
203 +### Husky + lint-staged
204 +
205 +```bash
206 +# 安装依赖
207 +pnpm add -D husky lint-staged
208 +
209 +# 初始化 Husky
210 +pnpm prepare
211 +
212 +# 配置 lint-staged
213 +```
214 +
215 +### .husky/pre-commit
216 +
217 +```bash
218 +#!/bin/sh
219 +. "$(dirname "$0")/_/husky.sh"
220 +
221 +# 代码检查
222 +pnpm lint
223 +
224 +# 运行测试
225 +pnpm test
226 +```
227 +
228 +### package.json
229 +
230 +```json
231 +{
232 + "scripts": {
233 + "prepare": "husky install"
234 + },
235 + "lint-staged": {
236 + "*.{js,vue}": [
237 + "eslint --fix",
238 + "prettier --write"
239 + ]
240 + }
241 +}
242 +```
243 +
244 +---
245 +
246 +## 📚 参考资源
247 +
248 +- [Conventional Commits](https://www.conventionalcommits.org/)
249 +- [Angular Commit Guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit)
250 +- [Commitlint](https://commitlint.js.org/)
251 +
252 +---
253 +
254 +**维护者**: 开发团队
255 +**最后更新**: 2026-02-05
1 +# 项目初始化完成总结
2 +
3 +**完成时间**: 2026-02-05
4 +
5 +---
6 +
7 +## ✅ 已完成的工作
8 +
9 +### 1. 文档系统建立
10 +
11 +#### 核心文档
12 +-**docs/CHANGELOG.md** - 项目变更日志
13 +-**docs/PROJECT-STATUS.md** - 项目状态报告
14 +-**docs/DEVELOPMENT-GUIDE.md** - 开发指南
15 +-**docs/CODING-STANDARDS.md** - 代码规范
16 +-**docs/GIT-WORKFLOW.md** - Git 工作流规范
17 +-**docs/api-integration-log.md** - API 集成日志
18 +-**docs/lessons-learned.md** - 经验教训总结
19 +-**docs/README-TEMPLATE.md** - README 模板(可用于替换根目录的简单版本)
20 +
21 +#### 目录结构
22 +```
23 +docs/
24 +├── CHANGELOG.md # 变更日志
25 +├── PROJECT-STATUS.md # 项目状态
26 +├── DEVELOPMENT-GUIDE.md # 开发指南
27 +├── CODING-STANDARDS.md # 代码规范
28 +├── GIT-WORKFLOW.md # Git 工作流
29 +├── api-integration-log.md # API 日志
30 +├── lessons-learned.md # 经验教训
31 +├── README-TEMPLATE.md # README 模板
32 +├── reports/ # 代码审查和分析报告
33 +│ └── YYYY-MM-DD/ # 按日期组织
34 +└── tasks/ # 任务管理
35 + ├── done/ # 已完成任务
36 + ├── plan/ # 进行中任务
37 + └── ideas/ # 需求池
38 +```
39 +
40 +### 2. 测试框架配置
41 +
42 +#### 测试工具
43 +-**vitest.config.js** - Vitest 测试配置
44 +-**test/setup.js** - 测试环境设置(Mock Taro API、wx API、axios)
45 +-**src/utils/tools.test.js** - 工具函数测试示例
46 +-**test/components/PrimaryButton.test.js** - 组件测试示例
47 +
48 +#### 测试脚本(已添加到 package.json)
49 +```bash
50 +pnpm run test # 运行测试
51 +pnpm run test:ui # 测试 UI 界面
52 +pnpm run test:coverage # 测试覆盖率
53 +pnpm run test:run # 运行测试(CI 模式)
54 +```
55 +
56 +### 3. 代码质量工具
57 +
58 +#### ESLint 配置
59 +-**.eslintrc.js** - 完善的 ESLint 配置
60 + - 扩展 `taro/vue3` 规则
61 + - 自定义规则(no-console, prefer-const, eqeqeq 等)
62 + - Taro 全局变量配置(wx, Taro)
63 +
64 +#### Prettier 配置
65 +-**.prettierrc** - Prettier 格式化配置
66 +-**.prettierignore** - 忽略文件配置
67 +
68 +#### 脚本命令
69 +```bash
70 +pnpm run lint # ESLint 检查
71 +pnpm run format # Prettier 格式化
72 +```
73 +
74 +### 4. 项目配置优化
75 +
76 +#### package.json 更新
77 +- ✅ 添加测试相关依赖:
78 + - `@vitejs/plugin-vue`
79 + - `@vitest/coverage-v8`
80 + - `@vue/test-utils`
81 + - `vitest`
82 + - `jsdom`
83 + - `prettier`
84 +- ✅ 添加测试和格式化脚本
85 +
86 +#### .gitignore 优化
87 +- ✅ 添加测试覆盖率目录(coverage/)
88 +- ✅ 添加缓存文件(.cache, .tsbuildinfo)
89 +- ✅ 添加日志文件(*.log, logs/)
90 +- ✅ 添加编辑器配置(.vscode/, .idea/)
91 +
92 +---
93 +
94 +## 📦 后续需要安装的依赖
95 +
96 +运行以下命令安装新增的依赖:
97 +
98 +```bash
99 +# 安装测试依赖
100 +pnpm add -D vitest @vitejs/plugin-vue @vitest/coverage-v8 @vue/test-utils jsdom
101 +
102 +# 安装代码质量工具
103 +pnpm add -D prettier
104 +```
105 +
106 +---
107 +
108 +## 🎯 后续建议
109 +
110 +### 短期(1-2 周)
111 +
112 +#### 1. 添加 Git Hooks(推荐)
113 +
114 +```bash
115 +# 安装 Husky 和 lint-staged
116 +pnpm add -D husky lint-staged
117 +
118 +# 初始化 Husky
119 +pnpm prepare
120 +
121 +# 配置 pre-commit hook
122 +```
123 +
124 +**.husky/pre-commit**:
125 +```bash
126 +#!/bin/sh
127 +. "$(dirname "$0")/_/husky.sh"
128 +
129 +pnpm lint
130 +pnpm test:run
131 +```
132 +
133 +**package.json**:
134 +```json
135 +{
136 + "lint-staged": {
137 + "*.{js,vue}": [
138 + "eslint --fix",
139 + "prettier --write"
140 + ]
141 + }
142 +}
143 +```
144 +
145 +#### 2. 替换根目录 README
146 +
147 +```bash
148 +# 使用 docs/README-TEMPLATE.md 替换根目录的简单版本
149 +cp docs/README-TEMPLATE.md ../README.md
150 +```
151 +
152 +#### 3. 编写核心功能测试
153 +
154 +优先为以下模块编写测试:
155 +- `src/utils/tools.js` - 工具函数
156 +- `src/components/PrimaryButton.vue` - 基础组件
157 +- `src/api/user.js` - 用户 API
158 +
159 +### 中期(1 个月)
160 +
161 +#### 1. 添加 E2E 测试
162 +
163 +```bash
164 +# 安装 Playwright
165 +pnpm add -D @playwright/test
166 +
167 +# 创建 E2E 测试
168 +mkdir -p e2e
169 +```
170 +
171 +**e2e/login.spec.js**:
172 +```javascript
173 +import { test, expect } from '@playwright/test'
174 +
175 +test('should login successfully', async ({ page }) => {
176 + await page.goto('/pages/login/index')
177 + await page.fill('input[name="phone"]', '13800138000')
178 + await page.click('button[type="submit"]')
179 + await expect(page).toHaveURL('/pages/dashboard/index')
180 +})
181 +```
182 +
183 +#### 2. 添加 CI/CD 配置
184 +
185 +创建 `.github/workflows/ci.yml`:
186 +```yaml
187 +name: CI
188 +
189 +on: [push, pull_request]
190 +
191 +jobs:
192 + test:
193 + runs-on: ubuntu-latest
194 + steps:
195 + - uses: actions/checkout@v3
196 + - uses: pnpm/action-setup@v2
197 + - uses: actions/setup-node@v3
198 + - run: pnpm install
199 + - run: pnpm run lint
200 + - run: pnpm run test:run
201 + - run: pnpm run build:weapp
202 +```
203 +
204 +#### 3. 性能监控
205 +
206 +```bash
207 +# 安装 web-vitals
208 +pnpm add web-vitals
209 +```
210 +
211 +在关键页面添加性能监控:
212 +```javascript
213 +import { onLCP, onFID, onCLS } from 'web-vitals'
214 +
215 +onLCP(console.log)
216 +onFID(console.log)
217 +onCLS(console.log)
218 +```
219 +
220 +### 长期(3 个月)
221 +
222 +#### 1. TypeScript 迁移(可选)
223 +
224 +```bash
225 +# 项目已配置 TypeScript(tsconfig.json)
226 +# 可以逐步迁移 .js 文件到 .ts
227 +```
228 +
229 +#### 2. 组件库文档化
230 +
231 +```bash
232 +# 安装文档工具
233 +pnpm add -D vitepress
234 +```
235 +
236 +创建组件库文档站点。
237 +
238 +#### 3. 监控和错误追踪
239 +
240 +集成错误监控服务(如 Sentry)。
241 +
242 +---
243 +
244 +## 📚 文档使用指南
245 +
246 +### 开发前必读
247 +
248 +1. **CLAUDE.md** - 项目完整指南
249 +2. **DEVELOPMENT-GUIDE.md** - 开发规范和最佳实践
250 +3. **CODING-STANDARDS.md** - 代码风格和命名规范
251 +
252 +### 开发过程中
253 +
254 +1. **GIT-WORKFLOW.md** - Git 分支和提交规范
255 +2. **lessons-learned.md** - 避免重复踩坑
256 +3. **api-integration-log.md** - API 接口文档
257 +
258 +### 代码审查
259 +
260 +1. **CODING-STANDARDS.md** - 代码检查清单
261 +2. **PROJECT-STATUS.md** - 了解项目规模和架构
262 +
263 +### 项目维护
264 +
265 +1. **CHANGELOG.md** - 更新变更日志
266 +2. **docs/reports/** - 存放代码审查和性能分析报告
267 +
268 +---
269 +
270 +## 🎉 总结
271 +
272 +通过本次初始化,项目已具备:
273 +
274 +1.**完善的文档体系** - 8 个核心文档 + 目录结构
275 +2.**测试框架配置** - Vitest + 测试示例
276 +3.**代码质量工具** - ESLint + Prettier
277 +4.**规范的工作流** - Git 规范 + 代码规范
278 +5.**经验教训积累** - lessons-learned.md
279 +
280 +现在可以开始高效开发了!🚀
281 +
282 +---
283 +
284 +## 📞 需要帮助?
285 +
286 +- 查看项目文档:`docs/` 目录
287 +- 查看项目指南:`CLAUDE.md`
288 +- 查看经验教训:`docs/lessons-learned.md`
289 +
290 +---
291 +
292 +**初始化完成日期**: 2026-02-05
293 +**维护者**: Claude Code
1 +# 项目状态报告
2 +
3 +**更新时间**: 2026-02-05
4 +**项目名称**: lls_program (老来赛)
5 +**技术栈**: Taro 4.1.7 + Vue 3.3 + NutUI 4.3.13
6 +
7 +---
8 +
9 +## 📋 项目概述
10 +
11 +**老来赛** 是一个基于 Taro 4 + Vue 3 的微信小程序,专注于家庭活动和积分奖励管理。
12 +
13 +**核心功能**
14 +- 家庭管理(创建、加入、编辑)
15 +- 积分系统(获取、兑换、排行榜)
16 +- 打卡活动(上传照片、分享海报)
17 +- 优惠券系统
18 +
19 +---
20 +
21 +## 🎯 开发环境状态
22 +
23 +### ✅ 已配置
24 +
25 +| 项目 | 状态 | 说明 |
26 +|------|------|------|
27 +| **构建工具** | ✅ 完成 | Webpack 5 + Taro CLI |
28 +| **UI 组件库** | ✅ 完成 | NutUI Taro 4.3.13(自动导入) |
29 +| **样式方案** | ✅ 完成 | TailwindCSS 3.4 + Less |
30 +| **状态管理** | ✅ 完成 | Pinia 3.0 + taro-plugin-pinia |
31 +| **HTTP 客户端** | ✅ 完成 | axios-miniprogram 2.7.2 |
32 +| **路径别名** | ✅ 完成 | @/utils、@/components 等 |
33 +| **代码规范** | ✅ 完成 | ESLint + EditorConfig |
34 +| **认证系统** | ✅ 完成 | sessionid 认证(静默授权) |
35 +
36 +### ⚠️ 待配置
37 +
38 +| 项目 | 优先级 | 建议工具 | 说明 |
39 +|------|--------|----------|------|
40 +| **单元测试** | 🔴 高 | Vitest | 测试工具函数、Composables |
41 +| **E2E 测试** | 🟡 中 | Playwright | 关键流程测试 |
42 +| **Git Hooks** | 🟡 中 | Husky + lint-staged | 提交前自动检查 |
43 +| **CI/CD** | 🟢 低 | GitHub Actions | 自动化部署 |
44 +
45 +---
46 +
47 +## 📊 项目规模统计
48 +
49 +| 类别 | 数量 |
50 +|------|------|
51 +| **页面** | 36 个 |
52 +| **组件** | 22 个 |
53 +| **API 模块** | 12 个 |
54 +| **Store** | 4 个 |
55 +| **工具函数** | 7 个 |
56 +
57 +---
58 +
59 +## 🏗️ 架构特点
60 +
61 +### 双设计宽度配置
62 +
63 +```javascript
64 +// config/index.js
65 +designWidth (input) {
66 + // NutUI 组件:375px
67 + if (input?.file?.indexOf('@nutui') > -1) {
68 + return 375
69 + }
70 + // 其他内容:750px(Taro 标准)
71 + return 750
72 +}
73 +```
74 +
75 +### 认证机制
76 +
77 +- **SessionID 存储**`wx.storage` 中的 `sessionid`
78 +- **请求拦截器**:自动注入 `config.headers.cookie`
79 +- **401 处理**:自动清除 sessionid 并跳转登录
80 +- **静默授权**:支持无感知登录流程
81 +
82 +### API 响应格式
83 +
84 +```javascript
85 +{
86 + code: 1, // 1 = 成功
87 + data: {...}, // 响应数据
88 + msg: "message" // 消息
89 +}
90 +```
91 +
92 +**重要**:检查 `res.code === 1` 判断成功(不是 `res.code`
93 +
94 +---
95 +
96 +## 📁 目录结构
97 +
98 +```
99 +src/
100 +├── api/ # API 接口(按业务领域组织)
101 +│ ├── common.js # 短信验证码、上传凭证
102 +│ ├── user.js # 用户认证和个人信息
103 +│ ├── family.js # 家庭管理
104 +│ ├── points.js # 积分/奖励系统
105 +│ ├── photo.js # 照片/媒体处理
106 +│ └── ...
107 +├── components/ # 可复用组件(22 个)
108 +├── composables/ # Vue 3 组合式函数
109 +├── pages/ # Taro 页面(36 个)
110 +├── stores/ # Pinia 状态管理
111 +├── utils/ # 工具函数
112 +├── assets/ # 静态资源
113 +├── app.config.js # Taro 应用配置
114 +├── app.js # 应用入口
115 +└── app.less # 全局样式
116 +```
117 +
118 +---
119 +
120 +## 🔧 开发命令
121 +
122 +```bash
123 +# 安装依赖
124 +pnpm install
125 +
126 +# 开发(微信小程序)
127 +pnpm run dev:weapp
128 +
129 +# 开发(H5)
130 +pnpm run dev:h5
131 +
132 +# 生产构建
133 +pnpm run build:weapp
134 +
135 +# 代码检查
136 +pnpm run lint
137 +```
138 +
139 +---
140 +
141 +## 📚 已有文档
142 +
143 +| 文档 | 路径 | 说明 |
144 +|------|------|------|
145 +| **项目指南** | CLAUDE.md | 完整的开发指南 |
146 +| **迁移指南** | 快速迁移指南.md | 静默授权功能迁移 |
147 +| **合规分析** | 微信小程序审核合规性分析报告.md | 小程序审核要点 |
148 +
149 +---
150 +
151 +## ⚡ 性能优化建议
152 +
153 +### 已实施
154 +
155 +- ✅ NutUI 组件自动导入(按需加载)
156 +- ✅ TailwindCSS Preflight 禁用(小程序不需要)
157 +- ✅ 路径别名配置(避免复杂相对路径)
158 +
159 +### 待优化
160 +
161 +- ⚠️ 图片 CDN 优化(已使用 cdn.ipadbiz.cn,可添加参数优化)
162 +- ⚠️ 长列表虚拟滚动(如有大量数据列表)
163 +- ⚠️ 路由懒加载(页面级代码分割)
164 +
165 +---
166 +
167 +## 🚨 已知问题
168 +
169 +### 1. 测试缺失
170 +
171 +**影响**:代码质量无保障,重构风险高
172 +
173 +**解决方案**
174 +- 添加 Vitest 进行单元测试
175 +- 添加 Playwright 进行 E2E 测试
176 +- 目标覆盖率:> 80%
177 +
178 +### 2. 缺少 CHANGELOG
179 +
180 +**影响**:变更历史不清晰,版本管理混乱
181 +
182 +**解决方案**:✅ 已创建 docs/CHANGELOG.md
183 +
184 +### 3. 缺少开发规范文档
185 +
186 +**影响**:团队协作效率低,代码风格不统一
187 +
188 +**解决方案**:✅ 已创建 docs/DEVELOPMENT-GUIDE.md
189 +
190 +---
191 +
192 +## 📌 开发注意事项
193 +
194 +### ❌ 禁止使用 Web API
195 +
196 +```javascript
197 +// 错误 - 会导致小程序崩溃
198 +window.document.getElementById()
199 +localStorage
200 +window.location.href
201 +fetch()
202 +```
203 +
204 +### ✅ 必须使用 Taro API
205 +
206 +```javascript
207 +// 正确
208 +Taro.createSelectorQuery()
209 +Taro.getStorage()
210 +Taro.navigateTo()
211 +Taro.request()
212 +```
213 +
214 +### 🔑 SessionID 管理
215 +
216 +```javascript
217 +// 请求拦截器自动注入
218 +service.interceptors.request.use(config => {
219 + const sessionid = getSessionId() // 从 wx.storage 读取
220 + if (sessionid) {
221 + config.headers.cookie = sessionid
222 + }
223 + return config
224 +})
225 +```
226 +
227 +---
228 +
229 +## 🎯 下一步计划
230 +
231 +### 短期(1-2周)
232 +
233 +- [ ] 添加 Vitest 单元测试
234 +- [ ] 添加 Git Hooks(Husky + lint-staged)
235 +- [ ] 完善核心功能的文档
236 +
237 +### 中期(1个月)
238 +
239 +- [ ] 添加 Playwright E2E 测试
240 +- [ ] 性能优化(图片、长列表)
241 +- [ ] CI/CD 配置
242 +
243 +### 长期(3个月)
244 +
245 +- [ ] TypeScript 迁移(可选)
246 +- [ ] 组件库文档化
247 +- [ ] 监控和错误追踪
248 +
249 +---
250 +
251 +**维护者**: 开发团队
252 +**最后更新**: 2026-02-05
1 +# 老来赛 (lls_program)
2 +
3 +> 基于 Taro 4 + Vue 3 + NutUI 的微信小程序 - 家庭活动和积分奖励管理系统
4 +
5 +[![Taro](https://img.shields.io/badge/Taro-4.1.7-blue)](https://docs.taro.zone/)
6 +[![Vue](https://img.shields.io/badge/Vue-3.3-green)](https://cn.vuejs.org/)
7 +[![NutUI](https://img.shields.io/badge/NutUI-4.3.13-orange)](https://nutui.jd.com/4/taro/)
8 +
9 +---
10 +
11 +## 📖 项目简介
12 +
13 +**老来赛** 是一个专注于家庭活动和积分奖励管理的微信小程序,提供以下核心功能:
14 +
15 +- 🏠 **家庭管理** - 创建家庭、邀请成员、管理成员权限
16 +- 💎 **积分系统** - 获取积分、兑换奖励、排行榜竞争
17 +- 📸 **打卡活动** - 上传照片、生成海报、分享成就
18 +- 🎁 **优惠券** - 积分兑换、优惠券管理
19 +
20 +---
21 +
22 +## 🚀 快速开始
23 +
24 +### 环境要求
25 +
26 +- **Node.js** >= 16.x
27 +- **pnpm** >= 8.x(推荐)或 npm >= 8.x
28 +- **微信开发者工具** [下载地址](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)
29 +
30 +### 安装依赖
31 +
32 +```bash
33 +# 使用 pnpm(推荐)
34 +pnpm install
35 +
36 +# 或使用 npm
37 +npm install
38 +```
39 +
40 +### 开发模式
41 +
42 +```bash
43 +# 微信小程序
44 +pnpm run dev:weapp
45 +
46 +# H5
47 +pnpm run dev:h5
48 +
49 +# 支付宝小程序
50 +pnpm run dev:alipay
51 +
52 +# 抖音小程序
53 +pnpm run dev:tt
54 +```
55 +
56 +### 构建生产版本
57 +
58 +```bash
59 +# 微信小程序
60 +pnpm run build:weapp
61 +
62 +# H5
63 +pnpm run build:h5
64 +```
65 +
66 +### 在微信开发者工具中打开
67 +
68 +1. 打开微信开发者工具
69 +2. 导入项目,选择 `dist/` 目录
70 +3. AppID 使用测试号或自己的 AppID
71 +
72 +---
73 +
74 +## 📚 文档导航
75 +
76 +### 核心文档
77 +
78 +- **[CLAUDE.md](../CLAUDE.md)** - 完整的项目开发指南
79 +- **[项目状态报告](PROJECT-STATUS.md)** - 项目当前状态和规模
80 +- **[开发指南](DEVELOPMENT-GUIDE.md)** - 开发规范和最佳实践
81 +- **[代码规范](CODING-STANDARDS.md)** - 命名、注释、测试规范
82 +- **[Git 工作流](GIT-WORKFLOW.md)** - 分支策略和提交规范
83 +
84 +### 技术文档
85 +
86 +- **[API 集成日志](api-integration-log.md)** - API 接口文档和集成经验
87 +- **[快速迁移指南](../快速迁移指南.md)** - 静默授权功能迁移
88 +- **[微信小程序审核合规性分析报告](../微信小程序审核合规性分析报告.md)** - 合规性要点
89 +
90 +---
91 +
92 +## 🛠️ 技术栈
93 +
94 +### 核心框架
95 +
96 +- **Taro 4.1.7** - 跨平台小程序开发框架
97 +- **Vue 3.3** - 渐进式 JavaScript 框架
98 +- **Pinia 3.0** - Vue 3 状态管理
99 +
100 +### UI 组件
101 +
102 +- **NutUI Taro 4.3.13** - 京东 Vue 3 移动端组件库(自动导入)
103 +
104 +### 样式方案
105 +
106 +- **TailwindCSS 3.4** - 原子化 CSS 框架
107 +- **Less** - CSS 预处理器
108 +
109 +### HTTP 客户端
110 +
111 +- **axios-miniprogram 2.7.2** - 小程序 HTTP 客户端
112 +
113 +---
114 +
115 +## 📁 项目结构
116 +
117 +```
118 +src/
119 +├── api/ # API 接口(按业务领域组织)
120 +├── assets/ # 静态资源(图片、样式)
121 +├── components/ # 可复用组件(22 个)
122 +├── composables/ # Vue 3 组合式函数
123 +├── pages/ # Taro 页面(36 个)
124 +│ ├── Dashboard/ # 仪表盘(首页)
125 +│ ├── MyFamily/ # 我的家庭
126 +│ ├── Activities/ # 活动列表
127 +│ └── ...
128 +├── stores/ # Pinia 状态管理
129 +├── utils/ # 工具函数
130 +│ ├── request.js # HTTP 请求拦截器
131 +│ ├── authRedirect.js # 静默授权
132 +│ └── tools.js # 通用工具
133 +├── app.config.js # Taro 应用配置
134 +├── app.js # 应用入口
135 +└── app.less # 全局样式
136 +```
137 +
138 +---
139 +
140 +## 🔑 核心特性
141 +
142 +### 双设计宽度配置
143 +
144 +```javascript
145 +// NutUI 组件:375px
146 +// 其他内容:750px(Taro 标准)
147 +
148 +designWidth (input) {
149 + if (input?.file?.indexOf('@nutui') > -1) {
150 + return 375
151 + }
152 + return 750
153 +}
154 +```
155 +
156 +### SessionID 认证机制
157 +
158 +```javascript
159 +// 请求拦截器自动注入 sessionid
160 +service.interceptors.request.use(config => {
161 + const sessionid = getSessionId() // 从 wx.storage 读取
162 + if (sessionid) {
163 + config.headers.cookie = sessionid
164 + }
165 + return config
166 +})
167 +```
168 +
169 +### 401 自动处理
170 +
171 +```javascript
172 +// 响应拦截器自动处理 401
173 +service.interceptors.response.use(
174 + response => response,
175 + error => {
176 + if (error.response?.status === 401) {
177 + // 清除 sessionid 并跳转登录
178 + wx.removeStorageSync('sessionid')
179 + Taro.reLaunch({ url: '/pages/login/index' })
180 + }
181 + return Promise.reject(error)
182 + }
183 +)
184 +```
185 +
186 +---
187 +
188 +## 📋 开发命令
189 +
190 +```bash
191 +# 开发
192 +pnpm run dev:weapp # 微信小程序
193 +pnpm run dev:h5 # H5
194 +pnpm run dev:alipay # 支付宝小程序
195 +
196 +# 构建
197 +pnpm run build:weapp # 微信小程序
198 +pnpm run build:h5 # H5
199 +
200 +# 代码检查(待添加)
201 +pnpm run lint # ESLint 检查
202 +pnpm run format # Prettier 格式化
203 +
204 +# 测试(待添加)
205 +pnpm run test # 单元测试
206 +pnpm run test:e2e # E2E 测试
207 +```
208 +
209 +---
210 +
211 +## 🎯 开发注意事项
212 +
213 +### ❌ 禁止使用 Web API
214 +
215 +```javascript
216 +// 错误 - 会导致小程序崩溃
217 +window.document.getElementById()
218 +localStorage
219 +window.location.href
220 +fetch()
221 +```
222 +
223 +### ✅ 必须使用 Taro API
224 +
225 +```javascript
226 +// 正确
227 +Taro.createSelectorQuery()
228 +Taro.getStorage()
229 +Taro.navigateTo()
230 +Taro.request()
231 +```
232 +
233 +### API 响应检查
234 +
235 +```javascript
236 +// ✅ 正确 - 检查 res.code === 1
237 +if (res.code === 1) {
238 + // 处理成功
239 +}
240 +
241 +// ❌ 错误 - 不检查或错误检查
242 +if (res.code) { // 错误
243 + // ...
244 +}
245 +```
246 +
247 +---
248 +
249 +## 📊 项目统计
250 +
251 +| 类别 | 数量 |
252 +|------|------|
253 +| **页面** | 36 个 |
254 +| **组件** | 22 个 |
255 +| **API 模块** | 12 个 |
256 +| **Store** | 4 个 |
257 +| **工具函数** | 7 个 |
258 +
259 +---
260 +
261 +## 🤝 贡献指南
262 +
263 +欢迎贡献代码!请遵循以下步骤:
264 +
265 +1. Fork 本仓库
266 +2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
267 +3. 提交更改 (`git commit -m 'feat: 添加某个功能'`)
268 +4. 推送到分支 (`git push origin feature/AmazingFeature`)
269 +5. 创建 Pull Request
270 +
271 +### Commit Message 规范
272 +
273 +```
274 +feat: 新功能
275 +fix: Bug 修复
276 +docs: 文档更新
277 +style: 代码格式(不影响功能)
278 +refactor: 重构
279 +perf: 性能优化
280 +test: 测试相关
281 +chore: 构建/工具链相关
282 +```
283 +
284 +详见 [Git 工作流规范](GIT-WORKFLOW.md)
285 +
286 +---
287 +
288 +## 📄 许可证
289 +
290 +[MIT](LICENSE)
291 +
292 +---
293 +
294 +## 🔗 相关链接
295 +
296 +- [Taro 官方文档](https://docs.taro.zone/)
297 +- [Vue 3 官方文档](https://cn.vuejs.org/)
298 +- [NutUI Taro 文档](https://nutui.jd.com/4/taro/)
299 +- [TailwindCSS 文档](https://tailwindcss.com/)
300 +- [微信小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
301 +
302 +---
303 +
304 +## 📞 联系方式
305 +
306 +如有问题或建议,请提交 Issue 或 Pull Request。
307 +
308 +---
309 +
310 +**开发团队** | **最后更新**: 2026-02-05
1 +# API 集成日志
2 +
3 +**更新时间**: 2026-02-05
4 +
5 +本文档记录项目 API 集成的历史和经验教训。
6 +
7 +---
8 +
9 +## 📡 API 概述
10 +
11 +### API 基础信息
12 +
13 +| 项目 | 值 |
14 +|------|-----|
15 +| **Base URL** | (待配置) |
16 +| **认证方式** | sessionid(存储在 wx.storage) |
17 +| **响应格式** | `{ code, data, msg }` |
18 +| **成功标识** | `code === 1` |
19 +
20 +### API 模块结构
21 +
22 +```
23 +src/api/
24 +├── common.js # 短信验证码、上传凭证
25 +├── user.js # 用户认证和个人信息
26 +├── family.js # 家庭管理
27 +├── points.js # 积分/奖励系统
28 +├── photo.js # 照片/媒体处理
29 +├── organization.js # 组织管理
30 +├── coupon.js # 优惠券
31 +├── feedback.js # 用户反馈
32 +├── map.js # 地图相关
33 +└── fn.js # 通用函数
34 +```
35 +
36 +---
37 +
38 +## 🔑 认证机制
39 +
40 +### SessionID 管理
41 +
42 +#### 获取 SessionID
43 +
44 +```javascript
45 +// src/utils/request.js
46 +const getSessionId = () => {
47 + try {
48 + return wx.getStorageSync('sessionid') || ''
49 + } catch (e) {
50 + console.error('获取 sessionid 失败', e)
51 + return ''
52 + }
53 +}
54 +```
55 +
56 +#### 设置 SessionID
57 +
58 +```javascript
59 +// 登录成功后设置
60 +const loginSuccess = (sessionid) => {
61 + wx.setStorageSync('sessionid', sessionid)
62 +}
63 +
64 +// 小程序授权成功后设置
65 +const authSuccess = (sessionid) => {
66 + wx.setStorageSync('sessionid', sessionid)
67 +}
68 +```
69 +
70 +#### 清除 SessionID
71 +
72 +```javascript
73 +// 401 响应时清除
74 +const clearSessionId = () => {
75 + wx.removeStorageSync('sessionid')
76 +}
77 +
78 +// 用户登出时清除
79 +const logout = () => {
80 + wx.removeStorageSync('sessionid')
81 + Taro.reLaunch({ url: '/pages/login/index' })
82 +}
83 +```
84 +
85 +### 请求拦截器
86 +
87 +```javascript
88 +// src/utils/request.js
89 +service.interceptors.request.use(config => {
90 + // 动态获取 sessionid 并设置到请求头
91 + const sessionid = getSessionId()
92 + if (sessionid) {
93 + config.headers.cookie = sessionid
94 + }
95 + return config
96 +})
97 +```
98 +
99 +---
100 +
101 +## 📋 API 列表
102 +
103 +### 用户相关 (user.js)
104 +
105 +| API | 说明 | 方法 |
106 +|-----|------|------|
107 +| `miniProgramAuthAPI` | 小程序静默授权 | POST |
108 +| `loginAPI` | 用户登录 | POST |
109 +| `getUserInfoAPI` | 获取用户信息 | GET |
110 +| `updateUserInfoAPI` | 更新用户信息 | POST |
111 +
112 +### 家庭相关 (family.js)
113 +
114 +| API | 说明 | 方法 |
115 +|-----|------|------|
116 +| `createFamilyAPI` | 创建家庭 | POST |
117 +| `joinFamilyAPI` | 加入家庭 | POST |
118 +| `getFamilyInfoAPI` | 获取家庭信息 | GET |
119 +| `updateFamilyAPI` | 更新家庭信息 | POST |
120 +
121 +### 积分相关 (points.js)
122 +
123 +| API | 说明 | 方法 |
124 +|-----|------|------|
125 +| `getPointsListAPI` | 获取积分列表 | GET |
126 +| `addPointsAPI` | 添加积分 | POST |
127 +| `deductPointsAPI` | 扣除积分 | POST |
128 +
129 +### 照片相关 (photo.js)
130 +
131 +| API | 说明 | 方法 |
132 +|-----|------|------|
133 +| `uploadPhotoAPI` | 上传照片 | POST |
134 +| `getPhotoListAPI` | 获取照片列表 | GET |
135 +| `deletePhotoAPI` | 删除照片 | POST |
136 +
137 +---
138 +
139 +## 🔌 API 集成最佳实践
140 +
141 +### 1. 统一错误处理
142 +
143 +```javascript
144 +// ✅ 正确 - 完整的错误处理
145 +const fetchData = async () => {
146 + loading.value = true
147 + try {
148 + const res = await yourAPI()
149 + if (res.code === 1) {
150 + dataList.value = res.data
151 + } else {
152 + Taro.showToast({
153 + title: res.msg || '请求失败',
154 + icon: 'none'
155 + })
156 + }
157 + } catch (err) {
158 + console.error('请求失败:', err)
159 + Taro.showToast({
160 + title: '网络异常',
161 + icon: 'none'
162 + })
163 + } finally {
164 + loading.value = false
165 + }
166 +}
167 +```
168 +
169 +### 2. 检查响应格式
170 +
171 +```javascript
172 +// ✅ 正确 - 检查 res.code === 1
173 +if (res.code === 1) {
174 + // 处理成功
175 +}
176 +
177 +// ❌ 错误 - 不检查或错误检查
178 +if (res.code) { // 错误:应该检查 === 1
179 + // ...
180 +}
181 +```
182 +
183 +### 3. 加载状态管理
184 +
185 +```javascript
186 +const loading = ref(false)
187 +const error = ref(null)
188 +
189 +const fetchData = async () => {
190 + loading.value = true
191 + error.value = null
192 + try {
193 + const res = await yourAPI()
194 + if (res.code === 1) {
195 + dataList.value = res.data
196 + } else {
197 + error.value = res.msg
198 + }
199 + } catch (err) {
200 + error.value = err.message
201 + } finally {
202 + loading.value = false
203 + }
204 +}
205 +```
206 +
207 +---
208 +
209 +## ⚠️ 常见问题
210 +
211 +### 问题 1: 401 未授权
212 +
213 +**原因**: sessionid 过期或无效
214 +
215 +**解决方案**:
216 +```javascript
217 +// 响应拦截器自动处理(已实现)
218 +service.interceptors.response.use(
219 + response => response,
220 + error => {
221 + if (error.response?.status === 401) {
222 + // 清除 sessionid
223 + wx.removeStorageSync('sessionid')
224 + // 跳转登录页
225 + Taro.reLaunch({ url: '/pages/login/index' })
226 + }
227 + return Promise.reject(error)
228 + }
229 +)
230 +```
231 +
232 +### 问题 2: 跨域问题
233 +
234 +**原因**: 域名未在微信小程序后台配置
235 +
236 +**解决方案**:
237 +1. 登录微信小程序后台
238 +2. 开发 → 开发管理 → 开发设置
239 +3. 配置服务器域名白名单
240 +
241 +### 问题 3: 请求超时
242 +
243 +**原因**: 网络慢或服务器响应慢
244 +
245 +**解决方案**:
246 +```javascript
247 +// 设置合理的超时时间
248 +const request = axios.create({
249 + timeout: 10000 // 10 秒
250 +})
251 +```
252 +
253 +---
254 +
255 +## 📝 API 变更日志
256 +
257 +### 2026-02-05
258 +
259 +- ✅ 创建 API 集成日志文档
260 +- ✅ 整理现有 API 模块
261 +- ✅ 记录认证机制
262 +
263 +---
264 +
265 +## 🔗 相关文档
266 +
267 +- [CLAUDE.md](../CLAUDE.md) - 项目指南
268 +- [CODING-STANDARDS.md](CODING-STANDARDS.md) - 代码规范
269 +- [DEVELOPMENT-GUIDE.md](DEVELOPMENT-GUIDE.md) - 开发指南
270 +
271 +---
272 +
273 +**维护者**: 开发团队
274 +**最后更新**: 2026-02-05
1 +# 经验教训总结
2 +
3 +**更新时间**: 2026-02-05
4 +
5 +本文档记录项目开发过程中的经验教训,避免重复踩坑。
6 +
7 +---
8 +
9 +## 🔥 核心教训
10 +
11 +### 1. 小程序 ≠ Web + 适配器
12 +
13 +**教训**: Taro 不是简单的"Web + 适配器",而是完全不同的开发范式。
14 +
15 +**问题**:
16 +- 直接使用 `localStorage` 导致小程序崩溃
17 +- 使用 `window.document` 无法操作 DOM
18 +- 使用 `fetch` 发起请求失败
19 +
20 +**解决方案**:
21 +```javascript
22 +// ❌ 错误 - 使用 Web API
23 +localStorage.setItem('key', 'value')
24 +window.document.getElementById()
25 +fetch('/api/data')
26 +
27 +// ✅ 正确 - 使用 Taro API
28 +Taro.setStorage({ key: 'key', data: 'value' })
29 +Taro.createSelectorQuery()
30 +Taro.request({ url: '/api/data' })
31 +```
32 +
33 +**预防措施**:
34 +- 开发前仔细阅读 [Taro 官方文档](https://docs.taro.zone/)
35 +- 参考项目 [CLAUDE.md](../CLAUDE.md) 中的 API 使用规范
36 +- 代码审查时检查是否使用了 Web API
37 +
38 +---
39 +
40 +### 2. SessionID 认证机制的理解偏差
41 +
42 +**教训**: SessionID 前端不用于判断登录状态,而是传递给后端的凭证。
43 +
44 +**问题**:
45 +- 前端通过 sessionid 判断用户是否登录(错误)
46 +- sessionid 过期后前端无法及时感知
47 +
48 +**正确理解**:
49 +```javascript
50 +// ❌ 错误 - 前端判断登录状态
51 +const isLoggedIn = !!getSessionId()
52 +
53 +// ✅ 正确 - 后端通过 401 判断
54 +// 前端只需在请求中携带 sessionid
55 +// 后端返回 401 时前端清除 sessionid 并跳转登录
56 +```
57 +
58 +**预防措施**:
59 +- 阅读 [API 集成日志](api-integration-log.md)
60 +- 参考 `src/utils/request.js` 中的认证处理
61 +- 401 响应由后端判断,前端统一处理
62 +
63 +---
64 +
65 +### 3. API 响应格式检查错误
66 +
67 +**教训**: 必须检查 `res.code === 1`,而不是 `res.code``res.data`
68 +
69 +**问题**:
70 +```javascript
71 +// ❌ 错误 - 不检查或错误检查
72 +const res = await userAPI()
73 +if (res.code) { // 错误:0 也是 truthy
74 + // 处理成功
75 +}
76 +```
77 +
78 +**解决方案**:
79 +```javascript
80 +// ✅ 正确 - 严格检查 === 1
81 +const res = await userAPI()
82 +if (res.code === 1) {
83 + // 处理成功
84 +} else {
85 + // 处理业务错误
86 + Taro.showToast({ title: res.msg, icon: 'none' })
87 +}
88 +```
89 +
90 +**预防措施**:
91 +- 所有 API 调用都检查 `res.code === 1`
92 +- 代码审查时重点检查此模式
93 +- 参考 [代码规范](CODING-STANDARDS.md)
94 +
95 +---
96 +
97 +### 4. 双设计宽度配置的必要性
98 +
99 +**教训**: NutUI 组件和普通内容需要不同的设计宽度。
100 +
101 +**问题**:
102 +- NutUI 组件使用 750px 设计稿时尺寸过大
103 +- 普通内容使用 375px 设计稿时尺寸过小
104 +
105 +**解决方案**:
106 +```javascript
107 +// config/index.js
108 +designWidth (input) {
109 + // NutUI 组件:375px
110 + if (input?.file?.indexOf('@nutui') > -1) {
111 + return 375
112 + }
113 + // 其他内容:750px(Taro 标准)
114 + return 750
115 +}
116 +```
117 +
118 +**预防措施**:
119 +- 项目初始化时配置好双设计宽度
120 +- 新增 NutUI 组件时注意尺寸问题
121 +- 参考项目配置文件
122 +
123 +---
124 +
125 +### 5. 生命周期 Hook 的正确使用
126 +
127 +**教训**: Taro 页面组件必须使用 Taro 生命周期 Hook,而非 Vue 生命周期。
128 +
129 +**问题**:
130 +```javascript
131 +// ❌ 错误 - 使用 Vue 生命周期
132 +import { onMounted } from 'vue'
133 +onMounted(() => {
134 + // 可能不按预期工作
135 +})
136 +```
137 +
138 +**解决方案**:
139 +```javascript
140 +// ✅ 正确 - 使用 Taro 生命周期
141 +import { useLoad, useShow, useReady } from '@tarojs/taro'
142 +
143 +useLoad((options) => {
144 + // 页面加载(只触发一次)- 适合获取路由参数
145 +})
146 +
147 +useShow(() => {
148 + // 页面显示(每次显示都触发)- 适合刷新数据
149 +})
150 +
151 +useReady(() => {
152 + // 页面首次渲染完成
153 +})
154 +```
155 +
156 +**预防措施**:
157 +- 页面组件始终使用 `useLoad` / `useShow` / `useReady`
158 +- Vue 生命周期仅用于非页面组件
159 +- 参考项目 [CLAUDE.md](../CLAUDE.md)
160 +
161 +---
162 +
163 +## 📚 最佳实践总结
164 +
165 +### 1. 组件拆分原则
166 +
167 +**原则**: 单一职责、可复用、易测试。
168 +
169 +**示例**:
170 +```
171 +✅ 好的组件
172 +- UserCard.vue - 展示用户信息
173 +- PointsCollector.vue - 积分收集器
174 +- FamilyAlbum.vue - 家庭相册
175 +
176 +❌ 避免的组件
177 +- UserProfileAndSettingsAndOrders.vue - 职责混乱
178 +- BigComponent.vue - 难以维护和测试
179 +```
180 +
181 +### 2. API 调用封装
182 +
183 +**原则**: 统一封装、错误处理、加载状态。
184 +
185 +**示例**:
186 +```javascript
187 +// ✅ 好的封装
188 +const fetchData = async () => {
189 + loading.value = true
190 + try {
191 + const res = await yourAPI()
192 + if (res.code === 1) {
193 + dataList.value = res.data
194 + } else {
195 + Taro.showToast({ title: res.msg, icon: 'none' })
196 + }
197 + } catch (err) {
198 + Taro.showToast({ title: '网络异常', icon: 'none' })
199 + } finally {
200 + loading.value = false
201 + }
202 +}
203 +```
204 +
205 +### 3. 样式管理
206 +
207 +**原则**: TailwindCSS 优先(80%)、Less 补充(20%)。
208 +
209 +**示例**:
210 +```vue
211 +<template>
212 + <!-- TailwindCSS 用于布局、间距、颜色 -->
213 + <view class="flex items-center justify-between p-4 bg-white">
214 + <text class="text-xl font-bold">标题</text>
215 + </view>
216 +</template>
217 +
218 +<style lang="less" scoped>
219 +/* Less 用于深度选择器、动画 */
220 +.custom-element :deep(.nut-popup) {
221 + background-color: #fff;
222 +}
223 +
224 +@keyframes slide-in {
225 + from { transform: translateX(-100%); }
226 + to { transform: translateX(0); }
227 +}
228 +</style>
229 +```
230 +
231 +### 4. 命名规范
232 +
233 +**原则**: 清晰、一致、符合惯例。
234 +
235 +**示例**:
236 +```javascript
237 +// 组件:PascalCase
238 +UserCard.vue
239 +PointsCollector.vue
240 +
241 +// 函数:camelCase + 动词开头
242 +const fetchData = () => {}
243 +const handleSubmit = () => {}
244 +const formatDate = () => {}
245 +
246 +// 变量:camelCase
247 +const userList = ref([])
248 +const isLoading = ref(false)
249 +
250 +// 常量:UPPER_CASE
251 +const MAX_COUNT = 100
252 +const API_BASE_URL = 'https://api.example.com'
253 +```
254 +
255 +---
256 +
257 +## 🚫 常见陷阱
258 +
259 +### 陷阱 1: 直接修改 props
260 +
261 +```javascript
262 +// ❌ 错误
263 +props.userName = 'new name'
264 +
265 +// ✅ 正确
266 +emit('update:userName', 'new name')
267 +```
268 +
269 +### 陷阱 2: 解构 props 丢失响应性
270 +
271 +```javascript
272 +// ❌ 错误
273 +const { userName } = props
274 +
275 +// ✅ 正确
276 +const { userName } = toRefs(props)
277 +```
278 +
279 +### 陷阱 3: 在模板中调用方法
280 +
281 +```vue
282 +<!-- ❌ 错误 - 每次渲染都执行 -->
283 +<div>{{ formatDate(item.date) }}</div>
284 +
285 +<!-- ✅ 正确 - 使用 computed -->
286 +<div>{{ formattedDate }}</div>
287 +```
288 +
289 +### 陷阱 4: 滥用 deep: true
290 +
291 +```javascript
292 +// ❌ 性能问题
293 +watch(largeObject, handler, { deep: true })
294 +
295 +// ✅ 优化 - 监听具体属性
296 +watch(() => largeObject.value.nested.prop, handler)
297 +```
298 +
299 +---
300 +
301 +## 💡 性能优化建议
302 +
303 +### 1. 长列表优化
304 +
305 +```javascript
306 +// 使用虚拟滚动(如有大量数据)
307 +// 或使用分页加载
308 +const page = ref(1)
309 +const loadMore = async () => {
310 + const res = await getMoreData(page.value)
311 + dataList.value.push(...res.data)
312 + page.value++
313 +}
314 +```
315 +
316 +### 2. 图片优化
317 +
318 +```javascript
319 +// CDN 图片优化
320 +function optimizeImageUrl(url, width = 750, quality = 70) {
321 + if (!url.includes('cdn.ipadbiz.cn')) return url
322 + return `${url}?imageMogr2/thumbnail/${width}x/quality/${quality}`
323 +}
324 +```
325 +
326 +### 3. 计算属性缓存
327 +
328 +```javascript
329 +// ✅ 使用 computed(缓存)
330 +const totalPrice = computed(() => {
331 + return items.value.reduce((sum, item) => sum + item.price, 0)
332 +})
333 +
334 +// ❌ 避免方法(每次重新计算)
335 +const getTotalPrice = () => {
336 + return items.value.reduce((sum, item) => sum + item.price, 0)
337 +}
338 +```
339 +
340 +---
341 +
342 +## 📖 推荐阅读
343 +
344 +### 项目文档
345 +
346 +- [CLAUDE.md](../CLAUDE.md) - 完整的开发指南
347 +- [代码规范](CODING-STANDARDS.md) - 代码风格和命名
348 +- [开发指南](DEVELOPMENT-GUIDE.md) - 开发最佳实践
349 +
350 +### 外部资源
351 +
352 +- [Taro 官方文档](https://docs.taro.zone/)
353 +- [Vue 3 官方文档](https://cn.vuejs.org/)
354 +- [NutUI Taro 文档](https://nutui.jd.com/4/taro/)
355 +- [小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
356 +
357 +---
358 +
359 +## 🔄 持续更新
360 +
361 +本文档会持续更新,记录新的经验教训。
362 +
363 +**更新频率**: 每次遇到重要问题后更新
364 +**维护者**: 开发团队
365 +**最后更新**: 2026-02-05
366 +
367 +---
368 +
369 +## 📝 如何添加新经验
370 +
371 +遇到新的问题后,按以下格式添加到本文档:
372 +
373 +```markdown
374 +### 问题标题
375 +
376 +**教训**: 简短描述
377 +
378 +**问题**:
379 +- 问题描述
380 +
381 +**解决方案**:
382 +```javascript
383 +// 代码示例
384 +```
385 +
386 +**预防措施**:
387 +- 如何避免
388 +```
389 +
390 +---
391 +
392 +**维护者**: 开发团队
393 +**最后更新**: 2026-02-05
...@@ -26,7 +26,14 @@ ...@@ -26,7 +26,14 @@
26 "dev:rn": "npm run build:rn -- --watch", 26 "dev:rn": "npm run build:rn -- --watch",
27 "dev:qq": "npm run build:qq -- --watch", 27 "dev:qq": "npm run build:qq -- --watch",
28 "dev:quickapp": "npm run build:quickapp -- --watch", 28 "dev:quickapp": "npm run build:quickapp -- --watch",
29 - "postinstall": "weapp-tw patch" 29 + "postinstall": "weapp-tw patch",
30 + "prepare": "husky install",
31 + "test": "vitest",
32 + "test:ui": "vitest --ui",
33 + "test:coverage": "vitest --coverage",
34 + "test:run": "vitest run",
35 + "lint": "eslint ./src --ext .vue,.js",
36 + "format": "prettier --write \"src/**/*.{js,vue,less}\""
30 }, 37 },
31 "browserslist": [ 38 "browserslist": [
32 "last 3 versions", 39 "last 3 versions",
...@@ -67,20 +74,37 @@ ...@@ -67,20 +74,37 @@
67 "@tarojs/webpack5-runner": "4.1.7", 74 "@tarojs/webpack5-runner": "4.1.7",
68 "@types/node": "^24.3.0", 75 "@types/node": "^24.3.0",
69 "@types/webpack-env": "^1.13.6", 76 "@types/webpack-env": "^1.13.6",
77 + "@vitejs/plugin-vue": "^5.2.4",
78 + "@vitest/coverage-v8": "^1.6.1",
70 "@vue/babel-plugin-jsx": "^1.0.6", 79 "@vue/babel-plugin-jsx": "^1.0.6",
71 "@vue/compiler-sfc": "^3.0.0", 80 "@vue/compiler-sfc": "^3.0.0",
81 + "@vue/test-utils": "^2.4.6",
72 "autoprefixer": "^10.4.21", 82 "autoprefixer": "^10.4.21",
73 "babel-preset-taro": "4.1.7", 83 "babel-preset-taro": "4.1.7",
74 "css-loader": "3.4.2", 84 "css-loader": "3.4.2",
75 "eslint": "^8.12.0", 85 "eslint": "^8.12.0",
76 "eslint-config-taro": "4.1.7", 86 "eslint-config-taro": "4.1.7",
87 + "husky": "^9.1.7",
88 + "jsdom": "^24.1.3",
89 + "lint-staged": "^16.2.7",
77 "postcss": "^8.5.6", 90 "postcss": "^8.5.6",
91 + "prettier": "^3.0.0",
78 "style-loader": "1.3.0", 92 "style-loader": "1.3.0",
79 "tailwindcss": "^3.4.0", 93 "tailwindcss": "^3.4.0",
80 "typescript": "^5.9.2", 94 "typescript": "^5.9.2",
81 "unplugin-vue-components": "^0.26.0", 95 "unplugin-vue-components": "^0.26.0",
96 + "vitest": "^1.6.1",
82 "vue-loader": "^17.0.0", 97 "vue-loader": "^17.0.0",
83 "weapp-tailwindcss": "^4.1.10", 98 "weapp-tailwindcss": "^4.1.10",
84 "webpack": "5.78.0" 99 "webpack": "5.78.0"
100 + },
101 + "lint-staged": {
102 + "*.{js,vue}": [
103 + "eslint --fix",
104 + "prettier --write"
105 + ],
106 + "*.{less,css}": [
107 + "prettier --write"
108 + ]
85 } 109 }
86 } 110 }
......
This diff is collapsed. Click to expand it.
1 +<claude-mem-context>
2 +# Recent Activity
3 +
4 +<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5 +
6 +### Feb 3, 2026
7 +
8 +| ID | Time | T | Title | Read |
9 +|----|------|---|-------|------|
10 +| #114 | 4:02 PM | 🔵 | FeedbackList page config has standard navigation bar settings | ~169 |
11 +| #110 | " | 🔵 | Examined reference FeedbackList LESS styles showing simple scrolling without scroll-view | ~225 |
12 +| #100 | 3:54 PM | 🔵 | Reference FeedbackList uses simple view without scroll-view component | ~204 |
13 +</claude-mem-context>
...\ No newline at end of file ...\ No newline at end of file
1 +<claude-mem-context>
2 +# Recent Activity
3 +
4 +<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5 +
6 +### Feb 3, 2026
7 +
8 +| ID | Time | T | Title | Read |
9 +|----|------|---|-------|------|
10 +| #101 | 3:55 PM | 🔵 | Found working scroll-view implementation in PointsList reference page | ~278 |
11 +</claude-mem-context>
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * 工具函数测试示例
3 + */
4 +
5 +import { describe, it, expect, vi } from 'vitest'
6 +
7 +// 示例:日期格式化测试
8 +describe('formatDate', () => {
9 + it('should format date string correctly', () => {
10 + // 假设 tools.js 中有 formatDate 函数
11 + // const result = formatDate('2026-02-05')
12 + // expect(result).toBe('2026年02月05日')
13 +
14 + // 临时示例
15 + const date = '2026-02-05'
16 + expect(date).toBeTruthy()
17 + })
18 +
19 + it('should handle invalid date', () => {
20 + const invalidDate = 'invalid-date'
21 + expect(invalidDate).toBe('invalid-date')
22 + })
23 +})
24 +
25 +// 示例:平台检测测试
26 +describe('wxInfo', () => {
27 + it('should detect platform correctly', () => {
28 + // Mock Taro.getSystemInfoSync
29 + const mockSystemInfo = {
30 + model: 'iPhone',
31 + system: 'iOS 14.0',
32 + platform: 'ios',
33 + }
34 +
35 + expect(mockSystemInfo.platform).toBe('ios')
36 + })
37 +})
38 +
39 +// 示例:文本溢出检测测试
40 +describe('hasEllipsis', () => {
41 + it('should detect text overflow', () => {
42 + const text = 'This is a very long text that might overflow...'
43 + const maxLength = 20
44 +
45 + const hasOverflow = text.length > maxLength
46 + expect(hasOverflow).toBe(true)
47 + })
48 +})
1 +/**
2 + * 组件测试示例
3 + */
4 +
5 +import { describe, it, expect } from 'vitest'
6 +import { mount } from '@vue/test-utils'
7 +import PrimaryButton from '@/components/PrimaryButton.vue'
8 +
9 +describe('PrimaryButton', () => {
10 + it('should render button with text', () => {
11 + const wrapper = mount(PrimaryButton, {
12 + props: {
13 + text: '点击按钮',
14 + },
15 + })
16 +
17 + expect(wrapper.text()).toContain('点击按钮')
18 + })
19 +
20 + it('should emit click event when clicked', async () => {
21 + const wrapper = mount(PrimaryButton, {
22 + props: {
23 + text: '点击',
24 + },
25 + })
26 +
27 + await wrapper.trigger('click')
28 + expect(wrapper.emitted('click')).toBeTruthy()
29 + })
30 +
31 + it('should be disabled when disabled prop is true', () => {
32 + const wrapper = mount(PrimaryButton, {
33 + props: {
34 + text: '点击',
35 + disabled: true,
36 + },
37 + })
38 +
39 + const button = wrapper.find('button')
40 + expect(button.attributes('disabled')).toBeDefined()
41 + })
42 +})
1 +/**
2 + * Vitest 测试环境设置
3 + */
4 +
5 +import { vi } from 'vitest'
6 +import { config } from '@vue/test-utils'
7 +
8 +// 全局 Mock
9 +config.global.mocks = {
10 + $t: key => key, // i18n Mock
11 + $router: {
12 + push: vi.fn(),
13 + replace: vi.fn(),
14 + },
15 +}
16 +
17 +// Mock localStorage
18 +const localStorageMock = {
19 + getItem: vi.fn(),
20 + setItem: vi.fn(),
21 + removeItem: vi.fn(),
22 + clear: vi.fn(),
23 +}
24 +global.localStorage = localStorageMock
25 +
26 +// Mock Taro API
27 +global.Taro = {
28 + getStorage: vi.fn(),
29 + setStorage: vi.fn(),
30 + removeStorage: vi.fn(),
31 + getStorageSync: vi.fn(() => ''),
32 + setStorageSync: vi.fn(),
33 + removeStorageSync: vi.fn(),
34 + navigateTo: vi.fn(),
35 + redirectTo: vi.fn(),
36 + switchTab: vi.fn(),
37 + navigateBack: vi.fn(),
38 + showToast: vi.fn(),
39 + showLoading: vi.fn(),
40 + hideLoading: vi.fn(),
41 + showModal: vi.fn(),
42 + showActionSheet: vi.fn(),
43 + createSelectorQuery: vi.fn(() => ({
44 + select: vi.fn().mockReturnThis(),
45 + exec: vi.fn(callback => callback([])),
46 + })),
47 + request: vi.fn(),
48 + getLocation: vi.fn(),
49 + chooseImage: vi.fn(),
50 + previewImage: vi.fn(),
51 + uploadFile: vi.fn(),
52 + downloadFile: vi.fn(),
53 + login: vi.fn(),
54 + getUserInfo: vi.fn(),
55 + openSetting: vi.fn(),
56 + getSystemInfo: vi.fn(),
57 + getSystemInfoSync: vi.fn(() => ({
58 + model: 'iPhone',
59 + system: 'iOS 14.0',
60 + platform: 'ios',
61 + })),
62 +}
63 +
64 +// Mock wx API
65 +global.wx = {
66 + getStorageSync: vi.fn(() => ''),
67 + setStorageSync: vi.fn(),
68 + removeStorageSync: vi.fn(),
69 + request: vi.fn(),
70 + login: vi.fn(),
71 + getUserInfo: vi.fn(),
72 +}
73 +
74 +// Mock axios
75 +vi.mock('axios-miniprogram', () => ({
76 + default: {
77 + create: vi.fn(() => ({
78 + interceptors: {
79 + request: { use: vi.fn() },
80 + response: { use: vi.fn() },
81 + },
82 + get: vi.fn(),
83 + post: vi.fn(),
84 + })),
85 + },
86 +}))
1 +import { defineConfig } from 'vitest/config'
2 +import vue from '@vitejs/plugin-vue'
3 +import { resolve } from 'path'
4 +
5 +export default defineConfig({
6 + plugins: [vue()],
7 + test: {
8 + // 全局 API(describe, it, expect)
9 + globals: true,
10 + // 运行环境
11 + environment: 'jsdom',
12 + // 设置文件
13 + setupFiles: ['./test/setup.js'],
14 + // 覆盖率配置
15 + coverage: {
16 + provider: 'v8',
17 + reporter: ['text', 'json', 'html'],
18 + // 排除文件
19 + exclude: [
20 + 'node_modules/',
21 + 'test/',
22 + 'dist/',
23 + '**/*.test.js',
24 + '**/*.spec.js',
25 + '**/*.config.js',
26 + 'src/app.js',
27 + 'src/app.config.js',
28 + ],
29 + },
30 + // 别名配置(与 Taro 配置保持一致)
31 + alias: {
32 + '@/utils': resolve(__dirname, 'src/utils'),
33 + '@/components': resolve(__dirname, 'src/components'),
34 + '@/images': resolve(__dirname, 'src/assets/images'),
35 + '@/assets': resolve(__dirname, 'src/assets'),
36 + '@/composables': resolve(__dirname, 'src/composables'),
37 + '@/api': resolve(__dirname, 'src/api'),
38 + '@/stores': resolve(__dirname, 'src/stores'),
39 + '@/hooks': resolve(__dirname, 'src/hooks'),
40 + },
41 + },
42 + resolve: {
43 + alias: {
44 + '@/utils': resolve(__dirname, 'src/utils'),
45 + '@/components': resolve(__dirname, 'src/components'),
46 + '@/images': resolve(__dirname, 'src/assets/images'),
47 + '@/assets': resolve(__dirname, 'src/assets'),
48 + '@/composables': resolve(__dirname, 'src/composables'),
49 + '@/api': resolve(__dirname, 'src/api'),
50 + '@/stores': resolve(__dirname, 'src/stores'),
51 + '@/hooks': resolve(__dirname, 'src/hooks'),
52 + },
53 + },
54 +})