011.005.story.md 24 KB

故事 011.005:数据统计功能实现

状态

In Progress

故事

作为企业用户, 我希望查看企业残疾人就业的数据统计图表, 以便进行数据分析和决策支持。

验收标准

  1. 数据统计页展示完整的统计图表集合
  2. 残疾类型分布图表正确显示,数据准确
  3. 性别分布、年龄分布、户籍省份分布图表功能正常
  4. 支持数据筛选(时间范围、部门等)和图表交互
  5. 页面设计符合原型标准,图表加载性能良好

任务 / 子任务

  • 任务1:实现数据统计页面框架(AC:1)
    • 创建数据统计页面组件,使用基础布局组件
    • 设计图表布局(网格或选项卡布局)
    • 实现页面标题和筛选条件区域
    • 添加图表加载状态和错误提示
  • 任务2:实现残疾类型分布图表(AC:2)
    • 集成数据统计API(@d8d/allin-statistics-module提供)
    • 实现饼图或环形图展示残疾类型分布
    • 添加图例和百分比显示
    • 支持图表点击交互(查看详情)
    • 添加数据表格展示(可选)
  • 任务3:实现人口统计图表组(AC:3)
    • 实现性别分布图表(饼图或柱状图)
    • 实现年龄分布图表(柱状图,按年龄段分组)
    • 实现户籍省份分布图表(地图或条形图)
    • 添加图表联动和筛选功能
    • 优化多图表同时加载的性能
  • 任务4:实现数据筛选和交互功能(AC:4)
    • 添加时间范围筛选器(年、月、季度)
    • 添加部门/岗位筛选器(如有多部门)
    • 实现图表数据下钻功能(如点击省份查看城市分布)
    • 添加数据导出功能(图表图片、数据表格)
    • 实现图表类型切换(如柱状图/折线图切换)
  • 任务5:优化性能和用户体验(AC:5)
    • 参考原型设计:docs/小程序原型/yongren.html中的数据统计页面
    • 实现图表懒加载和渐进式渲染
    • 添加图表数据缓存机制
    • 优化移动端图表显示和交互
    • 确保页面加载速度符合性能要求
  • 任务6:集成Navbar导航栏组件(页面层级结构规范)
    • 数据统计页:集成Navbar组件,标题"数据统计",隐藏左侧返回按钮(主页面配置)
    • 导入Navbar组件:import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'
    • 配置navbar:title="数据统计"leftIcon=""leftText=""
    • 保持YongrenTabBarLayout包裹,activeTab="statistics"
    • 调整ScrollView布局:px-4 pb-4 pt-0(移除顶部内边距适配navbar占位)
    • 统一页面层级结构:主页面使用YongrenTabBarLayout+Navbar(无返回)
    • 验证类型检查:确保所有页面类型检查通过
  • 任务7:编写集成测试
    • 编写图表数据加载测试
    • 测试数据筛选功能
    • 测试图表交互功能
    • 性能测试(大数据量图表渲染)

开发笔记

依赖关系

依赖故事

  • 011.001(基础框架搭建):提供API客户端、路由、基础布局、企业认证框架
  • 011.002(认证与首页):提供认证状态管理,Navbar组件集成规范
  • 011.003(人才管理):统计基于人才数据,页面层级结构规范
  • 011.004(订单管理):统计可能涉及订单数据,Navbar集成和页面结构规范

API规范

