In Progress
作为企业用户, 我希望查看企业残疾人就业的数据统计图表, 以便进行数据分析和决策支持。
docs/小程序原型/yongren.html中的数据统计页面import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'title="数据统计",leftIcon="",leftText=""activeTab="statistics"px-4 pb-4 pt-0(移除顶部内边距适配navbar占位)依赖故事:
数据统计API(statistics模块):
@d8d/allin-statistics-module已实现6个分布统计API,均为企业专用版本。statistics路由已在server包注册(路径前缀/api/v1/yongren/statistics),API实际可用。前端@d8d/yongren-statistics-ui包目前只有占位符API客户端,需要按照故事实现完整的API客户端集成。@d8d/yongren-statistics-ui包内创建,而非在mini/src/api.ts中统一注册。@d8d/allin-statistics-module导入statisticsRoutes类型定义/api/v1/yongren/statistics(企业专用版本,通过enterpriseAuthMiddleware中间件保护)GET /disability-type-distribution - 残疾类型分布统计接口GET /gender-distribution - 性别分布统计接口GET /age-distribution - 年龄分布统计接口GET /household-distribution - 户籍分布统计接口GET /job-status-distribution - 在职状态分布统计接口GET /salary-distribution - 薪资分布统计接口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的类型推导工具 InferResponseType 和 InferRequestType 从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'];
优势:
as any或as 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类型
技术集成:
@d8d/mini-shared-ui-components/utils/rpc/rpc-client提供的RPC客户端工具,在UI包内创建企业专用API客户端/api/v1/yongren/statistics(数据统计模块专用前缀)@d8d/yongren-statistics-ui包内创建@d8d/allin-statistics-module导入statisticsRoutes路由类型定义,确保类型安全enterpriseAuthMiddleware中间件保护,自动验证企业用户权限,仅返回当前企业关联数据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-4stat-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;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-chart(height: 120px; display: flex; align-items: end; justify-content: center; gap: 40px;).bar-container(display: flex; flex-direction: column; align-items: center;)
.bar-value(font-size: 14px; font-weight: 600; margin-bottom: 4px;).bar(width: 40px; border-radius: 4px 4px 0 0;,通过内联样式设置 height 和 background-color).bar-label(margin-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-chart(width: 200px; height: 200px; border-radius: 50%; position: relative; margin: 0 auto;)
conic-gradient 实现饼图分区(见基础样式规范).pie-legend(display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 16px;).legend-item(display: flex; align-items: center; font-size: 12px;)
.legend-color(width: 12px; height: 12px; border-radius: 2px; margin-right: 6px;)图表组件要求:
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组合title="数据统计",leftIcon="",leftText=""(无返回按钮)px-4 pb-4 pt-0(移除顶部内边距适配navbar占位)@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@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/statisticsflex flex-col强制垂直排列,确保布局符合原型设计现有技术栈参考:
测试框架:Jest + Testing Library 关键测试场景:
| 日期 | 版本 | 描述 | 作者 |
|---|---|---|---|
| 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):
文档更新记录 (2025-12-22):
文档更新记录 (2025-12-23):
此部分由开发代理在实施过程中填充
实施记录 (2025-12-23):
enterpriseStatisticsClient,路径前缀 /api/v1/yongren/statisticstypes.ts,使用Hono的 InferResponseType 和 InferRequestTypeStatistics.tsx,实现6个分布图表的展示待完成项:
来自QA代理对已完成故事实施的QA审查结果