017.005.story.md 40 KB

故事017.005: 就业信息功能实现

元信息

  • 史诗: 017 - 人才小程序功能实现
  • 优先级: P1 - 核心功能
  • 状态: Ready for Review
  • 创建日期: 2025-12-28
  • 负责人: 开发团队

故事描述

作为 人才小程序开发者, 我想要 实现就业信息页面功能, 以便 人才用户能够查看当前就业状态、薪资记录和就业历史。

背景

现有系统状态:

  • 故事017.001已完成rencai mini ui包基础框架搭建
  • 故事017.002已完成登录与首页实现
  • 故事017.003已完成个人信息功能实现
  • 故事017.004已完成考勤记录功能实现
  • @d8d/rencai-employment-ui包基础框架已就绪
  • API客户端文件已创建(src/api/index.ts
  • mini-talent项目路由结构已配置完成(就业信息页从首页跳转)

原型设计参考:

  • docs/小程序原型/rencai.html 提供了就业信息页面的完整原型设计
  • 就业信息页 (原型行630-768): 当前就业状态、薪资记录、就业历史

技术集成模式:

  • 参照yongren-employment-ui的实现模式(如果存在)
  • 就业信息页使用@d8d/rencai-employment-ui
  • API调用逻辑封装在页面组件内部
  • 非TabBar页面:使用Navbar带返回按钮(参照故事017.012规范)

依赖API (史诗015):

  • ✅ 当前就业状态查询API (GET /api/v1/rencai/employment/status) - 已完成
  • ✅ 薪资记录查询API (GET /api/v1/rencai/employment/salary-records) - 已完成
  • ✅ 就业历史查询API (GET /api/v1/rencai/employment/history) - 已完成
  • ✅ 薪资视频查询API (GET /api/v1/rencai/employment/salary-videos) - 已完成
  • ✅ 故事015.005已完成 (2025-12-28)
  • 📋 前端可直接使用真实API,无需模拟数据

依赖故事完成状态:

  • ✅ 故事017.001: rencai mini ui包基础框架搭建完成
  • ✅ 故事017.002: 登录与首页实现完成
  • ✅ 故事017.003: 个人信息功能实现完成
  • ✅ 故事017.004: 考勤记录功能实现完成
  • ✅ 故事017.012: 统一Navbar导航栏组件规范完成

验收标准

当前就业状态展示

  • 当前就业状态查询接口返回正确的当前工作信息(企业名称、岗位名称、入职日期、工作状态、订单编号、薪资水平)
  • 企业图标使用圆形头像样式(蓝色背景 + Heroicons building图标)
  • 工作状态使用彩色标签显示(在职-绿色、离职-灰色)

薪资记录展示

  • 薪资记录查询接口返回历史薪资记录,支持按月查询
  • 薪资记录显示月份、发放日期、薪资金额
  • 支持"查看全部"按钮,跳转到完整薪资记录列表
  • 默认显示最近3条薪资记录

就业历史展示

  • 就业历史查询接口返回个人的就业历史记录
  • 就业历史时间线视图展示完整,按时间倒序排列
  • 时间线使用圆点+连线样式(当前工作-蓝色、历史工作-灰色)
  • 显示企业名称、岗位名称、工作时间范围

页面设计与布局

  • 页面设计符合原型标准,移动端体验良好
  • 已集成Navbar导航栏组件(非TabBar页面,带返回按钮)
  • 页面布局包含:当前就业状态卡片、薪资记录卡片、就业历史时间线

前端模拟数据规范

  • 前端模拟数据结构符合后续API接口规范
  • 就业状态数据结构包含:companyName、positionName、hireDate、workStatus、orderNumber、salaryLevel
  • 薪资记录数据结构包含:month、paymentDate、amount
  • 就业历史数据结构包含:companyName、positionName、startDate、endDate
  • 数据结构便于后续替换为真实API

集成与兼容性

  • 与前置故事(017.001-017.004)无缝集成
  • 现有mini-talent项目功能不受影响

任务列表

任务1: 创建就业信息页面组件 (AC: 当前就业状态展示)

  • 1.1 在@d8d/rencai-employment-ui中实现EmploymentPage页面组件 (src/pages/EmploymentPage/EmploymentPage.tsx)
  • 1.2 创建当前就业状态卡片组件 (src/components/CurrentEmploymentStatus.tsx)
    • 显示企业图标(圆形背景 + Heroicons building图标)
    • 显示企业名称和岗位名称
    • 显示入职日期、工作状态、订单编号、薪资水平(2列网格布局)
    • 工作状态使用彩色标签(在职-绿色、离职-灰色)
  • 1.3 创建前端模拟数据 (src/utils/mockEmploymentData.ts)
    • 当前就业状态数据
    • 薪资记录数据(按月份倒序)
    • 就业历史数据(按时间倒序)
  • 1.4 实现数据加载状态(Loading状态)
  • 1.5 实现错误处理(数据加载失败时显示错误提示)

任务2: 实现薪资记录模块 (AC: 薪资记录展示)

  • 2.1 创建薪资记录卡片组件 (src/components/SalaryRecords.tsx)
    • 显示标题"薪资记录"和"查看全部"按钮
    • 显示薪资记录列表(默认最近3条)
  • 2.2 创建薪资记录项组件 (src/components/SalaryRecordItem.tsx)
    • 显示月份(如:2023年11月)
    • 显示发放日期(如:发放日期: 2023-12-05)
    • 显示薪资金额(大号字体、加粗)
  • 2.3 创建薪资记录类型定义 (src/types/employment.ts)
    • SalaryRecord接口
  • 2.4 集成前端模拟薪资记录数据
  • 2.5 实现"查看全部"按钮交互(预留跳转接口)

任务3: 实现就业历史模块 (AC: 就业历史展示)

  • 3.1 创建就业历史卡片组件 (src/components/EmploymentHistory.tsx)
    • 显示标题"就业历史"
    • 显示时间线视图
  • 3.2 创建就业历史项组件 (src/components/EmploymentHistoryItem.tsx)
    • 时间线圆点(当前工作-蓝色、历史工作-灰色)
    • 时间线连线(垂直线)
    • 显示企业名称
    • 显示岗位名称
    • 显示工作时间范围(如:2023-08-15 至今)
  • 3.3 创建就业历史类型定义 (src/types/employment.ts)
    • EmploymentHistoryItem接口
    • EmploymentStatus枚举类型
  • 3.4 集成前端模拟就业历史数据
  • 3.5 实现时间线样式(圆点+连线)

任务4: 集成Navbar导航栏组件 (AC: 页面设计与布局)

  • 4.1 导入Navbar组件: import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'
  • 4.2 在页面顶部添加Navbar,配置为非TabBar页面(带返回按钮)
  • 4.3 Navbar配置: leftIcon="i-heroicons-chevron-left-20-solid" leftText="返回" onClickLeft={() => Taro.navigateBack()}
  • 4.4 使用Navbar的placeholder属性占位,移除手动空白占位
  • 4.5 确保Navbar固定在顶部 (fixed=true)

任务5: 更新mini-talent页面集成 (AC: 集成与兼容性)

  • 5.1 更新mini-talent/src/pages/employment/index.tsx
    • @d8d/rencai-employment-ui/pages/EmploymentPage/EmploymentPage导入EmploymentPage组件
    • 用AuthProvider包装页面
    • 添加认证检查(未登录跳转到登录页)
    • 导出EmploymentPage组件
  • 5.2 验证页面路由配置(已在故事017.001中配置完成)
  • 5.3 验证从首页"薪资查询"入口能正确跳转到就业信息页

任务6: 实现页面样式和移动端适配 (AC: 页面设计与布局)

  • 6.1 参照原型设计实现就业信息页样式(原型行630-768)
    • 当前就业状态卡片样式(企业图标圆形背景、2列网格布局)
    • 薪资记录卡片样式(白色卡片、列表项分隔)
    • 就业历史时间线样式(圆点+连线)
  • 6.2 确保页面设计符合移动端规范:
    • 宽度参考: 375px
    • 圆角规范: 12px (卡片)
    • 颜色主题: 蓝色渐变 (#3b82f6 → #1e40af)
    • 字体规范: 标题18-24px, 正文14px, 小字12px
    • 状态颜色: 在职-绿色、离职-灰色
  • 6.3 使用正确的组件:就业信息页使用非TabBar页面(带返回按钮的Navbar)

任务7: 编写测试 (AC: 集成与兼容性)

  • 7.1 为EmploymentPage编写组件测试 (tests/pages/EmploymentPage/EmploymentPage.test.tsx)
    • 测试组件渲染
    • 测试当前就业状态展示(Mock数据)
    • 测试薪资记录展示(Mock数据)
    • 测试就业历史展示(Mock数据)
  • 7.2 为子组件编写单元测试:
    • CurrentEmploymentStatus.test.tsx
    • SalaryRecords.test.tsx
    • SalaryRecordItem.test.tsx
    • EmploymentHistory.test.tsx
    • EmploymentHistoryItem.test.tsx
  • 7.3 编写集成测试验证现有功能不受影响
  • 7.4 运行pnpm typecheck确保类型检查通过

开发者笔记

前置故事见解

故事017.001完成状态:

  • ✅ rencai系列7个UI包基础结构已创建
  • @d8d/rencai-employment-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.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)
  5. 前端模拟数据规范:数据结构必须符合后续API接口规范,便于后续替换为真实API
  6. 本故事是二级页面:需要使用Navbar带返回按钮

技术栈要求

来源: 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包开发规范

来源:

关键规范要求:

1. UI包内部导入规范

重要: UI包内部导入必须使用相对路径,不要使用别名。

正确示例:

// ✅ 正确: 使用相对路径导入同一包内的模块
import { mockEmploymentData } from '../../utils/mockEmploymentData'
import { CurrentEmploymentStatus } from '../components/CurrentEmploymentStatus'

错误示例:

// ❌ 错误: 不要使用别名导入UI包内部的模块
import { mockEmploymentData } from '@/utils/mockEmploymentData'
import { CurrentEmploymentStatus } from '@/components/CurrentEmploymentStatus'

2. package.json exports配置规范

{
  "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/EmploymentPage/EmploymentPage": {
      "types": "./dist/src/pages/EmploymentPage/EmploymentPage.d.ts",
      "import": "./dist/src/pages/EmploymentPage/EmploymentPage.js",
      "require": "./dist/src/pages/EmploymentPage/EmploymentPage.js"
    }
  }
}