数据统计API(statistics模块):

  • 实现状态:后端@d8d/allin-statistics-module已实现6个分布统计API,均为企业专用版本。statistics路由已在server包注册(路径前缀/api/v1/yongren/statistics),API实际可用。前端@d8d/yongren-statistics-ui包目前只有占位符API客户端,需要按照故事实现完整的API客户端集成。
  • 架构说明:按照史诗011的mini-ui-packages架构,API客户端在各UI包内创建。数据统计API客户端应在@d8d/yongren-statistics-ui包内创建,而非在mini/src/api.ts中统一注册。
  • 后端路由类型:从@d8d/allin-statistics-module导入statisticsRoutes类型定义
  • 路径前缀/api/v1/yongren/statistics(企业专用版本,通过enterpriseAuthMiddleware中间件保护)
  • 主要接口(6个分布统计接口,均为企业专用版本):
    • GET /disability-type-distribution - 残疾类型分布统计接口
    • 查询参数:无(企业ID强制从认证token获取)
    • GET /gender-distribution - 性别分布统计接口
    • 查询参数:无(企业ID强制从认证token获取)
    • GET /age-distribution - 年龄分布统计接口
    • 查询参数:无(企业ID强制从认证token获取)
    • GET /household-distribution - 户籍分布统计接口
    • 查询参数:无(企业ID强制从认证token获取)
    • GET /job-status-distribution - 在职状态分布统计接口
    • 查询参数:无(企业ID强制从认证token获取)
    • GET /salary-distribution - 薪资分布统计接口
    • 查询参数:无(企业ID强制从认证token获取)
  • 安全要求:所有API通过enterpriseAuthMiddleware中间件保护,自动验证企业用户权限,确保数据安全隔离。企业ID强制从认证token获取,不接受查询参数,防止越权访问。

企业专用数据统计API客户端创建

  • 实现状态@d8d/yongren-statistics-ui包目前只有占位符API客户端,需要按照本故事实现完整的API客户端。
  • 架构模式:参考@d8d/yongren-order-management-ui包的企业专用API客户端实现模式,在UI包内创建RPC客户端。
  • 客户端创建示例

    // 文件:mini-ui-packages/yongren-statistics-ui/src/api/enterpriseStatisticsClient.ts
    import type { statisticsRoutes } from '@d8d/allin-statistics-module';
    import { rpcClient } from '@d8d/mini-shared-ui-components/utils/rpc/rpc-client';
    
    // 注意:企业专用数据统计API通过enterpriseAuthMiddleware中间件保护,确保仅限企业用户访问
    // **重要安全要求**:企业专用API强制从JWT token中的`companyId`字段获取企业ID,不接受查询参数,确保数据隔离安全
    // 路径前缀 /api/v1/yongren/statistics 在路由层配置
    export const enterpriseStatisticsClient = rpcClient<typeof statisticsRoutes>('/api/v1/yongren/statistics');
    
  • 路径前缀/api/v1/yongren/statistics(企业专用版本)

  • 主要接口(6个分布统计接口,与后端实现保持一致):

    • GET /disability-type-distribution - 残疾类型分布统计接口
    • GET /gender-distribution - 性别分布统计接口
    • GET /age-distribution - 年龄分布统计接口
    • GET /household-distribution - 户籍分布统计接口
    • GET /job-status-distribution - 在职状态分布统计接口
    • GET /salary-distribution - 薪资分布统计接口
  • 使用示例

    // 在数据统计UI包内使用
    import { enterpriseStatisticsClient } from '../api/enterpriseStatisticsClient'
    
    // 获取残疾类型分布统计
    const disabilityStats = await enterpriseStatisticsClient['disability-type-distribution'].$get()
    
    // 获取性别分布统计
    const genderStats = await enterpriseStatisticsClient['gender-distribution'].$get()
    
    // 获取年龄分布统计
    const ageStats = await enterpriseStatisticsClient['age-distribution'].$get()
    
    // 获取户籍分布统计
    const householdStats = await enterpriseStatisticsClient['household-distribution'].$get()
    
    // 获取在职状态分布统计
    const jobStatusStats = await enterpriseStatisticsClient['job-status-distribution'].$get()
    
    // 获取薪资分布统计
    const salaryStats = await enterpriseStatisticsClient['salary-distribution'].$get()
    

RPC类型推断实现 参考 mini-ui-packages/yongren-order-management-ui/src/api/types.ts 的实现模式,使用Hono的类型推导工具 InferResponseTypeInferRequestType 从API客户端自动推导请求和响应类型,避免手动定义重复的自定义类型。

实现示例

