|
@@ -0,0 +1,979 @@
|
|
|
|
|
+# 故事017.006: 设置与帮助功能实现
|
|
|
|
|
+
|
|
|
|
|
+## 元信息
|
|
|
|
|
+- **史诗**: 017 - 人才小程序功能实现
|
|
|
|
|
+- **优先级**: P1 - 核心功能
|
|
|
|
|
+- **状态**: Approved
|
|
|
|
|
+- **创建日期**: 2025-12-28
|
|
|
|
|
+- **负责人**: 开发团队
|
|
|
|
|
+
|
|
|
|
|
+## 故事描述
|
|
|
|
|
+
|
|
|
|
|
+**作为** 人才小程序开发者,
|
|
|
|
|
+**我想要** 实现设置页和帮助功能,
|
|
|
|
|
+**以便** 人才用户能够管理个人信息、查看帮助文档、修改账号设置和退出登录。
|
|
|
|
|
+
|
|
|
|
|
+### 背景
|
|
|
|
|
+
|
|
|
|
|
+**现有系统状态:**
|
|
|
|
|
+- 故事017.001已完成rencai mini ui包基础框架搭建
|
|
|
|
|
+- 故事017.002已完成登录与首页实现
|
|
|
|
|
+- 故事017.003已完成个人信息功能实现
|
|
|
|
|
+- 故事017.004已完成考勤记录功能实现
|
|
|
|
|
+- 故事017.005已完成就业信息功能实现
|
|
|
|
|
+- `@d8d/rencai-settings-ui`包基础框架已就绪
|
|
|
|
|
+- API客户端文件已创建(`src/api/index.ts`)
|
|
|
|
|
+- mini-talent项目路由结构已配置完成(设置页在底部TabBar中)
|
|
|
|
|
+
|
|
|
|
|
+**原型设计参考:**
|
|
|
|
|
+- `docs/小程序原型/rencai.html` 提供了设置页的完整原型设计
|
|
|
|
|
+- 设置页 (原型行770-906): 个人信息摘要、功能入口、帮助与支持、退出登录
|
|
|
|
|
+
|
|
|
|
|
+**技术集成模式:**
|
|
|
|
|
+- 参照`yongren-settings-ui`的实现模式(如果存在)
|
|
|
|
|
+- 设置页使用`@d8d/rencai-settings-ui`包
|
|
|
|
|
+- API调用逻辑封装在页面组件内部
|
|
|
|
|
+- **TabBar页面**:使用Navbar无返回按钮(参照故事017.012规范)
|
|
|
|
|
+
|
|
|
|
|
+**依赖API (史诗015):**
|
|
|
|
|
+- ✅ 退出登录API (POST /api/v1/rencai/auth/logout) - 已完成
|
|
|
|
|
+- ✅ 登录日志查询API (GET /api/v1/rencai/auth/login-logs) - 已完成
|
|
|
|
|
+- 📋 帮助与支持API - P2优先级,当前使用静态内容
|
|
|
|
|
+- 📋 用户协议API - P2优先级,当前使用静态内容
|
|
|
|
|
+- 📋 隐私政策API - P2优先级,当前使用静态内容
|
|
|
|
|
+- 📋 前端可直接使用退出登录和登录日志API,帮助内容使用静态展示
|
|
|
|
|
+
|
|
|
|
|
+**依赖故事完成状态:**
|
|
|
|
|
+- ✅ 故事017.001: rencai mini ui包基础框架搭建完成
|
|
|
|
|
+- ✅ 故事017.002: 登录与首页实现完成
|
|
|
|
|
+- ✅ 故事017.003: 个人信息功能实现完成
|
|
|
|
|
+- ✅ 故事017.004: 考勤记录功能实现完成
|
|
|
|
|
+- ✅ 故事017.005: 就业信息功能实现完成
|
|
|
|
|
+- ✅ 故事017.012: 统一Navbar导航栏组件规范完成
|
|
|
|
|
+
|
|
|
|
|
+## 验收标准
|
|
|
|
|
+
|
|
|
|
|
+### 个人信息摘要展示
|
|
|
|
|
+- [ ] 设置页顶部展示个人信息摘要(姓名、残疾类型)
|
|
|
|
|
+- [ ] 显示3列统计数据:本月出勤、累计出勤、本月薪资
|
|
|
|
|
+- [ ] 圆形头像显示用户姓名首字
|
|
|
|
|
+- [ ] 头像样式:蓝色背景 + 白色文字
|
|
|
|
|
+
|
|
|
|
|
+### 功能入口列表
|
|
|
|
|
+- [ ] 修改个人信息入口可用(带图标和右箭头)
|
|
|
|
|
+- [ ] 账号与安全入口可用(带图标和右箭头)
|
|
|
|
|
+- [ ] 消息通知设置入口可用(带图标和右箭头)
|
|
|
|
|
+- [ ] 每个入口使用圆形图标背景(蓝色、绿色、紫色)
|
|
|
|
|
+
|
|
|
|
|
+### 帮助与支持入口
|
|
|
|
|
+- [ ] 帮助中心入口可用(带图标和右箭头)
|
|
|
|
|
+- [ ] 用户协议入口可用(带图标和右箭头)
|
|
|
|
|
+- [ ] 隐私政策入口可用(带图标和右箭头)
|
|
|
|
|
+- [ ] 每个入口使用圆形图标背景(黄色、靛蓝、粉色)
|
|
|
|
|
+
|
|
|
|
|
+### 退出登录功能
|
|
|
|
|
+- [ ] 退出登录按钮可用(红色背景图标)
|
|
|
|
|
+- [ ] 点击退出登录调用退出登录API
|
|
|
|
|
+- [ ] 退出成功后清除本地token和用户状态
|
|
|
|
|
+- [ ] 退出成功后跳转到登录页
|
|
|
|
|
+
|
|
|
|
|
+### 登录日志查询
|
|
|
|
|
+- [ ] 登录日志查询接口返回个人的登录日志记录
|
|
|
|
|
+- [ ] 在账号与安全页展示登录日志(时间、设备、IP地址)
|
|
|
|
|
+- [ ] 日志按时间倒序排列
|
|
|
|
|
+
|
|
|
|
|
+### 页面设计与布局
|
|
|
|
|
+- [ ] 页面设计符合原型标准,移动端体验良好
|
|
|
|
|
+- [ ] 已集成Navbar导航栏组件(**TabBar页面**,无返回按钮)
|
|
|
|
|
+- [ ] 所有功能入口列表项使用统一的hover样式
|
|
|
|
|
+
|
|
|
|
|
+## 任务列表
|
|
|
|
|
+
|
|
|
|
|
+### 任务1: 创建设置页主页面组件 (AC: 个人信息摘要展示, 页面设计与布局)
|
|
|
|
|
+- [ ] 1.1 在`@d8d/rencai-settings-ui`中实现`SettingsPage`页面组件 (`src/pages/SettingsPage/SettingsPage.tsx`)
|
|
|
|
|
+- [ ] 1.2 创建个人信息摘要组件 (`src/components/UserProfileSummary.tsx`)
|
|
|
|
|
+ - 显示圆形头像(用户姓名首字,蓝色背景)
|
|
|
|
|
+ - 显示用户姓名和残疾类型
|
|
|
|
|
+ - 显示3列统计:本月出勤、累计出勤、本月薪资
|
|
|
|
|
+- [ ] 1.3 集成Navbar导航栏组件(TabBar页面,无返回按钮)
|
|
|
|
|
+- [ ] 1.4 实现数据加载状态(Loading状态)
|
|
|
|
|
+- [ ] 1.5 实现错误处理(数据加载失败时显示错误提示)
|
|
|
|
|
+
|
|
|
|
|
+### 任务2: 实现功能入口列表 (AC: 功能入口列表)
|
|
|
|
|
+- [ ] 2.1 创建功能入口组件 (`src/components/MenuItem.tsx`)
|
|
|
|
|
+ - 圆形图标背景(支持不同颜色)
|
|
|
|
|
+ - 图标使用Heroicons
|
|
|
|
|
+ - 标题文本
|
|
|
|
|
+ - 右箭头指示器
|
|
|
|
|
+- [ ] 2.2 创建功能入口列表组件 (`src/components/MenuSection.tsx`)
|
|
|
|
|
+ - 修改个人信息:蓝色背景,user图标
|
|
|
|
|
+ - 账号与安全:绿色背景,shield图标
|
|
|
|
|
+ - 消息通知设置:紫色背景,bell图标
|
|
|
|
|
+- [ ] 2.3 实现入口点击事件处理(预留导航接口)
|
|
|
|
|
+
|
|
|
|
|
+### 任务3: 实现帮助与支持入口 (AC: 帮助与支持入口)
|
|
|
|
|
+- [ ] 3.1 使用MenuSection组件创建帮助与支持入口列表
|
|
|
|
|
+ - 帮助中心:黄色背景,question-circle图标
|
|
|
|
|
+ - 用户协议:靛蓝背景,document-text图标
|
|
|
|
|
+ - 隐私政策:粉色背景,lock-closed图标
|
|
|
|
|
+- [ ] 3.2 实现入口点击事件处理(预留导航接口)
|
|
|
|
|
+
|
|
|
|
|
+### 任务4: 实现退出登录功能 (AC: 退出登录功能)
|
|
|
|
|
+- [ ] 4.1 创建退出登录按钮组件 (`src/components/LogoutButton.tsx`)
|
|
|
|
|
+ - 红色背景图标
|
|
|
|
|
+ - "退出登录"文本
|
|
|
|
|
+- [ ] 4.2 集成退出登录API(使用React Query的useMutation)
|
|
|
|
|
+ - 调用`POST /api/v1/rencai/auth/logout`
|
|
|
|
|
+ - 成功后清除本地token
|
|
|
|
|
+ - 成功后清除用户状态
|
|
|
|
|
+ - 成功后跳转到登录页
|
|
|
|
|
+- [ ] 4.3 实现退出确认对话框(可选,提升用户体验)
|
|
|
|
|
+
|
|
|
|
|
+### 任务5: 实现账号与安全页(登录日志) (AC: 登录日志查询)
|
|
|
|
|
+- [ ] 5.1 创建账号与安全页组件 (`src/pages/AccountSecurityPage/AccountSecurityPage.tsx`)
|
|
|
|
|
+- [ ] 5.2 创建登录日志列表组件 (`src/components/LoginLogsList.tsx`)
|
|
|
|
|
+ - 显示登录时间
|
|
|
|
|
+ - 显示设备信息
|
|
|
|
|
+ - 显示IP地址
|
|
|
|
|
+ - 按时间倒序排列
|
|
|
|
|
+- [ ] 5.3 集成登录日志查询API(使用React Query的useQuery)
|
|
|
|
|
+ - 调用`GET /api/v1/rencai/auth/login-logs`
|
|
|
|
|
+ - 使用分页加载(可选)
|
|
|
|
|
+- [ ] 5.4 添加Navbar导航栏(非TabBar页面,带返回按钮)
|
|
|
|
|
+
|
|
|
|
|
+### 任务6: 更新mini-talent页面集成 (AC: 集成与兼容性)
|
|
|
|
|
+- [ ] 6.1 更新`mini-talent/src/pages/settings/index.tsx`:
|
|
|
|
|
+ - 从`@d8d/rencai-settings-ui/pages/SettingsPage/SettingsPage`导入SettingsPage组件
|
|
|
|
|
+ - 用AuthProvider包装页面
|
|
|
|
|
+ - 添加认证检查(未登录跳转到登录页)
|
|
|
|
|
+ - 导出SettingsPage组件
|
|
|
|
|
+- [ ] 6.2 添加账号与安全页路由(如果需要)
|
|
|
|
|
+- [ ] 6.3 验证底部TabBar"更多"入口能正确跳转到设置页
|
|
|
|
|
+
|
|
|
|
|
+### 任务7: 实现页面样式和移动端适配 (AC: 页面设计与布局)
|
|
|
|
|
+- [ ] 7.1 参照原型设计实现设置页样式(原型行770-906)
|
|
|
|
|
+ - 个人信息摘要样式(圆形头像、3列统计)
|
|
|
|
|
+ - 功能入口列表样式(圆形图标背景、右箭头)
|
|
|
|
|
+ - 退出登录按钮样式(红色图标)
|
|
|
|
|
+- [ ] 7.2 确保页面设计符合移动端规范:
|
|
|
|
|
+ - 宽度参考: 375px
|
|
|
|
|
+ - 圆角规范: 12px (列表项)
|
|
|
|
|
+ - 颜色主题: 蓝色为主色
|
|
|
|
|
+ - 字体规范: 标题18-24px, 正文14px, 小字12px
|
|
|
|
|
+ - 图标背景色: 蓝/绿/紫/黄/靛蓝/粉/红
|
|
|
|
|
+- [ ] 7.3 使用正确的组件:设置页使用**TabBar页面**(无返回按钮的Navbar)
|
|
|
|
|
+
|
|
|
|
|
+### 任务8: 编写测试 (AC: 集成与兼容性)
|
|
|
|
|
+- [ ] 8.1 为SettingsPage编写组件测试 (`tests/pages/SettingsPage/SettingsPage.test.tsx`)
|
|
|
|
|
+ - 测试组件渲染
|
|
|
|
|
+ - 测试个人信息摘要展示(Mock数据)
|
|
|
|
|
+ - 测试功能入口列表显示
|
|
|
|
|
+ - 测试退出登录功能
|
|
|
|
|
+- [ ] 8.2 为子组件编写单元测试:
|
|
|
|
|
+ - UserProfileSummary.test.tsx
|
|
|
|
|
+ - MenuItem.test.tsx
|
|
|
|
|
+ - LogoutButton.test.tsx
|
|
|
|
|
+ - LoginLogsList.test.tsx(如果实现)
|
|
|
|
|
+- [ ] 8.3 编写集成测试验证现有功能不受影响
|
|
|
|
|
+- [ ] 8.4 运行`pnpm typecheck`确保类型检查通过
|
|
|
|
|
+
|
|
|
|
|
+## 开发者笔记
|
|
|
|
|
+
|
|
|
|
|
+### 前置故事见解
|
|
|
|
|
+
|
|
|
|
|
+**故事017.001完成状态:**
|
|
|
|
|
+- ✅ rencai系列7个UI包基础结构已创建
|
|
|
|
|
+- ✅ `@d8d/rencai-settings-ui`包基础框架已就绪
|
|
|
|
|
+- ✅ API客户端文件已创建(`src/api/index.ts`)
|
|
|
|
|
+- ✅ mini-talent项目路由结构已配置完成
|
|
|
|
|
+- ✅ package.json的exports字段已配置
|
|
|
|
|
+- ✅ Jest测试框架已配置(`jest.config.cjs`)
|
|
|
|
|
+
|
|
|
|
|
+**故事017.002完成状态:**
|
|
|
|
|
+- ✅ 登录页面功能完整,支持人才用户身份证号/残疾证号/手机号密码登录
|
|
|
|
|
+- ✅ 首页/个人主页页面展示个人概览数据
|
|
|
|
|
+- ✅ 认证状态管理正常,token存储和验证可靠
|
|
|
|
|
+- ✅ AuthContext提供登录状态、用户信息和登出方法
|
|
|
|
|
+
|
|
|
|
|
+**故事017.003完成状态:**
|
|
|
|
|
+- ✅ 个人信息页面功能完整
|
|
|
|
|
+- ✅ 数据脱敏工具函数实现(`maskUtils.ts`)
|
|
|
|
|
+- ✅ 证件照片预览功能实现
|
|
|
|
|
+- ✅ 测试框架选择确认:**mini项目使用Jest**(不是Vitest)
|
|
|
|
|
+
|
|
|
|
|
+**故事017.004完成状态:**
|
|
|
|
|
+- ✅ 考勤记录页面功能完整
|
|
|
|
|
+- ✅ 前端模拟数据规范实现
|
|
|
|
|
+- ✅ Taro布局规范确认(使用`flex flex-col`实现垂直布局)
|
|
|
|
|
+- ✅ Navbar导航栏集成规范确认
|
|
|
|
|
+
|
|
|
|
|
+**故事017.005完成状态:**
|
|
|
|
|
+- ✅ 就业信息页面功能完整
|
|
|
|
|
+- ✅ 使用React Query (useQuery) 管理服务端状态
|
|
|
|
|
+- ✅ 使用React Query (useMutation) 处理数据修改操作
|
|
|
|
|
+- ✅ 测试使用真实的QueryClientProvider
|
|
|
|
|
+
|
|
|
|
|
+**故事017.012完成状态:**
|
|
|
|
|
+- ✅ 统一Navbar导航栏组件规范
|
|
|
|
|
+- ✅ TabBar页面使用Navbar无返回按钮(leftIcon="" leftText="")
|
|
|
|
|
+- ✅ **本故事是TabBar页面**:使用Navbar无返回按钮
|
|
|
|
|
+
|
|
|
|
|
+**关键实现经验:**
|
|
|
|
|
+1. API客户端导入路径修正:从相应的后端模块包导入,而不是`@d8d/server`
|
|
|
|
|
+2. 页面文件简化设计:采用"薄包装层",仅导入并导出组件
|
|
|
|
|
+3. 复用现有共享组件:StatusBar、PageContainer、Navbar使用`@d8d/mini-shared-ui-components`中的实现
|
|
|
|
|
+4. 测试框架选择:**mini项目使用Jest**(不是Vitest)
|
|
|
|
|
+5. **使用React Query管理服务端状态**,不要使用useState + useEffect手动管理
|
|
|
|
|
+6. **本故事是TabBar页面**:需要使用Navbar无返回按钮
|
|
|
|
|
+
|
|
|
|
|
+### 技术栈要求
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/tech-stack.md](../architecture/tech-stack.md)
|
|
|
|
|
+
|
|
|
|
|
+**运行时和框架:**
|
|
|
|
|
+- **Node.js**: 20.18.3
|
|
|
|
|
+- **Hono**: 4.8.5 (RPC客户端)
|
|
|
|
|
+- **React**: 19.1.0 (UI组件)
|
|
|
|
|
+- **Taro**: 4.1.4 (小程序框架)
|
|
|
|
|
+- **Tailwind CSS**: 4.1.11 (样式)
|
|
|
|
|
+- **React Query**: 5.83.0 (服务端状态管理)
|
|
|
|
|
+
|
|
|
|
|
+**测试框架:**
|
|
|
|
|
+- **Jest**: 30.2.0 (mini项目使用Jest,不是Vitest!)
|
|
|
|
|
+- **ts-jest**: 29.4.5 (TypeScript预处理器)
|
|
|
|
|
+- **@testing-library/react**: 16.3.0 (React组件测试)
|
|
|
|
|
+- **@d8d/mini-testing-utils**: workspace包 (Taro小程序测试工具)
|
|
|
|
|
+
|
|
|
|
|
+**重要**: **mini项目使用Jest测试框架**,与web应用使用的Vitest不同。
|
|
|
|
|
+
|
|
|
|
|
+### UI包开发规范
|
|
|
|
|
+
|
|
|
|
|
+**来源**:
|
|
|
|
|
+- [architecture/mini-ui-package-standards.md](../architecture/mini-ui-package-standards.md) - **Mini UI包专用规范(本文档必须遵循)**
|
|
|
|
|
+
|
|
|
|
|
+**关键规范要求:**
|
|
|
|
|
+
|
|
|
|
|
+#### 1. UI包内部导入规范
|
|
|
|
|
+**重要**: UI包内部导入必须使用相对路径,不要使用别名。
|
|
|
|
|
+
|
|
|
|
|
+**正确示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: 使用相对路径导入同一包内的模块
|
|
|
|
|
+import { UserProfileSummary } from '../../components/UserProfileSummary'
|
|
|
|
|
+import { MenuItem } from '../components/MenuItem'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**错误示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ❌ 错误: 不要使用别名导入UI包内部的模块
|
|
|
|
|
+import { UserProfileSummary } from '@/components/UserProfileSummary'
|
|
|
|
|
+import { MenuItem } from '@/components/MenuItem'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 2. package.json exports配置规范
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "exports": {
|
|
|
|
|
+ ".": {
|
|
|
|
|
+ "types": "./dist/src/index.d.ts",
|
|
|
|
|
+ "import": "./dist/src/index.js",
|
|
|
|
|
+ "require": "./dist/src/index.js"
|
|
|
|
|
+ },
|
|
|
|
|
+ "./api": {
|
|
|
|
|
+ "types": "./src/api/index.ts",
|
|
|
|
|
+ "import": "./src/api/index.ts",
|
|
|
|
|
+ "require": "./src/api/index.ts"
|
|
|
|
|
+ },
|
|
|
|
|
+ "./pages/SettingsPage/SettingsPage": {
|
|
|
|
|
+ "types": "./src/pages/SettingsPage/SettingsPage.tsx",
|
|
|
|
|
+ "import": "./dist/src/pages/SettingsPage/SettingsPage.js",
|
|
|
|
|
+ "require": "./dist/src/pages/SettingsPage/SettingsPage.js"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 3. React Query数据获取规范
|
|
|
|
|
+**重要**: Mini UI包必须使用React Query (`@tanstack/react-query`) 管理服务端状态。
|
|
|
|
|
+
|
|
|
|
|
+**数据查询 (useQuery)**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { useQuery } from '@tanstack/react-query'
|
|
|
|
|
+import { apiClient } from '../../api'
|
|
|
|
|
+
|
|
|
|
|
+const MyPage: React.FC = () => {
|
|
|
|
|
+ // ✅ 正确: 使用React Query
|
|
|
|
|
+ const { data, isLoading, error } = useQuery({
|
|
|
|
|
+ queryKey: ['login-logs'],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const res = await apiClient.auth['login-logs'].$get()
|
|
|
|
|
+ if (!res.ok) {
|
|
|
|
|
+ throw new Error('获取登录日志失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ return await res.json()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (isLoading) return <div>加载中...</div>
|
|
|
|
|
+ if (error) return <div>加载失败</div>
|
|
|
|
|
+
|
|
|
|
|
+ return <div>{/* 渲染数据 */}</div>
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**数据修改 (useMutation)**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
|
|
+import { apiClient } from '../../api'
|
|
|
|
|
+
|
|
|
|
|
+const SettingsPage: React.FC = () => {
|
|
|
|
|
+ const queryClient = useQueryClient()
|
|
|
|
|
+ const navigate = useNavigate()
|
|
|
|
|
+
|
|
|
|
|
+ // 退出登录mutation
|
|
|
|
|
+ const logoutMutation = useMutation({
|
|
|
|
|
+ mutationFn: async () => {
|
|
|
|
|
+ const res = await apiClient.auth.logout.$post()
|
|
|
|
|
+ if (!res.ok) {
|
|
|
|
|
+ throw new Error('退出登录失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ return await res.json()
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
|
+ // 清除查询缓存
|
|
|
|
|
+ queryClient.clear()
|
|
|
|
|
+ // 清除本地存储
|
|
|
|
|
+ localStorage.removeItem('token')
|
|
|
|
|
+ localStorage.removeItem('user')
|
|
|
|
|
+ // 跳转到登录页
|
|
|
|
|
+ Taro.reLaunch({ url: '/pages/login/index' })
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error) => {
|
|
|
|
|
+ Taro.showToast({
|
|
|
|
|
+ title: error.message,
|
|
|
|
|
+ icon: 'none'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const handleLogout = () => {
|
|
|
|
|
+ Taro.showModal({
|
|
|
|
|
+ title: '提示',
|
|
|
|
|
+ content: '确定要退出登录吗?',
|
|
|
|
|
+ success: (res) => {
|
|
|
|
|
+ if (res.confirm) {
|
|
|
|
|
+ logoutMutation.mutate()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View onClick={handleLogout}>
|
|
|
|
|
+ <Text>{logoutMutation.isPending ? '退出中...' : '退出登录'}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 4. Jest配置规范
|
|
|
|
|
+每个UI包必须创建`jest.config.cjs`配置文件,参照`rencai-personal-info-ui/jest.config.cjs`:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+module.exports = {
|
|
|
|
|
+ preset: 'ts-jest',
|
|
|
|
|
+ testEnvironment: 'jsdom',
|
|
|
|
|
+ setupFilesAfterEnv: ['@d8d/mini-testing-utils/setup'],
|
|
|
|
|
+ moduleNameMapper: {
|
|
|
|
|
+ '^@/(.*)$': '<rootDir>/src/$1',
|
|
|
|
|
+ '^~/(.*)$': '<rootDir>/tests/$1',
|
|
|
|
|
+ '^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts',
|
|
|
|
|
+ '\\.(css|less|scss|sass)$': '@d8d/mini-testing-utils/testing/style-mock.js',
|
|
|
|
|
+ '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
|
|
|
|
+ '@d8d/mini-testing-utils/testing/file-mock.js'
|
|
|
|
|
+ },
|
|
|
|
|
+ testMatch: [
|
|
|
|
|
+ '<rootDir>/tests/**/*.spec.{ts,tsx}',
|
|
|
|
|
+ '<rootDir>/tests/**/*.test.{ts,tsx}'
|
|
|
|
|
+ ],
|
|
|
|
|
+ collectCoverageFrom: [
|
|
|
|
|
+ 'src/**/*.{ts,tsx}',
|
|
|
|
|
+ '!src/**/*.d.ts',
|
|
|
|
|
+ '!src/**/index.{ts,tsx}',
|
|
|
|
|
+ '!src/**/*.stories.{ts,tsx}'
|
|
|
|
|
+ ],
|
|
|
|
|
+ coverageDirectory: 'coverage',
|
|
|
|
|
+ coverageReporters: ['text', 'lcov', 'html'],
|
|
|
|
|
+ testPathIgnorePatterns: [
|
|
|
|
|
+ '/node_modules/',
|
|
|
|
|
+ '/dist/',
|
|
|
|
|
+ '/coverage/'
|
|
|
|
|
+ ],
|
|
|
|
|
+ transform: {
|
|
|
|
|
+ '^.+\\.(ts|tsx)$': 'ts-jest',
|
|
|
|
|
+ '^.+\\.(js|jsx)$': 'babel-jest'
|
|
|
|
|
+ },
|
|
|
|
|
+ transformIgnorePatterns: [
|
|
|
|
|
+ '/node_modules/(?!(swiper|@tarojs)/)'
|
|
|
|
|
+ ],
|
|
|
|
|
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 原型设计参考
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [docs/小程序原型/rencai.html](../小程序原型/rencai.html)
|
|
|
|
|
+
|
|
|
|
|
+**设置页** (原型行770-906):
|
|
|
|
|
+
|
|
|
|
|
+#### 1. 个人信息摘要
|
|
|
|
|
+```html
|
|
|
|
|
+<!-- 个人信息 -->
|
|
|
|
|
+<div class="p-4 border-b border-gray-200">
|
|
|
|
|
+ <div class="flex items-center mb-4">
|
|
|
|
|
+ <div class="w-16 h-16 rounded-full mr-4 bg-blue-500 flex items-center justify-center text-white text-xl font-bold">
|
|
|
|
|
+ 张
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h3 class="font-semibold text-gray-800">张明</h3>
|
|
|
|
|
+ <p class="text-sm text-gray-500">肢体残疾 · 三级</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="grid grid-cols-3 gap-3 text-center">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="text-xl font-bold text-gray-800">28</p>
|
|
|
|
|
+ <p class="text-xs text-gray-500">本月出勤</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="text-xl font-bold text-gray-800">156</p>
|
|
|
|
|
+ <p class="text-xs text-gray-500">累计出勤</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="text-xl font-bold text-gray-800">¥4,800</p>
|
|
|
|
|
+ <p class="text-xs text-gray-500">本月薪资</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**移动端设计规范**:
|
|
|
|
|
+- 圆形头像:64x64px (`w-16 h-16`),蓝色背景 (`bg-blue-500`)
|
|
|
|
|
+- 头像文字:白色,大号字体 (`text-xl font-bold`)
|
|
|
|
|
+- 3列统计:等宽网格 (`grid grid-cols-3 gap-3`)
|
|
|
|
|
+- 统计数字:大号加粗 (`text-xl font-bold`)
|
|
|
|
|
+- 统计标签:小号灰色 (`text-xs text-gray-500`)
|
|
|
|
|
+
|
|
|
|
|
+#### 2. 功能入口列表
|
|
|
|
|
+```html
|
|
|
|
|
+<!-- 功能列表 -->
|
|
|
|
|
+<div class="space-y-1 mb-6">
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-user-edit text-blue-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">修改个人信息</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <i class="fas fa-chevron-right text-gray-400"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-shield-alt text-green-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">账号与安全</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <i class="fas fa-chevron-right text-gray-400"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-bell text-purple-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">消息通知设置</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <i class="fas fa-chevron-right text-gray-400"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**移动端设计规范**:
|
|
|
|
|
+- 列表项间距:4px (`space-y-1`)
|
|
|
|
|
+- 列表项内边距:12px (`p-3`)
|
|
|
|
|
+- 圆角:8px (`rounded-lg`)
|
|
|
|
|
+- 圆形图标:40x40px (`w-10 h-10`)
|
|
|
|
|
+- 图标背景色:蓝/绿/紫
|
|
|
|
|
+- 右箭头:灰色 (`text-gray-400`)
|
|
|
|
|
+
|
|
|
|
|
+#### 3. 帮助与支持入口
|
|
|
|
|
+```html
|
|
|
|
|
+<!-- 帮助与支持 -->
|
|
|
|
|
+<div class="space-y-1 mb-6">
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-yellow-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-question-circle text-yellow-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">帮助中心</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <i class="fas fa-chevron-right text-gray-400"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-file-alt text-indigo-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">用户协议</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <i class="fas fa-chevron-right text-gray-400"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-pink-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-lock text-pink-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">隐私政策</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <i class="fas fa-chevron-right text-gray-400"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**图标背景色**:
|
|
|
|
|
+- 帮助中心:黄色 (`bg-yellow-100 text-yellow-500`)
|
|
|
|
|
+- 用户协议:靛蓝 (`bg-indigo-100 text-indigo-500`)
|
|
|
|
|
+- 隐私政策:粉色 (`bg-pink-100 text-pink-500`)
|
|
|
|
|
+
|
|
|
|
|
+#### 4. 退出登录按钮
|
|
|
|
|
+```html
|
|
|
|
|
+<!-- 退出登录 -->
|
|
|
|
|
+<div class="space-y-1">
|
|
|
|
|
+ <div class="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
|
+ <div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <i class="fas fa-sign-out-alt text-red-500"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-gray-700">退出登录</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**退出登录样式**:
|
|
|
|
|
+- 图标背景:红色 (`bg-red-100 text-red-500`)
|
|
|
|
|
+
|
|
|
|
|
+### Navbar导航栏集成规范
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [docs/stories/017.012.story.md](../stories/017.012.story.md)
|
|
|
|
|
+
|
|
|
|
|
+**TabBar页面规范(设置页属于此类):**
|
|
|
|
|
+- 使用`leftIcon=""`和`leftText=""`隐藏返回按钮
|
|
|
|
|
+- 参照yongren-dashboard-ui:139-148
|
|
|
|
|
+- Navbar组件来源: `@d8d/mini-shared-ui-components/components/navbar`
|
|
|
|
|
+
|
|
|
|
|
+**Navbar集成示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'
|
|
|
|
|
+import { View, ScrollView } from '@tarojs/components'
|
|
|
|
|
+
|
|
|
|
|
+export function SettingsPage() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View className="h-screen bg-gray-100">
|
|
|
|
|
+ {/* Navbar导航栏 - TabBar页面无返回按钮 */}
|
|
|
|
|
+ <Navbar
|
|
|
|
|
+ title="设置"
|
|
|
|
|
+ leftIcon=""
|
|
|
|
|
+ leftText=""
|
|
|
|
|
+ onClickLeft={() => {}}
|
|
|
|
|
+ placeholder
|
|
|
|
|
+ fixed
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ {/* 页面内容 */}
|
|
|
|
|
+ <ScrollView scrollY className="h-full">
|
|
|
|
|
+ {/* 页面内容 */}
|
|
|
|
|
+ </ScrollView>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键配置**:
|
|
|
|
|
+- `leftIcon=""`: 隐藏返回按钮图标
|
|
|
|
|
+- `leftText=""`: 隐藏返回按钮文字
|
|
|
|
|
+- `placeholder`: 添加占位空间,避免内容被Navbar遮挡
|
|
|
|
|
+- `fixed`: 固定在顶部
|
|
|
|
|
+
|
|
|
|
|
+### Taro小程序布局规范
|
|
|
|
|
+
|
|
|
|
|
+**重要**: 在Taro小程序中,`<View>` 组件内的子元素默认是**横向布局**(`flex-row`),需要显式添加 `flex flex-col` 类才能实现**垂直布局**。
|
|
|
|
|
+
|
|
|
|
|
+**正确示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: 使用 flex flex-col 实现垂直布局
|
|
|
|
|
+<View className="flex flex-col">
|
|
|
|
|
+ <Text>姓名: 张三</Text>
|
|
|
|
|
+ <Text>残疾类型: 肢体残疾 · 三级</Text>
|
|
|
|
|
+</View>
|
|
|
|
|
+
|
|
|
|
|
+// ❌ 错误: 缺少 flex flex-col,子元素会横向排列
|
|
|
|
|
+<View>
|
|
|
|
|
+ <Text>姓名: 张三</Text>
|
|
|
|
|
+ <Text>残疾类型: 肢体残疾 · 三级</Text>
|
|
|
|
|
+</View>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 图标使用规范
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/mini-ui-package-standards.md](../architecture/mini-ui-package-standards.md#图标使用规范)
|
|
|
|
|
+
|
|
|
|
|
+**重要**: **不要使用emoji**,必须使用Heroicons图标类。
|
|
|
|
|
+
|
|
|
|
|
+**图标类命名格式**: `i-heroicons-{图标名称}-{尺寸}-{样式}`
|
|
|
|
|
+
|
|
|
|
|
+**本故事需要的图标**:
|
|
|
|
|
+- `user-20-solid` - 修改个人信息(替代fas fa-user-edit)
|
|
|
|
|
+- `shield-check-20-solid` - 账号与安全(替代fas fa-shield-alt)
|
|
|
|
|
+- `bell-20-solid` - 消息通知设置
|
|
|
|
|
+- `question-mark-circle-20-solid` - 帮助中心(替代fas fa-question-circle)
|
|
|
|
|
+- `document-text-20-solid` - 用户协议(替代fas fa-file-alt)
|
|
|
|
|
+- `lock-closed-20-solid` - 隐私政策
|
|
|
|
|
+- `arrow-right-on-rectangle-20-solid` - 退出登录(替代fas fa-sign-out-alt)
|
|
|
|
|
+- `chevron-right-20-solid` - 右箭头
|
|
|
|
|
+
|
|
|
|
|
+**正确示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: 使用Heroicons图标类
|
|
|
|
|
+<View className="i-heroicons-user-20-solid w-5 h-5 text-blue-500" />
|
|
|
|
|
+
|
|
|
|
|
+// ❌ 错误: 使用emoji
|
|
|
|
|
+<Text>👤</Text>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**功能入口图标示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { View } from '@tarojs/components'
|
|
|
|
|
+
|
|
|
|
|
+// 修改个人信息 - 蓝色
|
|
|
|
|
+<View className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <View className="i-heroicons-user-20-solid text-blue-500 w-5 h-5" />
|
|
|
|
|
+</View>
|
|
|
|
|
+
|
|
|
|
|
+// 账号与安全 - 绿色
|
|
|
|
|
+<View className="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <View className="i-heroicons-shield-check-20-solid text-green-500 w-5 h-5" />
|
|
|
|
|
+</View>
|
|
|
|
|
+
|
|
|
|
|
+// 消息通知设置 - 紫色
|
|
|
|
|
+<View className="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center mr-3">
|
|
|
|
|
+ <View className="i-heroicons-bell-20-solid text-purple-500 w-5 h-5" />
|
|
|
|
|
+</View>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构指南
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/source-tree.md](../architecture/source-tree.md)
|
|
|
|
|
+
|
|
|
|
|
+**mini-talent项目结构**:
|
|
|
|
|
+```
|
|
|
|
|
+mini-talent/ # 人才小程序项目
|
|
|
|
|
+├── src/
|
|
|
|
|
+│ ├── app.tsx # 小程序入口
|
|
|
|
|
+│ ├── app.config.ts # 小程序配置
|
|
|
|
|
+│ ├── app.css # 全局样式
|
|
|
|
|
+│ ├── pages/ # 页面目录
|
|
|
|
|
+│ │ ├── login/ # 登录页
|
|
|
|
|
+│ │ ├── index/ # 首页/个人主页
|
|
|
|
|
+│ │ ├── attendance/ # 考勤记录页
|
|
|
|
|
+│ │ ├── personal-info/ # 个人信息页
|
|
|
|
|
+│ │ ├── employment/ # 就业信息页
|
|
|
|
|
+│ │ └── settings/ # 设置页 (TabBar) - 本故事
|
|
|
|
|
+│ │ └── index.tsx
|
|
|
|
|
+├── package.json
|
|
|
|
|
+├── jest.config.js
|
|
|
|
|
+└── tsconfig.json
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**mini-ui-packages目录结构**:
|
|
|
|
|
+```
|
|
|
|
|
+mini-ui-packages/
|
|
|
|
|
+├── rencai-settings-ui/ # 人才设置UI包
|
|
|
|
|
+│ ├── src/
|
|
|
|
|
+│ │ ├── api/
|
|
|
|
|
+│ │ │ ├── settingsClient.ts
|
|
|
|
|
+│ │ │ └── index.ts
|
|
|
|
|
+│ │ ├── pages/
|
|
|
|
|
+│ │ │ └── SettingsPage/
|
|
|
|
|
+│ │ │ ├── SettingsPage.tsx
|
|
|
|
|
+│ │ │ └── index.ts (可选)
|
|
|
|
|
+│ │ ├── components/ # UI组件
|
|
|
|
|
+│ │ │ ├── UserProfileSummary.tsx # 个人信息摘要
|
|
|
|
|
+│ │ │ ├── MenuItem.tsx # 菜单项
|
|
|
|
|
+│ │ │ ├── MenuSection.tsx # 菜单分组
|
|
|
|
|
+│ │ │ ├── LogoutButton.tsx # 退出登录按钮
|
|
|
|
|
+│ │ │ └── LoginLogsList.tsx # 登录日志列表(可选)
|
|
|
|
|
+│ │ ├── types/
|
|
|
|
|
+│ │ │ └── settings.ts # 类型定义
|
|
|
|
|
+│ │ └── index.ts
|
|
|
|
|
+│ ├── tests/ # 测试文件
|
|
|
|
|
+│ │ ├── pages/
|
|
|
|
|
+│ │ │ └── SettingsPage/
|
|
|
|
|
+│ │ │ └── SettingsPage.test.tsx
|
|
|
|
|
+│ │ └── components/
|
|
|
|
|
+│ │ ├── UserProfileSummary.test.tsx
|
|
|
|
|
+│ │ ├── MenuItem.test.tsx
|
|
|
|
|
+│ │ ├── LogoutButton.test.tsx
|
|
|
|
|
+│ │ └── LoginLogsList.test.tsx
|
|
|
|
|
+│ ├── package.json
|
|
|
|
|
+│ ├── jest.config.cjs
|
|
|
|
|
+│ └── tsconfig.json
|
|
|
|
|
+└── mini-shared-ui-components/ # 通用小程序UI组件
|
|
|
|
|
+ ├── src/
|
|
|
|
|
+ │ └── components/
|
|
|
|
|
+ │ ├── status-bar.tsx
|
|
|
|
|
+ │ ├── page-container.tsx
|
|
|
|
|
+ │ ├── navbar.tsx
|
|
|
|
|
+ │ └── tab-bar.tsx
|
|
|
|
|
+ └── ...
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**mini-talent页面导入方式**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// mini-talent/src/pages/settings/index.tsx
|
|
|
|
|
+import SettingsPage from '@d8d/rencai-settings-ui/pages/SettingsPage/SettingsPage'
|
|
|
|
|
+import { AuthContextProvider, useAuth } from '@d8d/rencai-auth-ui/utils'
|
|
|
|
|
+
|
|
|
|
|
+function Settings() {
|
|
|
|
|
+ const { isLoggedIn } = useAuth()
|
|
|
|
|
+
|
|
|
|
|
+ // 未登录跳转到登录页
|
|
|
|
|
+ if (!isLoggedIn) {
|
|
|
|
|
+ Taro.navigateTo({ url: '/pages/login/index' })
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return <SettingsPage />
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default function SettingsIndex() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <AuthContextProvider>
|
|
|
|
|
+ <Settings />
|
|
|
|
|
+ </AuthContextProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 测试策略
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/testing-strategy.md](../architecture/testing-strategy.md)
|
|
|
|
|
+
|
|
|
|
|
+**测试框架:**
|
|
|
|
|
+- **Jest**: 30.2.0 (mini项目使用Jest,不是Vitest!)
|
|
|
|
|
+- **ts-jest**: 29.4.5 (TypeScript预处理器)
|
|
|
|
|
+- **@testing-library/react**: 16.3.0 (React组件测试)
|
|
|
|
|
+- **@d8d/mini-testing-utils**: workspace包 (Taro小程序测试工具)
|
|
|
|
|
+
|
|
|
|
|
+**测试文件位置**:
|
|
|
|
|
+```
|
|
|
|
|
+mini-ui-packages/<package-name>/
|
|
|
|
|
+└── tests/
|
|
|
|
|
+ ├── components/ # 组件测试
|
|
|
|
|
+ │ ├── UserProfileSummary.test.tsx
|
|
|
|
|
+ │ ├── MenuItem.test.tsx
|
|
|
|
|
+ │ ├── LogoutButton.test.tsx
|
|
|
|
|
+ │ └── LoginLogsList.test.tsx
|
|
|
|
|
+ └── pages/ # 页面组件测试
|
|
|
|
|
+ └── SettingsPage/
|
|
|
|
|
+ └── SettingsPage.test.tsx
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试要求:**
|
|
|
|
|
+1. 为每个页面组件编写Jest测试
|
|
|
|
|
+2. 测试React Query数据加载(使用真实的QueryClientProvider)
|
|
|
|
|
+3. 测试退出登录功能(使用useMutation)
|
|
|
|
|
+4. 测试个人信息摘要展示
|
|
|
|
|
+5. 测试功能入口列表显示
|
|
|
|
|
+6. 测试登录日志列表显示(如果实现)
|
|
|
|
|
+7. 验证mini-talent项目现有功能不受影响
|
|
|
|
|
+8. 运行`pnpm typecheck`确保类型检查通过
|
|
|
|
|
+
|
|
|
|
|
+**页面集成测试示例(使用真实React Query)**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import React from 'react'
|
|
|
|
|
+import { render, screen, waitFor } from '@testing-library/react'
|
|
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
|
|
|
+import SettingsPage from '../pages/SettingsPage/SettingsPage'
|
|
|
|
|
+
|
|
|
|
|
+// Mock API client
|
|
|
|
|
+jest.mock('../../api', () => ({
|
|
|
|
|
+ apiClient: {
|
|
|
|
|
+ auth: {
|
|
|
|
|
+ logout: {
|
|
|
|
|
+ $post: jest.fn()
|
|
|
|
|
+ },
|
|
|
|
|
+ 'login-logs': {
|
|
|
|
|
+ $get: jest.fn()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+const { apiClient } = require('../../api')
|
|
|
|
|
+
|
|
|
|
|
+const createTestQueryClient = () => new QueryClient({
|
|
|
|
|
+ defaultOptions: {
|
|
|
|
|
+ queries: { retry: false, staleTime: Infinity },
|
|
|
|
|
+ mutations: { retry: false }
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const renderWithQueryClient = (component: React.ReactElement) => {
|
|
|
|
|
+ const queryClient = createTestQueryClient()
|
|
|
|
|
+ return render(
|
|
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
|
|
+ {component}
|
|
|
|
|
+ </QueryClientProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+describe('SettingsPage', () => {
|
|
|
|
|
+ beforeEach(() => {
|
|
|
|
|
+ jest.clearAllMocks()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('应该显示个人信息摘要', async () => {
|
|
|
|
|
+ renderWithQueryClient(<SettingsPage />)
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('张明')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('肢体残疾 · 三级')).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('应该显示功能入口列表', async () => {
|
|
|
|
|
+ renderWithQueryClient(<SettingsPage />)
|
|
|
|
|
+
|
|
|
|
|
+ expect(screen.getByText('修改个人信息')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('账号与安全')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('消息通知设置')).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('应该成功退出登录', async () => {
|
|
|
|
|
+ apiClient.auth.logout.$post.mockResolvedValue({
|
|
|
|
|
+ ok: true,
|
|
|
|
|
+ json: async () => ({ message: '退出成功' })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ renderWithQueryClient(<SettingsPage />)
|
|
|
|
|
+
|
|
|
|
|
+ const logoutButton = screen.getByText('退出登录')
|
|
|
|
|
+ logoutButton.click()
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(apiClient.auth.logout.$post).toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 编码标准
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/coding-standards.md](../architecture/coding-standards.md)
|
|
|
|
|
+
|
|
|
|
|
+**关键编码规范:**
|
|
|
|
|
+
|
|
|
|
|
+#### 1. 必须遵循Mini UI包开发规范
|
|
|
|
|
+开发Mini UI包时,**必须**参考并遵循[Mini UI包开发规范](../architecture/mini-ui-package-standards.md),该规范基于史诗011和017的经验总结。
|
|
|
|
|
+
|
|
|
|
|
+#### 2. 关键检查点 (基于史诗011和017经验)
|
|
|
|
|
+- **Taro布局规范**: View容器默认横向布局,必须添加`flex flex-col`实现垂直布局
|
|
|
|
|
+- **图标使用规范**: 必须使用Heroicons图标类,不要使用emoji
|
|
|
|
|
+- **测试框架选择**: **mini项目使用Jest**(不是Vitest)
|
|
|
|
|
+- **Navbar集成**: TabBar页面使用Navbar无返回按钮
|
|
|
|
|
+- **React Query**: 使用React Query管理服务端状态,不要使用useState + useEffect
|
|
|
|
|
+
|
|
|
|
|
+#### 3. 常见错误避免
|
|
|
|
|
+- ❌ 不要忘记添加 `flex flex-col` 实现垂直布局
|
|
|
|
|
+- ❌ 不要使用emoji代替Heroicons图标
|
|
|
|
|
+- ❌ 不要忘记为图标添加尺寸类(`w-5 h-5`、`text-lg`等)
|
|
|
|
|
+- ❌ 不要在Mini UI包内部导入中使用别名 (`@/`、`~/`等),必须使用相对路径
|
|
|
|
|
+- ❌ 不要忘记为TabBar页面隐藏返回按钮
|
|
|
|
|
+- ❌ 不要使用Vitest作为Mini项目的测试框架(应使用Jest)
|
|
|
|
|
+- ❌ 不要使用useState + useEffect手动管理服务端状态(应使用React Query)
|
|
|
|
|
+
|
|
|
|
|
+**路径使用示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: UI包内部使用相对路径
|
|
|
|
|
+import { UserProfileSummary } from '../../components/UserProfileSummary'
|
|
|
|
|
+import { MenuItem } from '../components/MenuItem'
|
|
|
|
|
+
|
|
|
|
|
+// ✅ 正确: 跨包导入使用workspace包名
|
|
|
|
|
+import { SharedComponent } from '@d8d/mini-shared-ui-components'
|
|
|
|
|
+
|
|
|
|
|
+// ❌ 错误: UI包内部使用别名
|
|
|
|
|
+import { UserProfileSummary } from '@/components/UserProfileSummary'
|
|
|
|
|
+import { MenuItem } from '@/components/MenuItem'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 4. 参考实现
|
|
|
|
|
+- **用人方设置UI包**: `mini-ui-packages/yongren-settings-ui` (如果存在)
|
|
|
|
|
+- **人才个人信息UI包**: `mini-ui-packages/rencai-personal-info-ui`
|
|
|
|
|
+ - 组件结构和测试参考
|
|
|
|
|
+ - Navbar集成参考
|
|
|
|
|
+
|
|
|
|
|
+### 技术约束
|
|
|
|
|
+
|
|
|
|
|
+1. **向后兼容**: 不影响现有mini-talent项目功能
|
|
|
|
|
+2. **类型安全**: 使用TypeScript严格模式,所有数据结构必须有类型定义
|
|
|
|
|
+3. **模块独立性**: 每个UI包独立管理自己的类型定义
|
|
|
|
|
+4. **测试覆盖**: 所有新增代码必须有测试覆盖
|
|
|
|
|
+5. **代码规范**: 遵循项目编码标准和Mini UI包开发规范
|
|
|
|
|
+6. **React Query**: 必须使用React Query管理服务端状态
|
|
|
|
|
+
|
|
|
|
|
+### 风险和缓解措施
|
|
|
|
|
+
|
|
|
|
|
+**主要风险:**
|
|
|
|
|
+1. **API延期风险**: 史诗015的帮助与支持API为P2延期功能,使用静态内容展示
|
|
|
|
|
+2. **退出登录状态同步**: 退出后需要确保所有相关状态被清除
|
|
|
|
|
+3. **UI组件复用风险**: rencai系列UI包可能与现有yongren系列UI包存在差异
|
|
|
|
|
+4. **登录日志分页**: 登录日志数据量可能较大,需要考虑分页加载
|
|
|
|
|
+
|
|
|
|
|
+**缓解措施:**
|
|
|
|
|
+1. **静态内容展示**: 帮助中心、用户协议、隐私政策使用静态内容展示,便于后续替换为API
|
|
|
|
|
+2. **完整状态清理**: 退出登录时清除React Query缓存、本地存储、用户状态
|
|
|
|
|
+3. **参考现有模式**: 参照yongren系列UI包的实现模式和架构
|
|
|
|
|
+4. **类型安全**: 使用TypeScript接口定义数据结构,确保类型一致性
|
|
|
|
|
+5. **测试驱动**: 编写完整的测试,确保功能正确性
|
|
|
|
|
+6. **React Query缓存**: 使用queryClient.clear()清除所有缓存
|
|
|
|
|
+
|
|
|
|
|
+## 变更日志
|
|
|
|
|
+
|
|
|
|
|
+| 日期 | 版本 | 描述 | 作者 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| 2025-12-28 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
|
|
|
|
|
+| 2025-12-28 | 1.1 | 状态更新为Approved | Bob (Scrum Master) |
|
|
|
|
|
+
|
|
|
|
|
+## 开发者记录
|
|
|
|
|
+
|
|
|
|
|
+*此部分由开发代理在实施过程中填写*
|
|
|
|
|
+
|
|
|
|
|
+### 使用的代理模型
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+### 调试日志引用
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+### 完成说明列表
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+### 文件列表
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+## QA结果
|
|
|
|
|
+
|
|
|
|
|
+*此部分由QA代理在审查完成后填写*
|