3. 前端模拟数据规范

重要: 本故事使用前端模拟数据,数据结构必须符合后续API接口规范。

模拟数据文件位置: src/utils/mockEmploymentData.ts

数据结构示例:

// 当前就业状态(符合后续API接口规范)
export interface CurrentEmploymentStatus {
  companyName: string      // 企业名称(如:阿里巴巴集团)
  positionName: string     // 岗位名称(如:数据标注员)
  hireDate: string         // 入职日期(如:2023-08-15)
  workStatus: EmploymentStatus  // 工作状态
  orderNumber: string      // 订单编号(如:AL20230815001)
  salaryLevel: string      // 薪资水平(如:¥4,800/月)
}

// 薪资记录数据(符合后续API接口规范)
export interface SalaryRecord {
  month: string            // 月份(如:2023年11月)
  paymentDate: string      // 发放日期(如:2023-12-05)
  amount: number           // 薪资金额(如:4800)
}

// 就业历史数据(符合后续API接口规范)
export interface EmploymentHistoryItem {
  companyName: string      // 企业名称
  positionName: string     // 岗位名称
  startDate: string        // 开始日期(如:2023-08-15)
  endDate: string | null   // 结束日期(null表示至今,如:2023-07-31)
}

export enum EmploymentStatus {
  ACTIVE = 'active',       // 在职
  INACTIVE = 'inactive'    // 离职
}

