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