// 文件:mini-ui-packages/yongren-statistics-ui/src/api/types.ts
import type { InferResponseType, InferRequestType } from 'hono/client';
import { enterpriseStatisticsClient } from './enterpriseStatisticsClient';

// 使用Hono类型推导 - 注意正确的属性访问语法
export type DisabilityTypeDistributionResponse = InferResponseType<typeof enterpriseStatisticsClient['disability-type-distribution']['$get'], 200>;
export type GenderDistributionResponse = InferResponseType<typeof enterpriseStatisticsClient['gender-distribution']['$get'], 200>;
export type AgeDistributionResponse = InferResponseType<typeof enterpriseStatisticsClient['age-distribution']['$get'], 200>;
export type HouseholdDistributionResponse = InferResponseType<typeof enterpriseStatisticsClient['household-distribution']['$get'], 200>;
export type JobStatusDistributionResponse = InferResponseType<typeof enterpriseStatisticsClient['job-status-distribution']['$get'], 200>;
export type SalaryDistributionResponse = InferResponseType<typeof enterpriseStatisticsClient['salary-distribution']['$get'], 200>;

// 查询参数类型推导
export type DisabilityTypeDistributionParams = InferRequestType<typeof enterpriseStatisticsClient['disability-type-distribution']['$get']>['query'];
export type GenderDistributionParams = InferRequestType<typeof enterpriseStatisticsClient['gender-distribution']['$get']>['query'];
export type AgeDistributionParams = InferRequestType<typeof enterpriseStatisticsClient['age-distribution']['$get']>['query'];
export type HouseholdDistributionParams = InferRequestType<typeof enterpriseStatisticsClient['household-distribution']['$get']>['query'];
export type JobStatusDistributionParams = InferRequestType<typeof enterpriseStatisticsClient['job-status-distribution']['$get']>['query'];
export type SalaryDistributionParams = InferRequestType<typeof enterpriseStatisticsClient['salary-distribution']['$get']>['query'];

优势

  • 类型安全:自动与后端路由类型保持同步,确保编译时类型检查
  • 减少重复:避免手动定义重复的类型定义,消除代码冗余
  • 维护性:后端路由变更时,前端类型自动更新,减少维护成本
  • 一致性:确保请求/响应类型与API契约完全匹配
  • 消除不必要的类型断言:无需使用as anyas DisabilityTypeDistributionResponse等手动断言,TypeScript可自动推断API响应类型
  • 简化组件代码:组件中直接导入推导的类型,代码更简洁清晰

组件中使用示例

import type { DisabilityTypeDistributionResponse, GenderDistributionResponse } from '../../api'

// API响应自动推断类型,无需手动断言
const response = await enterpriseStatisticsClient['disability-type-distribution'].$get()
const data = await response.json()  // TypeScript自动推断为DisabilityTypeDistributionResponse类型

// 统计数据使用具体类型
const transformedStats = (data.stats || []).map((item: DisabilityTypeDistributionResponse['stats'][0]) => {
  // 类型安全的转换逻辑
  return {
    key: item.key,
    value: item.value,
    percentage: item.percentage
  }
})

// 性别分布数据示例
const genderResponse = await enterpriseStatisticsClient['gender-distribution'].$get()
const genderData = await genderResponse.json()  // TypeScript自动推断为GenderDistributionResponse类型

技术集成

  • RPC客户端工具:使用@d8d/mini-shared-ui-components/utils/rpc/rpc-client提供的RPC客户端工具,在UI包内创建企业专用API客户端
  • 企业专用API路径前缀/api/v1/yongren/statistics(数据统计模块专用前缀)
  • 架构模式:按照史诗011的mini-ui-packages架构,每个UI包负责创建和管理自己的API客户端,实现模块化集成。数据统计API客户端在@d8d/yongren-statistics-ui包内创建
  • 类型安全:从@d8d/allin-statistics-module导入statisticsRoutes路由类型定义,确保类型安全
  • 数据安全:所有API通过enterpriseAuthMiddleware中间件保护,自动验证企业用户权限,仅返回当前企业关联数据
  • 认证集成:所有API调用自动携带企业用户token(通过企业认证框架管理),自动从token中提取companyId进行数据过滤