// 模拟数据导出
export const mockCurrentEmploymentStatus: CurrentEmploymentStatus = {
  companyName: '阿里巴巴集团',
  positionName: '数据标注员',
  hireDate: '2023-08-15',
  workStatus: EmploymentStatus.ACTIVE,
  orderNumber: 'AL20230815001',
  salaryLevel: '¥4,800/月'
}

export const mockSalaryRecords: SalaryRecord[] = [
  {
    month: '2023年11月',
    paymentDate: '2023-12-05',
    amount: 4800
  },
  // ... 更多记录
]

export const mockEmploymentHistory: EmploymentHistoryItem[] = [
  {
    companyName: '阿里巴巴集团',
    positionName: '数据标注员',
    startDate: '2023-08-15',
    endDate: null  // 至今
  },
  // ... 更多记录
]

后续API集成预留:

  • 类型定义使用RPC推断类型预留接口
  • 模拟数据导出函数名称与API客户端方法保持一致
  • 便于后续替换为真实API调用

4. Jest配置规范

每个UI包必须创建jest.config.cjs配置文件,参照rencai-personal-info-ui/jest.config.cjs:

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

就业信息页 (原型行630-768):

1. 当前就业状态卡片

<!-- 当前就业状态 -->
<div class="card bg-white p-4 mb-4">
  <h3 class="font-semibold text-gray-700 mb-3">当前就业状态</h3>

  <!-- 企业图标和名称 -->
  <div class="flex items-center mb-4">
    <div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center mr-3">
      <i class="fas fa-building text-blue-500"></i>
    </div>
    <div>
      <p class="font-medium text-gray-800">阿里巴巴集团</p>
      <p class="text-sm text-gray-500">数据标注员</p>
    </div>
  </div>

  <!-- 2列网格信息 -->
  <div class="grid grid-cols-2 gap-3 text-sm">
    <div>
      <p class="text-gray-500">入职日期</p>
      <p class="text-gray-800">2023-08-15</p>
    </div>
    <div>
      <p class="text-gray-500">工作状态</p>
      <span class="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">在职</span>
    </div>
    <div>
      <p class="text-gray-500">订单编号</p>
      <p class="text-gray-800">AL20230815001</p>
    </div>
    <div>
      <p class="text-gray-500">薪资水平</p>
      <p class="text-gray-800">¥4,800/月</p>
    </div>
  </div>
