|
@@ -0,0 +1,901 @@
|
|
|
|
|
+# 故事017.003: 个人信息功能实现
|
|
|
|
|
+
|
|
|
|
|
+## 元信息
|
|
|
|
|
+- **史诗**: 017 - 人才小程序功能实现
|
|
|
|
|
+- **优先级**: P1 - 核心功能
|
|
|
|
|
+- **状态**: Draft
|
|
|
|
|
+- **创建日期**: 2025-12-26
|
|
|
|
|
+- **负责人**: 开发团队
|
|
|
|
|
+
|
|
|
|
|
+## 故事描述
|
|
|
|
|
+
|
|
|
|
|
+**作为** 人才小程序开发者,
|
|
|
|
|
+**我想要** 实现个人信息页面功能,
|
|
|
|
|
+**以便** 人才用户能够查看个人基本信息、银行卡信息和证件照片。
|
|
|
|
|
+
|
|
|
|
|
+### 背景
|
|
|
|
|
+
|
|
|
|
|
+**现有系统状态:**
|
|
|
|
|
+- 故事017.001已完成rencai mini ui包基础框架搭建
|
|
|
|
|
+- 故事017.002已完成登录与首页实现
|
|
|
|
|
+- rencai系列7个UI包基础目录结构已创建
|
|
|
|
|
+- `@d8d/rencai-personal-info-ui`包基础框架已就绪
|
|
|
|
|
+- API客户端文件已创建(`src/api/index.ts`)
|
|
|
|
|
+- mini-talent项目路由结构已配置完成(`pages/personal-info/index`在TabBar中)
|
|
|
|
|
+
|
|
|
|
|
+**原型设计参考:**
|
|
|
|
|
+- `docs/小程序原型/rencai.html` 提供了个人信息页面的完整原型设计
|
|
|
|
|
+- 个人信息页 (原型行483-628): 个人基本信息、银行卡信息、证件照片
|
|
|
|
|
+
|
|
|
|
|
+**技术集成模式:**
|
|
|
|
|
+- 参照`yongren-personal-info-ui`的实现模式(如果存在)
|
|
|
|
|
+- 个人信息页使用`@d8d/rencai-personal-info-ui`包,集成个人信息查询API、银行卡信息查询API、证件照片查询API
|
|
|
|
|
+- API调用逻辑封装在页面组件内部
|
|
|
|
|
+- TabBar页面使用Navbar无返回按钮(参照故事017.012规范)
|
|
|
|
|
+
|
|
|
|
|
+**依赖API (史诗015):**
|
|
|
|
|
+- ✅ 个人信息查询API (GET /api/v1/rencai/personal/info) - 已就绪
|
|
|
|
|
+- ✅ 银行卡信息查询API (GET /api/v1/rencai/personal/bank-cards) - 已就绪
|
|
|
|
|
+- ✅ 证件照片查询API (GET /api/v1/rencai/personal/documents) - 已就绪
|
|
|
|
|
+
|
|
|
|
|
+**依赖故事完成状态:**
|
|
|
|
|
+- ✅ 故事017.001: rencai mini ui包基础框架搭建完成
|
|
|
|
|
+- ✅ 故事017.002: 登录与首页实现完成
|
|
|
|
|
+- ✅ 故事017.012: 统一Navbar导航栏组件规范完成
|
|
|
|
|
+
|
|
|
|
|
+## 验收标准
|
|
|
|
|
+
|
|
|
|
|
+### 个人基本信息展示
|
|
|
|
|
+- [ ] 个人信息页面展示完整的个人基本信息(姓名、性别、年龄、身份证号、残疾证号、残疾类型、联系电话、联系地址)
|
|
|
|
|
+
|
|
|
|
|
+### 银行卡信息展示
|
|
|
|
|
+- [ ] 银行卡信息查询接口返回银行卡信息列表
|
|
|
|
|
+- [ ] 银行卡信息展示包含:银行名称、卡号脱敏、卡类型、是否默认
|
|
|
|
|
+- [ ] 支持多张银行卡的列表展示
|
|
|
|
|
+
|
|
|
|
|
+### 证件照片展示与预览
|
|
|
|
|
+- [ ] 证件照片查询接口返回证件照片信息列表
|
|
|
|
|
+- [ ] 证件照片类型包含:身份证、残疾证、体检报告、征信报告
|
|
|
|
|
+- [ ] 支持证件照片预览功能(点击照片放大查看)
|
|
|
|
|
+
|
|
|
|
|
+### 页面设计与布局
|
|
|
|
|
+- [ ] 页面设计符合原型标准,移动端体验良好
|
|
|
|
|
+- [ ] 已集成Navbar导航栏组件(TabBar页面,无返回按钮)
|
|
|
|
|
+- [ ] TabBarLayout底部导航正常工作
|
|
|
|
|
+
|
|
|
|
|
+### 数据安全与权限
|
|
|
|
|
+- [ ] 所有查询接口验证用户权限,确保用户只能查询自己的数据
|
|
|
|
|
+- [ ] 银行卡号进行脱敏处理(如:**** **** **** 1234)
|
|
|
|
|
+
|
|
|
|
|
+### 集成与兼容性
|
|
|
|
|
+- [ ] 与前置故事(017.001、017.002)无缝集成
|
|
|
|
|
+- [ ] 现有mini-talent项目功能不受影响
|
|
|
|
|
+
|
|
|
|
|
+## 任务列表
|
|
|
|
|
+
|
|
|
|
|
+### 任务1: 创建个人信息页面组件 (AC: 个人基本信息展示)
|
|
|
|
|
+- [ ] 1.1 在`@d8d/rencai-personal-info-ui`中实现`PersonalInfoPage`页面组件 (`src/pages/PersonalInfoPage/PersonalInfoPage.tsx`)
|
|
|
|
|
+- [ ] 1.2 创建个人基本信息卡片组件:
|
|
|
|
|
+ - 显示姓名
|
|
|
|
|
+ - 显示性别
|
|
|
|
|
+ - 显示年龄
|
|
|
|
|
+ - 显示身份证号(脱敏显示)
|
|
|
|
|
+ - 显示残疾证号(脱敏显示)
|
|
|
|
|
+ - 显示残疾类型
|
|
|
|
|
+ - 显示联系电话
|
|
|
|
|
+ - 显示联系地址
|
|
|
|
|
+- [ ] 1.3 集成个人信息查询API (`talentPersonalInfoClient.personal.info.$get`)
|
|
|
|
|
+- [ ] 1.4 实现数据加载状态(Loading状态)
|
|
|
|
|
+- [ ] 1.5 实现错误处理(API调用失败时显示错误提示)
|
|
|
|
|
+
|
|
|
|
|
+### 任务2: 实现银行卡信息模块 (AC: 银行卡信息展示)
|
|
|
|
|
+- [ ] 2.1 创建银行卡信息卡片组件 (`src/components/BankCardInfo.tsx`)
|
|
|
|
|
+- [ ] 2.2 创建银行卡列表项组件 (`src/components/BankCardItem.tsx`)
|
|
|
|
|
+- [ ] 2.3 集成银行卡信息查询API (`talentPersonalInfoClient.personal.bankCards.$get`)
|
|
|
|
|
+- [ ] 2.4 实现银行卡号脱敏显示(如:**** **** **** 1234)
|
|
|
|
|
+- [ ] 2.5 显示银行卡信息:银行名称、卡号脱敏、卡类型(储蓄卡/信用卡)、是否默认标识
|
|
|
|
|
+- [ ] 2.6 支持多张银行卡的列表展示
|
|
|
|
|
+
|
|
|
|
|
+### 任务3: 实现证件照片模块 (AC: 证件照片展示与预览)
|
|
|
|
|
+- [ ] 3.1 创建证件照片卡片组件 (`src/components/DocumentPhotos.tsx`)
|
|
|
|
|
+- [ ] 3.2 创建证件照片列表项组件 (`src/components/DocumentPhotoItem.tsx`)
|
|
|
|
|
+- [ ] 3.3 集成证件照片查询API (`talentPersonalInfoClient.personal.documents.$get`)
|
|
|
|
|
+- [ ] 3.4 支持证件照片类型展示:身份证、残疾证、体检报告、征信报告
|
|
|
|
|
+- [ ] 3.5 实现证件照片预览功能(点击照片放大查看)
|
|
|
|
|
+- [ ] 3.6 使用Taro的Image组件展示照片
|
|
|
|
|
+- [ ] 3.7 实现照片预览Modal(使用Taro.previewImage或自定义Modal)
|
|
|
|
|
+
|
|
|
|
|
+### 任务4: 集成Navbar导航栏组件 (AC: 页面设计与布局)
|
|
|
|
|
+- [ ] 4.1 导入Navbar组件: `import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'`
|
|
|
|
|
+- [ ] 4.2 在页面顶部添加Navbar,配置为TabBar页面(无返回按钮)
|
|
|
|
|
+- [ ] 4.3 Navbar配置: `leftIcon="" leftText="" onClickLeft={() => {}}`
|
|
|
|
|
+- [ ] 4.4 使用Navbar的`placeholder`属性占位,移除手动空白占位
|
|
|
|
|
+- [ ] 4.5 确保Navbar固定在顶部 (`fixed=true`)
|
|
|
|
|
+
|
|
|
|
|
+### 任务5: 更新mini-talent页面集成 (AC: 集成与兼容性)
|
|
|
|
|
+- [ ] 5.1 更新`mini-talent/src/pages/personal-info/index.tsx`:
|
|
|
|
|
+ - 从`@d8d/rencai-personal-info-ui/pages/PersonalInfoPage/PersonalInfoPage`导入PersonalInfoPage组件
|
|
|
|
|
+ - 用AuthProvider包装页面
|
|
|
|
|
+ - 添加认证检查(未登录跳转到登录页)
|
|
|
|
|
+ - 导出PersonalInfoPage组件
|
|
|
|
|
+- [ ] 5.2 验证页面路由配置(已在故事017.001中配置完成)
|
|
|
|
|
+- [ ] 5.3 验证底部TabBar导航正常工作(TabBar中"我的"对应personal-info页面)
|
|
|
|
|
+
|
|
|
|
|
+### 任务6: 实现页面样式和移动端适配 (AC: 页面设计与布局)
|
|
|
|
|
+- [ ] 6.1 参照原型设计实现个人信息页样式(原型行483-628)
|
|
|
|
|
+ - 个人基本信息卡片样式(白色卡片,圆角)
|
|
|
|
|
+ - 银行卡信息列表样式(白色卡片,列表项分隔)
|
|
|
|
|
+ - 证件照片列表样式(白色卡片,网格布局)
|
|
|
|
|
+- [ ] 6.2 确保页面设计符合移动端规范:
|
|
|
|
|
+ - 宽度参考: 375px
|
|
|
|
|
+ - 圆角规范: 12px (卡片)
|
|
|
|
|
+ - 颜色主题: 蓝色渐变 (#3b82f6 → #1e40af)
|
|
|
|
|
+ - 字体规范: 标题18-24px, 正文14px, 小字12px
|
|
|
|
|
+- [ ] 6.3 使用正确的组件:个人信息页使用TabBarLayout(无返回按钮的Navbar)
|
|
|
|
|
+
|
|
|
|
|
+### 任务7: 编写测试 (AC: 集成与兼容性)
|
|
|
|
|
+- [ ] 7.1 为PersonalInfoPage编写组件测试 (`tests/pages/PersonalInfoPage/PersonalInfoPage.test.tsx`)
|
|
|
|
|
+ - 测试组件渲染
|
|
|
|
|
+ - 测试个人基本信息展示(Mock API响应)
|
|
|
|
|
+ - 测试银行卡信息展示(Mock API响应)
|
|
|
|
|
+ - 测试证件照片展示(Mock API响应)
|
|
|
|
|
+ - 测试照片预览功能
|
|
|
|
|
+- [ ] 7.2 编写集成测试验证现有功能不受影响
|
|
|
|
|
+- [ ] 7.3 运行`pnpm typecheck`确保类型检查通过
|
|
|
|
|
+
|
|
|
|
|
+## 开发者笔记
|
|
|
|
|
+
|
|
|
|
|
+### 前置故事见解
|
|
|
|
|
+
|
|
|
|
|
+**故事017.001完成状态:**
|
|
|
|
|
+- ✅ rencai系列7个UI包基础结构已创建
|
|
|
|
|
+- ✅ API客户端文件已创建(`src/api/index.ts`)
|
|
|
|
|
+- ✅ mini-talent项目路由结构已配置完成
|
|
|
|
|
+- ✅ 基础布局组件(TabBarLayout、Navbar等)已就绪
|
|
|
|
|
+- ✅ package.json的exports字段已配置
|
|
|
|
|
+- ✅ Jest测试框架已配置(`jest.config.cjs`)
|
|
|
|
|
+
|
|
|
|
|
+**故事017.002完成状态:**
|
|
|
|
|
+- ✅ 登录页面功能完整,支持人才用户身份证号/残疾证号密码登录
|
|
|
|
|
+- ✅ 首页/个人主页页面展示个人概览数据
|
|
|
|
|
+- ✅ 认证状态管理正常,token存储和验证可靠
|
|
|
|
|
+- ✅ AuthContext提供登录状态、用户信息和登出方法
|
|
|
|
|
+- ✅ 页面设计符合原型标准,移动端体验良好
|
|
|
|
|
+
|
|
|
|
|
+**故事017.012完成状态:**
|
|
|
|
|
+- ✅ 统一Navbar导航栏组件规范
|
|
|
|
|
+- ✅ TabBar页面使用Navbar无返回按钮(leftIcon="" leftText="")
|
|
|
|
|
+- ✅ 非TabBar页面使用Navbar带返回按钮
|
|
|
|
|
+- ✅ Navbar样式与用人方小程序保持一致
|
|
|
|
|
+
|
|
|
|
|
+**关键实现经验:**
|
|
|
|
|
+1. API客户端导入路径修正:从相应的后端模块包导入,而不是`@d8d/server`
|
|
|
|
|
+2. 页面文件简化设计:采用"薄包装层",仅导入并导出组件
|
|
|
|
|
+3. 复用现有共享组件:StatusBar、PageContainer、Navbar使用`@d8d/mini-shared-ui-components`中的实现
|
|
|
|
|
+4. 测试框架选择:**mini项目使用Jest**(不是Vitest)
|
|
|
|
|
+
|
|
|
|
|
+### 技术栈要求
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [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 (样式)
|
|
|
|
|
+
|
|
|
|
|
+**测试框架:**
|
|
|
|
|
+- **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包专用规范(本文档必须遵循)**
|
|
|
|
|
+- [architecture/ui-package-standards.md](../architecture/ui-package-standards.md) - 通用UI包规范(Web应用适用)
|
|
|
|
|
+
|
|
|
|
|
+**关键规范要求:**
|
|
|
|
|
+
|
|
|
|
|
+#### 1. UI包内部导入规范
|
|
|
|
|
+**重要**: UI包内部导入必须使用相对路径,不要使用别名。
|
|
|
|
|
+
|
|
|
|
|
+**正确示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: 使用相对路径导入同一包内的模块
|
|
|
|
|
+import { talentPersonalInfoClient } from '../../api'
|
|
|
|
|
+import { BankCardItem } from '../components/BankCardItem'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**错误示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ❌ 错误: 不要使用别名导入UI包内部的模块
|
|
|
|
|
+import { talentPersonalInfoClient } from '@/api'
|
|
|
|
|
+import { BankCardItem } from '@/components/BankCardItem'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 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/PersonalInfoPage/PersonalInfoPage": {
|
|
|
|
|
+ "types": "./dist/src/pages/PersonalInfoPage/PersonalInfoPage.d.ts",
|
|
|
|
|
+ "import": "./dist/src/pages/PersonalInfoPage/PersonalInfoPage.js",
|
|
|
|
|
+ "require": "./dist/src/pages/PersonalInfoPage/PersonalInfoPage.js"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 3. RPC客户端实现规范
|
|
|
|
|
+使用简单的RPC客户端导出模式 (参照`yongren-statistics-ui/src/api/enterpriseStatisticsClient.ts`):
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// src/api/talentPersonalInfoClient.ts
|
|
|
|
|
+import type { rencaiPersonalInfoRoutes } from '@d8d/core-module/allin-disability-module'
|
|
|
|
|
+import { rpcClient } from '@d8d/mini-shared-ui-components/utils/rpc/rpc-client'
|
|
|
|
|
+
|
|
|
|
|
+// 人才小程序API路径前缀: /api/v1/rencai
|
|
|
|
|
+export const talentPersonalInfoClient = rpcClient<typeof rencaiPersonalInfoRoutes>('/api/v1/rencai')
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**src/api/index.ts导出**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+export { talentPersonalInfoClient } from './talentPersonalInfoClient'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 4. 类型定义规范
|
|
|
|
|
+**必须使用RPC推断类型**,避免直接导入schema类型:
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确:使用RPC推断类型 + 相对路径导入
|
|
|
|
|
+import type { InferResponseType } from 'hono'
|
|
|
|
|
+import { talentPersonalInfoClient } from '../api/talentPersonalInfoClient'
|
|
|
|
|
+
|
|
|
|
|
+export type PersonalInfoResponse = InferResponseType<typeof talentPersonalInfoClient.personal.info.$get, 200>
|
|
|
|
|
+export type BankCardsResponse = InferResponseType<typeof talentPersonalInfoClient.personal.bankCards.$get, 200>
|
|
|
|
|
+export type DocumentsResponse = InferResponseType<typeof talentPersonalInfoClient.personal.documents.$get, 200>
|
|
|
|
|
+
|
|
|
|
|
+// ❌ 错误:直接导入schema类型 (可能导致Date/string不匹配)
|
|
|
|
|
+import type { PersonalInfoSchema } from '@d8d/allin-disability-module/schemas'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 5. Jest配置规范
|
|
|
|
|
+每个UI包必须创建`jest.config.cjs`配置文件,参照`yongren-dashboard-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']
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键配置说明**:
|
|
|
|
|
+- `setupFilesAfterEnv`: 使用`@d8d/mini-testing-utils/setup`进行测试环境初始化
|
|
|
|
|
+- `moduleNameMapper`:
|
|
|
|
|
+ - `^@/(.*)$` 和 `^~/(.*)$`: 仅用于测试文件的路径映射,不在源代码中使用
|
|
|
|
|
+ - `^@tarojs/taro$`: 映射Taro API到mock
|
|
|
|
|
+ - 样式和文件映射: 将样式文件和静态文件映射到mock
|
|
|
|
|
+- **重要**: 源代码中不使用`@/`或`~/`别名,只使用相对路径
|
|
|
|
|
+- `testMatch`: 支持`.spec.{ts,tsx}`和`.test.{ts,tsx}`两种测试文件格式
|
|
|
|
|
+
|
|
|
|
|
+### 史诗015 API集成要点
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [docs/prd/epic-015-talent-mini-program-api-support.md](../prd/epic-015-talent-mini-program-api-support.md)
|
|
|
|
|
+
|
|
|
|
|
+**API路径约定:**
|
|
|
|
|
+所有人才小程序API使用 `api/v1/rencai` 前缀:
|
|
|
|
|
+- 人才个人信息: `GET /api/v1/rencai/personal/info`
|
|
|
|
|
+- 人才银行卡信息: `GET /api/v1/rencai/personal/bank-cards`
|
|
|
|
|
+- 人才证件照片: `GET /api/v1/rencai/personal/documents`
|
|
|
|
|
+
|
|
|
|
|
+**已完成的API (可直接集成):**
|
|
|
|
|
+- ✅ 个人信息查询API (故事015-03)
|
|
|
|
|
+ - 路由定义: `@d8d/allin-disability-module/src/routes/talent-personal-info.route.ts`
|
|
|
|
|
+ - RPC路由: `talentPersonalInfoRoutes.personal.info`
|
|
|
|
|
+ - 响应格式: `{ name: string, gender: string, age: number, idCard: string, disabilityCard: string, disabilityType: string, phone: string, address: string, ... }`
|
|
|
|
|
+- ✅ 银行卡信息查询API (故事015-04)
|
|
|
|
|
+ - 路由定义: `@d8d/allin-disability-module/src/routes/talent-bank-cards.route.ts`
|
|
|
|
|
+ - RPC路由: `talentPersonalInfoRoutes.personal.bankCards`
|
|
|
|
|
+ - 响应格式: `{ bankCards: [{ bankName: string, cardNumber: string, cardType: string, isDefault: boolean, ... }] }`
|
|
|
|
|
+- ✅ 证件照片查询API (故事015-05)
|
|
|
|
|
+ - 路由定义: `@d8d/allin-disability-module/src/routes/talent-documents.route.ts`
|
|
|
|
|
+ - RPC路由: `talentPersonalInfoRoutes.personal.documents`
|
|
|
|
|
+ - 响应格式: `{ documents: [{ type: string, url: string, uploadDate: Date, ... }] }`
|
|
|
|
|
+
|
|
|
|
|
+**只读设计原则:**
|
|
|
|
|
+遵循与用人方小程序相同的设计原则,人才小程序API以查询功能为主。
|
|
|
|
|
+
|
|
|
|
|
+### 原型设计参考
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [docs/小程序原型/rencai.html](../小程序原型/rencai.html)
|
|
|
|
|
+
|
|
|
|
|
+**个人信息页** (原型行483-628):
|
|
|
|
|
+```html
|
|
|
|
|
+<!-- 个人基本信息卡片 -->
|
|
|
|
|
+<div style="background: white; border-radius: 12px;">
|
|
|
|
|
+ <h3>个人基本信息</h3>
|
|
|
|
|
+ <div>姓名: 张三</div>
|
|
|
|
|
+ <div>性别: 男</div>
|
|
|
|
|
+ <div>年龄: 35</div>
|
|
|
|
|
+ <div>身份证号: 3301**********1234</div>
|
|
|
|
|
+ <div>残疾证号: 3301**********5678</div>
|
|
|
|
|
+ <div>残疾类型: 肢体残疾</div>
|
|
|
|
|
+ <div>联系电话: 138****5678</div>
|
|
|
|
|
+ <div>联系地址: 浙江省杭州市...</div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<!-- 银行卡信息卡片 -->
|
|
|
|
|
+<div style="background: white; border-radius: 12px;">
|
|
|
|
|
+ <h3>银行卡信息</h3>
|
|
|
|
|
+ <div style="border-bottom: 1px solid #eee;">
|
|
|
|
|
+ <div>中国建设银行</div>
|
|
|
|
|
+ <div>**** **** **** 1234</div>
|
|
|
|
|
+ <div>储蓄卡</div>
|
|
|
|
|
+ <div>默认</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div>中国工商银行</div>
|
|
|
|
|
+ <div>**** **** **** 5678</div>
|
|
|
|
|
+ <div>储蓄卡</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<!-- 证件照片卡片 -->
|
|
|
|
|
+<div style="background: white; border-radius: 12px;">
|
|
|
|
|
+ <h3>证件照片</h3>
|
|
|
|
|
+ <div style="display: grid; grid-template-columns: 1fr 1fr;">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <img src="id-card-front.jpg" />
|
|
|
|
|
+ <div>身份证(正面)</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <img src="id-card-back.jpg" />
|
|
|
|
|
+ <div>身份证(反面)</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <img src="disability-card.jpg" />
|
|
|
|
|
+ <div>残疾证</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <img src="medical-report.jpg" />
|
|
|
|
|
+ <div>体检报告</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**移动端设计规范:**
|
|
|
|
|
+- 宽度参考: 375px
|
|
|
|
|
+- 状态栏高度: 44px
|
|
|
|
|
+- 底部导航高度: 60px
|
|
|
|
|
+- 圆角规范: 12px (卡片)、40px (移动框架)
|
|
|
|
|
+- 颜色主题: 蓝色渐变 (#3b82f6 → #1e40af)
|
|
|
|
|
+- 字体规范: 标题18-24px, 正文14px, 小字12px
|
|
|
|
|
+
|
|
|
|
|
+### 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 PersonalInfoPage() {
|
|
|
|
|
+ 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=""`: 隐藏返回按钮文字
|
|
|
|
|
+- `onClickLeft={() => {}}`: 空函数(TabBar页面不需要返回功能)
|
|
|
|
|
+- `placeholder`: 添加占位空间,避免内容被Navbar遮挡
|
|
|
|
|
+- `fixed`: 固定在顶部
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构指南
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/source-tree.md](../architecture/source-tree.md)
|
|
|
|
|
+
|
|
|
|
|
+**mini-talent项目结构:**
|
|
|
|
|
+```
|
|
|
|
|
+mini-talent/ # 人才小程序项目
|
|
|
|
|
+├── src/
|
|
|
|
|
+│ ├── app.tsx # 小程序入口
|
|
|
|
|
+│ ├── app.config.ts # 小程序配置 (已在故事017.001中更新)
|
|
|
|
|
+│ ├── app.css # 全局样式
|
|
|
|
|
+│ ├── pages/ # 页面目录
|
|
|
|
|
+│ │ ├── login/ # 登录页 (从UI包导入)
|
|
|
|
|
+│ │ │ └── index.tsx
|
|
|
|
|
+│ │ ├── index/ # 首页/个人主页 (从UI包导入)
|
|
|
|
|
+│ │ │ └── index.tsx
|
|
|
|
|
+│ │ ├── personal-info/ # 个人信息页 (从UI包导入) - 本故事
|
|
|
|
|
+│ │ │ └── index.tsx
|
|
|
|
|
+│ │ ├── attendance/ # 考勤记录页 (从UI包导入) - 故事017.004
|
|
|
|
|
+│ │ │ └── index.tsx
|
|
|
|
|
+│ │ └── settings/ # 设置页 (从UI包导入) - 故事017.006
|
|
|
|
|
+│ │ └── index.tsx
|
|
|
|
|
+├── package.json
|
|
|
|
|
+├── jest.config.js # Jest配置
|
|
|
|
|
+└── tsconfig.json
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**mini-ui-packages目录结构:**
|
|
|
|
|
+```
|
|
|
|
|
+mini-ui-packages/
|
|
|
|
|
+├── rencai-personal-info-ui/ # 人才个人信息UI包
|
|
|
|
|
+│ ├── src/
|
|
|
|
|
+│ │ ├── api/
|
|
|
|
|
+│ │ │ ├── talentPersonalInfoClient.ts
|
|
|
|
|
+│ │ │ └── index.ts
|
|
|
|
|
+│ │ ├── pages/
|
|
|
|
|
+│ │ │ └── PersonalInfoPage/
|
|
|
|
|
+│ │ │ ├── PersonalInfoPage.tsx
|
|
|
|
|
+│ │ │ └── index.ts (可选)
|
|
|
|
|
+│ │ ├── components/ # UI组件
|
|
|
|
|
+│ │ │ ├── PersonalBasicInfo.tsx # 个人基本信息卡片
|
|
|
|
|
+│ │ │ ├── BankCardInfo.tsx # 银行卡信息卡片
|
|
|
|
|
+│ │ │ ├── BankCardItem.tsx # 银行卡列表项
|
|
|
|
|
+│ │ │ ├── DocumentPhotos.tsx # 证件照片卡片
|
|
|
|
|
+│ │ │ └── DocumentPhotoItem.tsx # 证件照片列表项
|
|
|
|
|
+│ │ └── index.ts
|
|
|
|
|
+│ ├── package.json # 包含exports配置
|
|
|
|
|
+│ ├── 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/personal-info/index.tsx
|
|
|
|
|
+import PersonalInfoPage from '@d8d/rencai-personal-info-ui/pages/PersonalInfoPage/PersonalInfoPage'
|
|
|
|
|
+import { AuthContextProvider, useAuth } from '@d8d/rencai-auth-ui/utils'
|
|
|
|
|
+
|
|
|
|
|
+function PersonalInfo() {
|
|
|
|
|
+ const { isLoggedIn } = useAuth()
|
|
|
|
|
+
|
|
|
|
|
+ // 未登录跳转到登录页
|
|
|
|
|
+ if (!isLoggedIn) {
|
|
|
|
|
+ Taro.navigateTo({ url: '/pages/login/index' })
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return <PersonalInfoPage />
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default function PersonalInfoIndex() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <AuthContextProvider>
|
|
|
|
|
+ <PersonalInfo />
|
|
|
|
|
+ </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/
|
|
|
|
|
+ ├── unit/ # 单元测试
|
|
|
|
|
+ │ └── components/
|
|
|
|
|
+ │ ├── PersonalBasicInfo.test.tsx
|
|
|
|
|
+ │ ├── BankCardItem.test.tsx
|
|
|
|
|
+ │ └── DocumentPhotoItem.test.tsx
|
|
|
|
|
+ └── pages/ # 页面组件测试
|
|
|
|
|
+ └── PersonalInfoPage/
|
|
|
|
|
+ └── PersonalInfoPage.test.tsx
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试要求:**
|
|
|
|
|
+1. 为每个页面组件编写Jest测试
|
|
|
|
|
+2. 测试API客户端的集成(使用Mock响应)
|
|
|
|
|
+3. 测试银行卡号脱敏逻辑
|
|
|
|
|
+4. 测试证件照片预览功能
|
|
|
|
|
+5. 验证mini-talent项目现有功能不受影响
|
|
|
|
|
+6. 运行`pnpm typecheck`确保类型检查通过
|
|
|
|
|
+
|
|
|
|
|
+**Mock响应工具函数:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+const createMockResponse = (status: number, data?: any) => ({
|
|
|
|
|
+ status,
|
|
|
|
|
+ ok: status >= 200 && status < 300,
|
|
|
|
|
+ body: null,
|
|
|
|
|
+ bodyUsed: false,
|
|
|
|
|
+ statusText: status === 200 ? 'OK' : 'Error',
|
|
|
|
|
+ headers: new Headers(),
|
|
|
|
|
+ url: '',
|
|
|
|
|
+ redirected: false,
|
|
|
|
|
+ type: 'basic' as ResponseType,
|
|
|
|
|
+ json: async () => data || {},
|
|
|
|
|
+ text: async () => '',
|
|
|
|
|
+ blob: async () => new Blob(),
|
|
|
|
|
+ arrayBuffer: async () => new ArrayBuffer(0),
|
|
|
|
|
+ formData: async () => new FormData(),
|
|
|
|
|
+ clone: function() { return this; }
|
|
|
|
|
+})
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 编码标准
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [architecture/coding-standards.md](../architecture/coding-standards.md)
|
|
|
|
|
+
|
|
|
|
|
+**关键编码规范:**
|
|
|
|
|
+
|
|
|
|
|
+#### 1. 必须遵循UI包开发规范
|
|
|
|
|
+开发UI包时,**必须**参考并遵循[UI包开发规范](../architecture/ui-package-standards.md),该规范基于史诗008(AllIn UI模块移植)的经验总结。
|
|
|
|
|
+
|
|
|
|
|
+#### 2. 关键检查点 (基于史诗008经验)
|
|
|
|
|
+- **API路径映射验证**: 开发前必须验证故事中的API路径映射与实际后端路由定义的一致性
|
|
|
|
|
+- **类型推断最佳实践**: 必须使用RPC推断类型,而不是直接导入schema类型
|
|
|
|
|
+- **测试选择器优化**: 必须为关键交互元素添加`data-testid`属性
|
|
|
|
|
+- **API调用一致性**: 必须根据实际路由名称修正API调用
|
|
|
|
|
+
|
|
|
|
|
+#### 3. 常见错误避免
|
|
|
|
|
+- ❌ 不要直接导入schema类型 (可能导致Date/string类型不匹配)
|
|
|
|
|
+- ❌ 不要使用`getByText()`查找可能重复的文本元素
|
|
|
|
|
+- ❌ 不要在UI包内部导入中使用别名 (`@/`, `~/`等),必须使用相对路径
|
|
|
|
|
+- ❌ 不要忘记在TabBar页面集成Navbar导航栏组件
|
|
|
|
|
+- ❌ 不要在TabBar页面添加返回按钮
|
|
|
|
|
+
|
|
|
|
|
+**路径使用示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: UI包内部使用相对路径
|
|
|
|
|
+import { apiClient } from '../../api'
|
|
|
|
|
+import { MyComponent } from '../components'
|
|
|
|
|
+
|
|
|
|
|
+// ✅ 正确: 跨包导入使用workspace包名
|
|
|
|
|
+import { SharedComponent } from '@d8d/mini-shared-ui-components'
|
|
|
|
|
+
|
|
|
|
|
+// ❌ 错误: UI包内部使用别名
|
|
|
|
|
+import { apiClient } from '@/api'
|
|
|
|
|
+import { MyComponent } from '@/pages/PersonalInfoPage/components'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 4. 参考实现
|
|
|
|
|
+- **用人方个人信息UI包**: `mini-ui-packages/yongren-personal-info-ui` (如果存在)
|
|
|
|
|
+- **用人方仪表板UI包**: `mini-ui-packages/yongren-dashboard-ui`
|
|
|
|
|
+ - 组件: `src/pages/Dashboard/Dashboard.tsx`
|
|
|
|
|
+ - package.json exports配置
|
|
|
|
|
+ - 目录结构参考
|
|
|
|
|
+ - Jest配置: `jest.config.cjs`
|
|
|
|
|
+
|
|
|
|
|
+### 数据脱敏规范
|
|
|
|
|
+
|
|
|
|
|
+**银行卡号脱敏:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+/**
|
|
|
|
|
+ * 脱敏银行卡号
|
|
|
|
|
+ * @param cardNumber 完整银行卡号
|
|
|
|
|
+ * @returns 脱敏后的银行卡号(如:**** **** **** 1234)
|
|
|
|
|
+ */
|
|
|
|
|
+export function maskCardNumber(cardNumber: string): string {
|
|
|
|
|
+ if (!cardNumber || cardNumber.length < 4) {
|
|
|
|
|
+ return '****'
|
|
|
|
|
+ }
|
|
|
|
|
+ const last4 = cardNumber.slice(-4)
|
|
|
|
|
+ return `**** **** **** ${last4}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 使用示例
|
|
|
|
|
+<BankCardItem>
|
|
|
|
|
+ <Text>{maskCardNumber(bankCard.cardNumber)}</Text>
|
|
|
|
|
+</BankCardItem>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**身份证号脱敏:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+/**
|
|
|
|
|
+ * 脱敏身份证号
|
|
|
|
|
+ * @param idCard 完整身份证号
|
|
|
|
|
+ * @returns 脱敏后的身份证号(如:3301**********1234)
|
|
|
|
|
+ */
|
|
|
|
|
+export function maskIdCard(idCard: string): string {
|
|
|
|
|
+ if (!idCard || idCard.length < 8) {
|
|
|
|
|
+ return '********'
|
|
|
|
|
+ }
|
|
|
|
|
+ const prefix = idCard.slice(0, 4)
|
|
|
|
|
+ const suffix = idCard.slice(-4)
|
|
|
|
|
+ return `${prefix}**********${suffix}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 使用示例
|
|
|
|
|
+<PersonalBasicInfo>
|
|
|
|
|
+ <Text>身份证号: {maskIdCard(personalInfo.idCard)}</Text>
|
|
|
|
|
+</PersonalBasicInfo>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 证件照片预览实现
|
|
|
|
|
+
|
|
|
|
|
+**使用Taro.previewImage:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
|
|
+import { Image, Text } from '@tarojs/components'
|
|
|
|
|
+
|
|
|
|
|
+interface DocumentPhotoItemProps {
|
|
|
|
|
+ type: string
|
|
|
|
|
+ url: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function DocumentPhotoItem({ type, url }: DocumentPhotoItemProps) {
|
|
|
|
|
+ const handlePreview = () => {
|
|
|
|
|
+ Taro.previewImage({
|
|
|
|
|
+ current: url, // 当前显示图片的http链接
|
|
|
|
|
+ urls: [url] // 需要预览的图片http链接列表
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View onClick={handlePreview} className="flex flex-col">
|
|
|
|
|
+ <Image
|
|
|
|
|
+ src={url}
|
|
|
|
|
+ mode="aspectFill"
|
|
|
|
|
+ className="w-full h-32 rounded-lg"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Text>{type}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Taro小程序布局规范
|
|
|
|
|
+
|
|
|
|
|
+**重要**: 在Taro小程序中,`<View>` 组件内的子元素默认是**横向布局**(`flex-row`),需要显式添加 `flex flex-col` 类才能实现**垂直布局**。
|
|
|
|
|
+
|
|
|
|
|
+**正确示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// ✅ 正确: 使用 flex flex-col 实现垂直布局
|
|
|
|
|
+<View className="flex flex-col">
|
|
|
|
|
+ <Text>姓名: 张三</Text>
|
|
|
|
|
+ <Text>性别: 男</Text>
|
|
|
|
|
+ <Text>年龄: 35</Text>
|
|
|
|
|
+</View>
|
|
|
|
|
+
|
|
|
|
|
+// ❌ 错误: 缺少 flex flex-col,子元素会横向排列
|
|
|
|
|
+<View>
|
|
|
|
|
+ <Text>姓名: 张三</Text>
|
|
|
|
|
+ <Text>性别: 男</Text>
|
|
|
|
|
+ <Text>年龄: 35</Text>
|
|
|
|
|
+</View>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**个人信息卡片示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { View, Text } from '@tarojs/components'
|
|
|
|
|
+
|
|
|
|
|
+export function PersonalBasicInfo({ personalInfo }: { personalInfo: PersonalInfoResponse }) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View className="bg-white rounded-lg p-4">
|
|
|
|
|
+ <Text className="text-lg font-semibold mb-4">个人基本信息</Text>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 垂直布局的信息列表 */}
|
|
|
|
|
+ <View className="flex flex-col space-y-2">
|
|
|
|
|
+ <View className="flex justify-between">
|
|
|
|
|
+ <Text className="text-gray-600">姓名</Text>
|
|
|
|
|
+ <Text>{personalInfo.name}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View className="flex justify-between">
|
|
|
|
|
+ <Text className="text-gray-600">性别</Text>
|
|
|
|
|
+ <Text>{personalInfo.gender}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View className="flex justify-between">
|
|
|
|
|
+ <Text className="text-gray-600">年龄</Text>
|
|
|
|
|
+ <Text>{personalInfo.age}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 更多字段... */}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键点**:
|
|
|
|
|
+1. 信息列表容器使用 `flex flex-col` 实现垂直布局
|
|
|
|
|
+2. 每个信息项使用 `flex justify-between` 实现标签和值的左右分布
|
|
|
|
|
+3. 使用 `space-y-2` 或 `space-y-3` 添加垂直间距
|
|
|
|
|
+4. **重要**: 记住在所有需要垂直排列的 View 上添加 `flex flex-col`
|
|
|
|
|
+
|
|
|
|
|
+### 技术约束
|
|
|
|
|
+
|
|
|
|
|
+1. **向后兼容**: 不影响现有mini-talent项目功能
|
|
|
|
|
+2. **类型安全**: 使用TypeScript严格模式,所有API调用必须有类型定义
|
|
|
|
|
+3. **模块独立性**: 每个UI包独立管理自己的API客户端和类型定义
|
|
|
|
|
+4. **测试覆盖**: 所有新增代码必须有测试覆盖
|
|
|
|
|
+5. **代码规范**: 遵循项目编码标准和UI包开发规范
|
|
|
|
|
+6. **数据安全**: 银行卡号、身份证号等敏感信息必须脱敏显示
|
|
|
|
|
+
|
|
|
|
|
+### 风险和缓解措施
|
|
|
|
|
+
|
|
|
|
|
+**主要风险:**
|
|
|
|
|
+1. **API集成风险**: 史诗015的API可能存在接口变更
|
|
|
|
|
+2. **数据脱敏风险**: 银行卡号、身份证号脱敏逻辑可能存在漏洞
|
|
|
|
|
+3. **照片预览风险**: Taro.previewImage在不同平台可能有兼容性问题
|
|
|
|
|
+4. **UI组件复用风险**: rencai系列UI包可能与现有yongren系列UI包存在差异
|
|
|
|
|
+
|
|
|
|
|
+**缓解措施:**
|
|
|
|
|
+1. **API验证**: 开发前验证API路径映射与实际后端路由定义的一致性
|
|
|
|
|
+2. **分阶段实现**: 先实现个人基本信息,再实现银行卡信息,最后实现证件照片
|
|
|
|
|
+3. **参考现有模式**: 参照yongren系列UI包的实现模式和架构
|
|
|
|
|
+4. **类型安全**: 使用RPC推断类型,避免类型不匹配问题
|
|
|
|
|
+5. **测试驱动**: 编写完整的测试,确保功能正确性
|
|
|
|
|
+6. **数据安全测试**: 测试各种边界情况,确保脱敏逻辑正确
|
|
|
|
|
+
|
|
|
|
|
+## 测试
|
|
|
|
|
+
|
|
|
|
|
+### 测试框架和模式
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [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/
|
|
|
|
|
+ ├── unit/ # 单元测试
|
|
|
|
|
+ │ └── components/
|
|
|
|
|
+ │ ├── PersonalBasicInfo.test.tsx
|
|
|
|
|
+ │ ├── BankCardItem.test.tsx
|
|
|
|
|
+ │ └── DocumentPhotoItem.test.tsx
|
|
|
|
|
+ └── pages/ # 页面组件测试
|
|
|
|
|
+ └── PersonalInfoPage/
|
|
|
|
|
+ └── PersonalInfoPage.test.tsx
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 测试要求
|
|
|
|
|
+
|
|
|
|
|
+1. **组件测试**:
|
|
|
|
|
+ - 测试组件渲染正确
|
|
|
|
|
+ - 测试用户交互(点击照片预览等)
|
|
|
|
|
+ - 测试API集成(使用Mock响应)
|
|
|
|
|
+ - 测试错误处理
|
|
|
|
|
+
|
|
|
|
|
+2. **脱敏逻辑测试**:
|
|
|
|
|
+ - 测试银行卡号脱敏功能
|
|
|
|
|
+ - 测试身份证号脱敏功能
|
|
|
|
|
+ - 测试各种边界情况(空值、短字符串等)
|
|
|
|
|
+
|
|
|
|
|
+3. **集成测试**:
|
|
|
|
|
+ - 测试个人信息页面的三个模块数据加载
|
|
|
|
|
+ - 测试Navbar导航栏的正确显示
|
|
|
|
|
+
|
|
|
|
|
+4. **回归测试**:
|
|
|
|
|
+ - 验证mini-talent项目现有功能不受影响
|
|
|
|
|
+ - 运行`pnpm typecheck`确保类型检查通过
|
|
|
|
|
+
|
|
|
|
|
+### 测试执行
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# 运行所有测试
|
|
|
|
|
+cd mini-ui-packages/rencai-personal-info-ui && pnpm test
|
|
|
|
|
+
|
|
|
|
|
+# 运行特定测试
|
|
|
|
|
+pnpm test --testNamePattern="PersonalInfoPage"
|
|
|
|
|
+
|
|
|
|
|
+# 生成覆盖率报告
|
|
|
|
|
+pnpm test:coverage
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 变更日志
|
|
|
|
|
+
|
|
|
|
|
+| 日期 | 版本 | 描述 | 作者 |
|
|
|
|
|
+|------|------|------|------|
|
|
|
|
|
+| 2025-12-26 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
|
|
|
|
|
+
|
|
|
|
|
+## 开发者记录
|
|
|
|
|
+
|
|
|
|
|
+*此部分由开发代理在实施过程中填写*
|
|
|
|
|
+
|
|
|
|
|
+### 使用的代理模型
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+### 调试日志引用
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+### 完成说明列表
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+### 文件列表
|
|
|
|
|
+
|
|
|
|
|
+待填写
|
|
|
|
|
+
|
|
|
|
|
+## QA结果
|
|
|
|
|
+
|
|
|
|
|
+*此部分由QA代理在审查完成后填写*
|