组件规范

数据统计页设计规范: 必须严格对照原型文件 docs/小程序原型/yongren.html 第865-1113行的数据统计页面设计实现:

页面结构

  • 注意:小程序环境中状态栏由宿主提供,无需自行实现
  • 内容区域:h-[calc(100%-60px)] overflow-y-auto p-4(仅减去底部导航60px)
  • 底部导航:数据标签激活状态

时间筛选区域(第880-886行):

  • 容器:flex justify-between items-center mb-4
  • 标题:<h3 class="font-semibold text-gray-700">数据统计</h3>
  • 时间选择器:flex items-center bg-gray-100 rounded-lg px-3 py-1
    • 时间显示:<span class="text-sm text-gray-700 mr-2">2023年11月</span>
    • 下拉图标:<i class="fas fa-chevron-down text-gray-500"></i>

统计卡片网格(第889-910行):

  • 容器:grid grid-cols-2 gap-3 mb-4
  • 统计卡片:stat-card(复用基础样式)
    • 标题:<p class="text-sm text-gray-600 mb-2">在职人数</p>
    • 数值:<p class="text-2xl font-bold text-gray-800">24</p>
    • 趋势指示:<p class="text-xs text-green-500 mt-1">↑ 比上月增加2人</p>(绿色-上涨,红色-下降)
  • 卡片类型:在职人数、平均薪资、在职率、新增人数

残疾类型分布图表(第913-931行):

  • 容器:card bg-white p-4 mb-4
  • 标题:<h3 class="font-semibold text-gray-700 mb-3">残疾类型分布</h3>
  • 图表容器:chart-container mb-2(高度200px,相对定位)
  • 柱状图实现:使用 .chart-bar 样式(绝对定位,底部对齐)
    • 样式:position: absolute; bottom: 0; width: 30px; background-color: #3b82f6; border-radius: 4px 4px 0 0;
    • 通过内联样式设置 left 和 height
  • 横坐标标签:flex justify-between text-xs text-gray-500,标签:肢体、听力、视力、言语、智力、精神

性别分布图表(第934-948行):

  • 容器:card bg-white p-4 mb-4
  • 标题:<h3 class="font-semibold text-gray-700 mb-3">性别分布</h3>
  • 图表容器:.bar-chartheight: 120px; display: flex; align-items: end; justify-content: center; gap: 40px;
  • 柱状图组件:.bar-containerdisplay: flex; flex-direction: column; align-items: center;
    • 数值:.bar-valuefont-size: 14px; font-weight: 600; margin-bottom: 4px;
    • 柱体:.barwidth: 40px; border-radius: 4px 4px 0 0;,通过内联样式设置 height 和 background-color)
    • 标签:.bar-labelmargin-top: 8px; font-size: 12px; color: #6b7280;
  • 颜色:男性 #3b82f6,女性 #ec4899

年龄分布图表(第951-...行):

  • 容器:card bg-white p-4 mb-4
  • 标题:<h3 class="font-semibold text-gray-700 mb-3">年龄分布</h3>
  • 饼图容器:.pie-chartwidth: 200px; height: 200px; border-radius: 50%; position: relative; margin: 0 auto;
    • 使用 conic-gradient 实现饼图分区(见基础样式规范)
  • 图例区域:.pie-legenddisplay: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 16px;
  • 图例项:.legend-itemdisplay: flex; align-items: center; font-size: 12px;
    • 颜色块:.legend-colorwidth: 12px; height: 12px; border-radius: 2px; margin-right: 6px;
    • 标签:年龄分段和百分比

图表组件要求

  • 响应式设计,适配不同屏幕尺寸
  • 清晰的视觉层次和颜色方案
  • 必要的辅助信息(标题、图例、数据标签)
  • 交互反馈(悬停、点击、选中状态)