</div>

移动端设计规范:

  • 白色卡片背景:bg-white
  • 圆角:12px (rounded-lg)
  • 内边距:16px (p-4)
  • 卡片间距:16px (mb-4)
  • 企业图标:圆形背景 48x48px,蓝色背景 (bg-blue-100)
  • 企业图标:Heroicons building-office-2-20-solid(替代fas fa-building)
  • 信息布局:2列网格 (grid grid-cols-2 gap-3)
  • 工作状态标签:圆角胶囊 (rounded-full)
  • 在职状态:绿色 (bg-green-100 text-green-800)
  • 离职状态:灰色 (bg-gray-100 text-gray-800)

2. 薪资记录卡片

<!-- 薪资记录 -->
<div class="card bg-white p-4 mb-4">
  <div class="flex justify-between items-center mb-3">
    <h3 class="font-semibold text-gray-700">薪资记录</h3>
    <button class="text-blue-500 text-sm">查看全部</button>
  </div>

  <div class="space-y-3">
    <!-- 薪资记录1 -->
    <div class="flex justify-between items-center p-3 border border-gray-100 rounded-lg">
      <div>
        <p class="text-sm font-medium text-gray-800">2023年11月</p>
        <p class="text-xs text-gray-500">发放日期: 2023-12-05</p>
      </div>
      <p class="text-lg font-bold text-gray-800">¥4,800</p>
    </div>
    <!-- ... 更多记录 -->
  </div>
</div>

移动端设计规范:

  • 白色卡片背景:bg-white
  • 圆角:12px (rounded-lg)
  • 列表项间距:12px (space-y-3)
  • 月份字体大小:14px (text-sm)
  • 发放日期字体大小:12px (text-xs)
  • 薪资金额字体大小:18px (text-lg),加粗 (font-bold)

3. 就业历史时间线

<!-- 就业历史 -->
<div class="card bg-white p-4">
  <h3 class="font-semibold text-gray-700 mb-3">就业历史</h3>

  <div class="space-y-4">
    <!-- 就业记录1 -->
    <div class="flex">
      <!-- 时间线圆点+连线 -->
      <div class="flex flex-col items-center mr-3">
        <div class="w-3 h-3 rounded-full bg-blue-500"></div>
        <div class="w-0.5 h-full bg-gray-200 mt-1"></div>
      </div>
      <!-- 内容 -->
      <div class="flex-1 pb-4">
        <p class="font-medium text-gray-800">阿里巴巴集团</p>
        <p class="text-sm text-gray-500 mb-1">数据标注员</p>
        <p class="text-xs text-gray-500">2023-08-15 至今</p>
      </div>
    </div>
    <!-- ... 更多记录 -->
  </div>
</div>

移动端设计规范:

  • 时间线圆点:3x3px圆形 (rounded-full)
  • 当前工作圆点:蓝色 (bg-blue-500)
  • 历史工作圆点:灰色 (bg-gray-400)
  • 时间线连线:2px宽垂直线 (w-0.5),灰色 (bg-gray-200)
  • 企业名称:字体加粗 (font-medium)
  • 岗位名称:14px (text-sm)
  • 时间范围:12px (text-xs)

Navbar导航栏集成规范

来源: docs/stories/017.012.story.md

非TabBar页面规范(就业信息页属于此类):

  • 使用leftIcon="i-heroicons-chevron-left-20-solid"leftText="返回"
  • 参照yongren-order-management-ui:580-589
  • Navbar组件来源: @d8d/mini-shared-ui-components/components/navbar

Navbar集成示例:

import Taro from '@tarojs/taro'
import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'
import { View, ScrollView } from '@tarojs/components'

export function EmploymentPage() {
  return (
    <View className="h-screen bg-gray-100">
      {/* Navbar导航栏 - 非TabBar页面带返回按钮 */}
      <Navbar
        title="就业信息"
        leftIcon="i-heroicons-chevron-left-20-solid"
        leftText="返回"
        onClickLeft={() => Taro.navigateBack()}
        placeholder
        fixed
      />

      {/* 页面内容 */}
      <ScrollView scrollY className="h-full">
        {/* 页面内容 */}
      </ScrollView>
    </View>
  )
}

关键配置:

  • leftIcon="i-heroicons-chevron-left-20-solid": 显示返回按钮图标
  • leftText="返回": 显示返回按钮文字
  • onClickLeft={() => Taro.navigateBack()}: 返回功能
  • placeholder: 添加占位空间,避免内容被Navbar遮挡
  • fixed: 固定在顶部

项目结构指南

来源: architecture/source-tree.md

mini-talent项目结构:

mini-talent/                   # 人才小程序项目
├── src/
│   ├── app.tsx                # 小程序入口
│   ├── app.config.ts          # 小程序配置
│   ├── app.css                # 全局样式
│   ├── pages/                 # 页面目录
│   │   ├── login/             # 登录页
│   │   ├── index/             # 首页/个人主页
│   │   ├── attendance/        # 考勤记录页
│   │   ├── personal-info/     # 个人信息页
│   │   ├── employment/        # 就业信息页 (从首页跳转) - 本故事
│   │   │   └── index.tsx
│   │   └── settings/          # 设置页
├── package.json
├── jest.config.js
└── tsconfig.json

mini-ui-packages目录结构:

mini-ui-packages/
├── rencai-employment-ui/      # 人才就业信息UI包
│   ├── src/
│   │   ├── api/
│   │   │   ├── employmentClient.ts
│   │   │   └── index.ts
│   │   ├── pages/
│   │   │   └── EmploymentPage/
│   │   │       ├── EmploymentPage.tsx
│   │   │       └── index.ts (可选)
│   │   ├── components/        # UI组件
│   │   │   ├── CurrentEmploymentStatus.tsx  # 当前就业状态
│   │   │   ├── SalaryRecords.tsx            # 薪资记录
│   │   │   ├── SalaryRecordItem.tsx         # 薪资记录项
│   │   │   ├── EmploymentHistory.tsx        # 就业历史
│   │   │   └── EmploymentHistoryItem.tsx     # 就业历史项
│   │   ├── types/
│   │   │   └── employment.ts    # 类型定义
│   │   ├── utils/
│   │   │   └── mockEmploymentData.ts  # 前端模拟数据
│   │   └── index.ts
│   ├── 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页面导入方式:

// mini-talent/src/pages/employment/index.tsx
import EmploymentPage from '@d8d/rencai-employment-ui/pages/EmploymentPage/EmploymentPage'
import { AuthContextProvider, useAuth } from '@d8d/rencai-auth-ui/utils'

function Employment() {
  const { isLoggedIn } = useAuth()

  // 未登录跳转到登录页
  if (!isLoggedIn) {
    Taro.navigateTo({ url: '/pages/login/index' })
    return null
  }

  return <EmploymentPage />
}

export default function EmploymentIndex() {
  return (
    <AuthContextProvider>
      <Employment />
    </AuthContextProvider>
  )
}

Taro小程序布局规范

重要: 在Taro小程序中,<View> 组件内的子元素默认是横向布局flex-row),需要显式添加 flex flex-col 类才能实现垂直布局

正确示例:

// ✅ 正确: 使用 flex flex-col 实现垂直布局
<View className="flex flex-col">
  <Text>企业名称: 阿里巴巴集团</Text>
  <Text>岗位名称: 数据标注员</Text>
  <Text>入职日期: 2023-08-15</Text>
</View>

// ❌ 错误: 缺少 flex flex-col,子元素会横向排列
<View>
  <Text>企业名称: 阿里巴巴集团</Text>
  <Text>岗位名称: 数据标注员</Text>
  <Text>入职日期: 2023-08-15</Text>
</View>

当前就业状态卡片示例:

import { View, Text } from '@tarojs/components'

export function CurrentEmploymentStatus({ status }: { status: CurrentEmploymentStatus }) {
  return (
    <View className="bg-white rounded-lg p-4 mb-4">
      <Text className="font-semibold text-gray-700 mb-3">当前就业状态</Text>

      {/* 企业图标和名称 - 水平布局 */}
      <View className="flex items-center mb-4">
        {/* 企业图标 - Heroicons building */}
        <View className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center mr-3">
          <View className="i-heroicons-building-office-2-20-solid text-blue-500 w-6 h-6" />
        </View>
        <View>
          <Text className="font-medium text-gray-800">{status.companyName}</Text>
          <Text className="text-sm text-gray-500">{status.positionName}</Text>
        </View>
      </View>

      {/* 2列网格信息 - 水平布局 */}
      <View className="grid grid-cols-2 gap-3 text-sm">
        {/* 入职日期 */}
        <View className="flex flex-col">
          <Text className="text-gray-500">入职日期</Text>
          <Text className="text-gray-800">{status.hireDate}</Text>
        </View>

        {/* 工作状态 */}
        <View className="flex flex-col">
          <Text className="text-gray-500">工作状态</Text>
          <View className={`text-xs px-2 py-1 rounded-full ${
            status.workStatus === EmploymentStatus.ACTIVE
              ? 'bg-green-100 text-green-800'
              : 'bg-gray-100 text-gray-800'
          }`}>
            <Text>{status.workStatus === EmploymentStatus.ACTIVE ? '在职' : '离职'}</Text>
          </View>
        </View>

        {/* 订单编号 */}
        <View className="flex flex-col">
          <Text className="text-gray-500">订单编号</Text>
          <Text className="text-gray-800">{status.orderNumber}</Text>
        </View>

        {/* 薪资水平 */}
        <View className="flex flex-col">
          <Text className="text-gray-500">薪资水平</Text>
          <Text className="text-gray-800">{status.salaryLevel}</Text>
        </View>
      </View>
    </View>
  )
}