UI组件使用

  • 基础组件来源(现在来自独立的UI包):
    • YongrenTabBarLayout - 从@d8d/yongren-shared-ui/components/YongrenTabBarLayout导入(底部导航布局组件,主页面必须使用,数据统计标签激活状态)
    • Navbar - 从@d8d/mini-shared-ui-components/components/navbar导入(顶部导航栏组件,所有页面必须使用)
    • PageContainer - 从@d8d/mini-shared-ui-components/components/page-container导入(页面容器组件)
  • 页面层级结构规范
    • 主页面(数据统计页):使用YongrenTabBarLayout + Navbar组合
    • Navbar配置:title="数据统计"leftIcon=""leftText=""(无返回按钮)
    • ScrollView布局:px-4 pb-4 pt-0(移除顶部内边距适配navbar占位)
  • 独立开发小程序UI组件:基于原型文件独立设计开发数据统计相关UI组件
  • 复用现有基础组件:复用mini项目中已有的基础UI组件(卡片、筛选器、按钮等),根据原型设计调整样式
  • 注意:史诗011针对mini小程序,UI组件应独立设计,而非复用管理后台的@d8d/allin-*系列UI包

文件位置

当前架构(已按mini-ui-packages拆分)

数据统计UI包

  • 包名称@d8d/yongren-statistics-ui(位于mini-ui-packages/yongren-statistics-ui/
  • 主要页面组件
    • src/pages/Statistics/Statistics.tsx - 数据统计页面组件
    • 使用YongrenTabBarLayout布局,activeTab="statistics"
    • 集成Navbar组件,标题"数据统计",隐藏左侧返回按钮(主页面)
    • 使用ScrollView作为内容容器,布局px-4 pb-4 pt-0适配navbar占位
    • 路由路径:pages/yongren/statistics
  • 依赖的UI包
    • 布局组件
    • @d8d/yongren-shared-ui/components/YongrenTabBarLayout - 底部导航布局组件(主页面使用)
    • @d8d/mini-shared-ui-components/components/navbar - 顶部导航栏组件(所有页面使用)
    • @d8d/mini-shared-ui-components/components/page-container - 页面容器组件
    • 业务模块
    • @d8d/allin-statistics-module - 数据统计模块类型定义(如存在)

桥接文件位置(在mini项目中):

  • mini/src/pages/yongren/statistics/index.tsx - 数据统计页面桥接文件
    • @d8d/yongren-statistics-ui导入Statistics组件
    • 路由路径:pages/yongren/statistics

技术约束

  • 性能优化:多个图表同时加载需优化请求和渲染性能
  • 数据准确性:统计口径需明确,与企业业务逻辑一致
  • 移动端适配:图表在移动端需保持良好的可读性和交互性
  • 数据权限:统计仅限本企业数据,不能跨企业访问
  • 页面结构:主页面必须使用YongrenTabBarLayout+Navbar(无返回按钮),遵循统一的页面层级结构规范
  • 布局渲染:Taro小程序中View容器内的Text组件默认内联显示,需要使用flex flex-col强制垂直排列,确保布局符合原型设计

图表技术选型

现有技术栈参考

  • 根据项目现有图表库选择(需检查mini项目现有依赖)
  • 如无现有图表库,推荐使用Recharts(React友好,TypeScript支持好)
  • 地图图表可能需要额外库(如中国地图数据)

测试要求

测试框架:Jest + Testing Library 关键测试场景

  1. 图表数据测试
    • 数据加载准确性测试
    • 空数据状态处理测试
    • 数据错误处理测试
  2. 交互功能测试
    • 筛选条件功能测试
    • 图表点击交互测试
    • 数据下钻功能测试
  3. 性能测试
    • 多图表同时加载性能测试
    • 大数据量渲染性能测试
    • 移动端渲染性能测试
  4. 集成测试
    • 与统计API的集成测试
    • 页面导航和数据流测试
  5. 页面结构测试
    • Navbar组件集成测试(主页面无返回按钮)
    • 页面层级结构验证(主页面使用YongrenTabBarLayout)
    • 类型检查验证

变更日志

日期 版本 描述 作者
2025-12-17 1.0 初始创建(数据统计故事) Bob(Scrum Master)
2025-12-20 1.1 更新Navbar集成规范,添加页面层级结构,反映mini-ui-packages架构 Claude Code
2025-12-22 1.2 更新故事状态为Ready,依赖故事011.001-011.004已完成 Claude Code
2025-12-22 1.3 根据实际API实现修正API规范,移除虚构API描述,更新为6个实际分布统计接口 Claude Code
2025-12-23 1.4 后端统计API测试完全通过:1) statistics-module 7个测试全部通过,2) order-module统计API测试全部通过,3) 修复路由聚合顺序问题确保所有测试稳定运行 Claude Code
2025-12-23 1.5 根据故事012.015安全修复完成状态更新文档:1) 修正API客户端使用示例,移除所有companyId查询参数,2) 更新API规范和安全要求描述,3) 更新变更日志反映实际实现 Claude Code