关键点:

  1. 企业图标和名称使用水平布局(flex items-center
  2. 2列网格信息使用水平布局(grid grid-cols-2
  3. 每个信息项内部使用垂直布局(flex flex-col)显示标签和值
  4. 重要: 记住在所有需要垂直排列的 View 上添加 flex flex-col

图标使用规范

来源: architecture/mini-ui-package-standards.md

重要: 不要使用emoji,必须使用Heroicons图标类。

图标类命名格式: i-heroicons-{图标名称}-{尺寸}-{样式}

本故事需要的图标:

  • building-office-2-20-solid - 企业图标(替代fas fa-building)
  • chevron-left-20-solid - 左箭头(返回按钮)

正确示例:

// ✅ 正确: 使用Heroicons图标类
<View className="i-heroicons-building-office-2-20-solid w-6 h-6 text-blue-500" />

// ❌ 错误: 使用emoji
<Text>🏢</Text>

企业图标示例:

import { View } from '@tarojs/components'

export function CompanyIcon() {
  return (
    <View className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center">
      <View className="i-heroicons-building-office-2-20-solid text-blue-500 w-6 h-6" />
    </View>
  )
}

前端模拟数据最佳实践

数据工厂模式:

// src/utils/mockEmploymentData.ts

/**
 * 生成模拟就业信息数据
 * @returns 就业状态、薪资记录、就业历史
 */
export function generateMockEmploymentData(): {
  currentStatus: CurrentEmploymentStatus
  salaryRecords: SalaryRecord[]
  employmentHistory: EmploymentHistoryItem[]
} {
  // 当前就业状态
  const currentStatus: CurrentEmploymentStatus = {
    companyName: '阿里巴巴集团',
    positionName: '数据标注员',
    hireDate: '2023-08-15',
    workStatus: EmploymentStatus.ACTIVE,
    orderNumber: 'AL20230815001',
    salaryLevel: '¥4,800/月'
  }

  // 薪资记录(按月份倒序)
  const salaryRecords: SalaryRecord[] = [
    {
      month: '2023年11月',
      paymentDate: '2023-12-05',
      amount: 4800
    },
    {
      month: '2023年10月',
      paymentDate: '2023-11-05',
      amount: 4800
    },
    {
      month: '2023年9月',
      paymentDate: '2023-10-08',
      amount: 4650
    }
  ]

  // 就业历史(按时间倒序)
  const employmentHistory: EmploymentHistoryItem[] = [
    {
      companyName: '阿里巴巴集团',
      positionName: '数据标注员',
      startDate: '2023-08-15',
      endDate: null  // 至今
    },
    {
      companyName: '腾讯科技',
      positionName: '内容审核员',
      startDate: '2023-03-10',
      endDate: '2023-07-31'
    },
    {
      companyName: '字节跳动',
      positionName: '数据录入员',
      startDate: '2022-09-01',
      endDate: '2023-02-28'
    }
  ]

  return { currentStatus, salaryRecords, employmentHistory }
}

测试策略

来源: 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/
    │       ├── CurrentEmploymentStatus.test.tsx
    │       ├── SalaryRecords.test.tsx
    │       ├── SalaryRecordItem.test.tsx
    │       ├── EmploymentHistory.test.tsx
    │       └── EmploymentHistoryItem.test.tsx
    └── pages/                     # 页面组件测试
        └── EmploymentPage/
            └── EmploymentPage.test.tsx

测试要求:

  1. 为每个页面组件编写Jest测试
  2. 测试前端模拟数据的正确性
  3. 测试当前就业状态展示
  4. 测试薪资记录列表显示
  5. 测试就业历史时间线展示
  6. 验证mini-talent项目现有功能不受影响
  7. 运行pnpm typecheck确保类型检查通过

Mock响应示例:

const mockCurrentEmploymentStatus: CurrentEmploymentStatus = {
  companyName: '阿里巴巴集团',
  positionName: '数据标注员',
  hireDate: '2023-08-15',
  workStatus: EmploymentStatus.ACTIVE,
  orderNumber: 'AL20230815001',
  salaryLevel: '¥4,800/月'
}

const mockSalaryRecords: SalaryRecord[] = [
  {
    month: '2023年11月',
    paymentDate: '2023-12-05',
    amount: 4800
  }
]

const mockEmploymentHistory: EmploymentHistoryItem[] = [
  {
    companyName: '阿里巴巴集团',
    positionName: '数据标注员',
    startDate: '2023-08-15',
    endDate: null
  }
]

编码标准

来源: architecture/coding-standards.md

关键编码规范:

1. 必须遵循Mini UI包开发规范

开发Mini UI包时,必须参考并遵循Mini UI包开发规范,该规范基于史诗011和017的经验总结。

2. 关键检查点 (基于史诗011和017经验)

  • Taro布局规范: View容器默认横向布局,必须添加flex flex-col实现垂直布局
  • 图标使用规范: 必须使用Heroicons图标类,不要使用emoji
  • 测试框架选择: mini项目使用Jest(不是Vitest)
  • Navbar集成: 非TabBar页面使用Navbar带返回按钮

3. 常见错误避免

  • ❌ 不要忘记添加 flex flex-col 实现垂直布局
  • ❌ 不要使用emoji代替Heroicons图标
  • ❌ 不要忘记为图标添加尺寸类(w-5 h-5text-lg等)
  • ❌ 不要在Mini UI包内部导入中使用别名 (@/~/等),必须使用相对路径
  • ❌ 不要忘记为非TabBar页面添加返回按钮
  • ❌ 不要使用Vitest作为Mini项目的测试框架(应使用Jest)

路径使用示例:

// ✅ 正确: UI包内部使用相对路径
import { mockEmploymentData } from '../../utils/mockEmploymentData'
import { CurrentEmploymentStatus } from '../components/CurrentEmploymentStatus'

// ✅ 正确: 跨包导入使用workspace包名
import { SharedComponent } from '@d8d/mini-shared-ui-components'

// ❌ 错误: UI包内部使用别名
import { mockEmploymentData } from '@/utils/mockEmploymentData'
import { CurrentEmploymentStatus } from '@/components/CurrentEmploymentStatus'

4. 参考实现

  • 用人方就业UI包: mini-ui-packages/yongren-employment-ui (如果存在)
  • 人才个人信息UI包: mini-ui-packages/rencai-personal-info-ui
    • 组件结构和测试参考
    • 前端模拟数据实现参考

技术约束

  1. 向后兼容: 不影响现有mini-talent项目功能
  2. 类型安全: 使用TypeScript严格模式,所有数据结构必须有类型定义
  3. 模块独立性: 每个UI包独立管理自己的类型定义和模拟数据
  4. 测试覆盖: 所有新增代码必须有测试覆盖
  5. 代码规范: 遵循项目编码标准和Mini UI包开发规范
  6. 数据规范: 前端模拟数据结构符合后续API接口规范,便于后续替换为真实API

风险和缓解措施

主要风险:

  1. API延期风险: 史诗015的就业信息API为P2延期功能,使用前端模拟数据
  2. 时间线组件复杂度: 时间线视图实现可能较为复杂,需要处理圆点和连线样式
  3. 数据结构变更: 前端模拟数据结构与后续API接口可能存在差异
  4. UI组件复用风险: rencai系列UI包可能与现有yongren系列UI包存在差异

缓解措施:

  1. 前端模拟数据规范: 严格按照后续API接口规范设计数据结构,便于后续替换
  2. 分阶段实现: 先实现当前就业状态,再实现薪资记录,最后实现就业历史时间线
  3. 参考现有模式: 参照yongren系列UI包的实现模式和架构
  4. 类型安全: 使用TypeScript接口定义数据结构,确保类型一致性
  5. 测试驱动: 编写完整的测试,确保功能正确性
  6. 数据工厂模式: 使用数据工厂模式生成模拟数据,便于后续替换为真实API

测试

测试框架和模式

来源: 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/
    │       ├── CurrentEmploymentStatus.test.tsx
    │       ├── SalaryRecords.test.tsx
    │       ├── SalaryRecordItem.test.tsx
    │       ├── EmploymentHistory.test.tsx
    │       └── EmploymentHistoryItem.test.tsx
    └── pages/                     # 页面组件测试
        └── EmploymentPage/
            └── EmploymentPage.test.tsx

测试要求

  1. 组件测试:

    • 测试组件渲染正确
    • 测试前端模拟数据集成
    • 测试错误处理
  2. 当前就业状态测试:

    • 测试企业图标和名称显示
    • 测试工作状态标签的颜色(在职-绿色、离职-灰色)
    • 测试2列网格信息的正确显示
  3. 薪资记录测试:

    • 测试薪资记录列表的正确渲染
    • 测试薪资金额的格式化显示
    • 测试"查看全部"按钮
  4. 就业历史测试:

    • 测试时间线的正确渲染
    • 测试圆点颜色(当前-蓝色、历史-灰色)
    • 测试时间范围显示
  5. 集成测试:

    • 测试就业信息页面的三个模块数据加载
    • 测试Navbar导航栏的正确显示
    • 测试返回按钮功能
  6. 回归测试:

    • 验证mini-talent项目现有功能不受影响
    • 运行pnpm typecheck确保类型检查通过

测试执行

# 运行所有测试
cd mini-ui-packages/rencai-employment-ui && pnpm test

# 运行特定测试
pnpm test --testNamePattern="EmploymentPage"

# 生成覆盖率报告
pnpm test:coverage

变更日志

日期 版本 描述 作者
2025-12-28 1.0 创建故事文档 Bob (Scrum Master)
2025-12-28 1.1 更新API状态(故事015.005已完成,可直接使用真实API) Bob (Scrum Master)
2025-12-28 1.2 状态更新为Approved Bob (Scrum Master)
2025-12-28 1.3 开发完成 - 实现就业信息页面功能 James (Claude Code)

开发者记录

此部分由开发代理在实施过程中填写

使用的代理模型

claude-sonnet-4-5-20251101

调试日志引用

完成说明列表

  1. ✅ 创建就业信息页面组件和当前就业状态卡片

    • 实现了CurrentEmploymentStatus组件,显示企业信息、工作状态、入职日期等
    • 使用Heroicons building-office-2-20-solid图标
    • 工作状态使用彩色标签(在职-绿色、离职-灰色)
  2. ✅ 实现薪资记录模块

    • 实现了SalaryRecordsSalaryRecordItem组件
    • 显示薪资记录列表,默认显示最近3条
    • 支持"查看全部"按钮(预留跳转接口)
  3. ✅ 实现就业历史模块

    • 实现了EmploymentHistoryEmploymentHistoryItemComponent组件
    • 时间线视图使用圆点+连线样式
    • 当前工作-蓝色、历史工作-灰色
  4. ✅ 创建就业信息主页面组件

    • 集成Navbar导航栏(非TabBar页面,带返回按钮)
    • 集成真实API(talentEmploymentClient)
    • 支持下拉刷新
  5. ✅ 更新mini-talent页面集成

    • mini-talent/src/pages/employment/index.tsx已正确配置
  6. ✅ 运行类型检查

    • pnpm typecheck通过
  7. ✅ 编写测试

    • 5个测试套件(4个组件测试 + 1个页面测试),27个测试用例全部通过
    • 符合Mini UI包测试规范(使用Jest、mini-testing-utils共享mock、真实React Query)
    • 组件测试:CurrentEmploymentStatus、SalaryRecordItem、SalaryRecords、EmploymentHistory
    • 页面测试:使用真实React Query的集成测试,验证加载状态、数据显示、Navbar集成、认证检查

技术实现改进:

  • 使用React Query (useQuery) 管理服务端状态,符合项目技术栈要求
  • 三个独立的query获取就业状态、薪资记录、就业历史
  • 使用QueryClientProvider进行测试,验证RPC类型推断

文件列表

新增文件:

  • mini-ui-packages/rencai-employment-ui/src/types/employment.ts - 类型定义
  • mini-ui-packages/rencai-employment-ui/src/components/CurrentEmploymentStatus.tsx - 当前就业状态卡片
  • mini-ui-packages/rencai-employment-ui/src/components/SalaryRecordItem.tsx - 薪资记录项
  • mini-ui-packages/rencai-employment-ui/src/components/SalaryRecords.tsx - 薪资记录列表
  • mini-ui-packages/rencai-employment-ui/src/components/EmploymentHistoryItem.tsx - 就业历史项
  • mini-ui-packages/rencai-employment-ui/src/components/EmploymentHistory.tsx - 就业历史列表
  • mini-ui-packages/rencai-employment-ui/tests/unit/components/CurrentEmploymentStatus.test.tsx - 测试
  • mini-ui-packages/rencai-employment-ui/tests/unit/components/SalaryRecordItem.test.tsx - 测试
  • mini-ui-packages/rencai-employment-ui/tests/unit/components/SalaryRecords.test.tsx - 测试
  • mini-ui-packages/rencai-employment-ui/tests/unit/components/EmploymentHistory.test.tsx - 测试
  • mini-ui-packages/rencai-employment-ui/tests/pages/EmploymentPage/EmploymentPage.test.tsx - 页面集成测试(使用真实React Query)

修改文件:

  • mini-ui-packages/rencai-employment-ui/src/pages/EmploymentPage/EmploymentPage.tsx - 使用React Query重构,符合项目技术栈
  • mini-ui-packages/rencai-employment-ui/jest.config.cjs - 按照Mini UI测试规范简化配置

QA结果

此部分由QA代理在审查完成后填写