开发代理记录

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

文档更新记录 (2025-12-20)

  • 根据史诗011最新实现,更新Navbar组件集成规范
  • 添加任务6:集成Navbar导航栏组件(页面层级结构规范)
  • 更新UI组件使用规范,反映mini-ui-packages拆分后的架构
  • 更新技术约束,添加页面结构要求和布局渲染约束
  • 更新测试要求,添加页面结构测试场景
  • 统一所有用人小程序页面的Navbar集成标准

文档更新记录 (2025-12-22)

  • 根据实际API实现验证和修正API规范部分
  • 移除虚构的通用版本和企业专用扩展API描述
  • 更新为实际存在的6个分布统计接口:残疾类型分布、性别分布、年龄分布、户籍分布、在职状态分布、薪资分布
  • 修正接口路径名称(添加-distribution后缀)
  • 修正查询参数(不支持任何查询参数,企业ID强制从认证token获取)
  • 更新API客户端创建示例,反映mini-ui-packages架构
  • 更新RPC类型推断实现示例,使用实际接口名称
  • 更新组件中使用示例,使用正确的接口和类型
  • 更新技术集成描述,反映实际实现状态

文档更新记录 (2025-12-23)

  • 根据故事012.015安全修复完成状态,更新API规范和安全要求描述
  • 修正API客户端使用示例,移除所有companyId查询参数(企业ID强制从认证token获取)
  • 更新变更日志中的查询参数描述,反映实际实现(不支持任何查询参数)

开发代理记录

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

实施记录 (2025-12-23)

  • 根据故事012.015安全修复完成状态,更新数据统计API客户端实现
  • 创建企业专用数据统计API客户端 enterpriseStatisticsClient,路径前缀 /api/v1/yongren/statistics
  • 实现完整的类型推导文件 types.ts,使用Hono的 InferResponseTypeInferRequestType
  • 更新数据统计页面组件 Statistics.tsx,实现6个分布图表的展示
  • 使用React Query进行API数据获取,实现加载状态和错误处理
  • 基于原型设计实现CSS图表:残疾类型分布(柱状图)、性别分布(柱状图)、年龄分布(饼图占位)、户籍分布(进度条)、在职状态统计(环形图占位)、薪资分布(进度条)
  • 添加统计卡片展示(在职人数、平均薪资、在职率、新增人数)- 目前为静态数据
  • 集成Navbar组件,遵循主页面布局规范(YongrenTabBarLayout + Navbar,无返回按钮)
  • 添加时间筛选器占位符(2023年11月)

待完成项

  • 图表数据需要与实际API响应格式对齐
  • 饼图和环形图需要实现可视化(可考虑添加recharts或SVG实现)
  • 统计卡片数据需要从API数据计算或添加新的API端点
  • 时间筛选功能需要实现
  • 需要完整的集成测试

QA结果

来自QA代理对已完成故事实施的QA审查结果