feat(web): 透析 API + 积分账户组件 + 工作台 store + 统计页修复

- dialysis.ts: 新增透析管理 API 模块
- PointsAccountTab.tsx: 积分账户标签页组件
- workbenchStore.ts: 工作台状态管理
- StatisticsDashboard.tsx: 统计页空列表修复
- auth.test.ts: 修复权限码拼写 health.alert → health.alerts
- api.test.ts: API 契约测试
This commit is contained in:
iven
2026-05-03 19:32:00 +08:00
parent 70322e4132
commit 3e4baa38a6
6 changed files with 485 additions and 5 deletions

View File

@@ -85,7 +85,7 @@ function createFakeUser(overrides: Partial<UserInfo> = {}): UserInfo {
function createFakeLoginResponse(overrides: Partial<LoginResponse> = {}): LoginResponse {
return {
access_token: createFakeToken(['health.patient.list', 'health.alert.manage']),
access_token: createFakeToken(['health.patient.list', 'health.alerts.manage']),
refresh_token: 'refresh-token-xxx',
expires_in: 3600,
user: createFakeUser(),
@@ -157,7 +157,7 @@ describe('useAuthStore', () => {
expect(state.user).toEqual(fakeUser);
expect(state.isAuthenticated).toBe(true);
expect(state.loading).toBe(false);
expect(state.permissions).toEqual(['health.patient.list', 'health.alert.manage']);
expect(state.permissions).toEqual(['health.patient.list', 'health.alerts.manage']);
// API 被正确调用
expect(mockApiLogin).toHaveBeenCalledWith({ username: 'testuser', password: 'password123' });
@@ -283,7 +283,7 @@ describe('useAuthStore', () => {
// =========================================================================
describe('权限提取', () => {
it('登录后 permissions 应从 JWT token 中正确解析', async () => {
const permissions = ['health.patient.list', 'health.alert.manage', 'health.report.review'];
const permissions = ['health.patient.list', 'health.alerts.manage', 'health.report.review'];
const token = createFakeToken(permissions);
const fakeResponse = createFakeLoginResponse({ access_token: token });
mockApiLogin.mockResolvedValue(fakeResponse);

View File

@@ -0,0 +1,58 @@
import { create } from 'zustand';
import { actionInboxApi, type ActionItem, type WorkbenchStats } from '../api/health/actionInbox';
interface WorkbenchState {
tasks: ActionItem[];
selectedTaskId: string | null;
tab: 'pending' | 'completed';
loading: boolean;
stats: WorkbenchStats | null;
selectTask: (id: string | null) => void;
setTab: (tab: 'pending' | 'completed') => void;
refreshTasks: () => Promise<void>;
refreshStats: () => Promise<void>;
completeTask: (id: string) => void;
}
export const useWorkbenchStore = create<WorkbenchState>((set, get) => ({
tasks: [],
selectedTaskId: null,
tab: 'pending',
loading: false,
stats: null,
selectTask: (id) => set({ selectedTaskId: id }),
setTab: (tab) => {
set({ tab, selectedTaskId: null });
get().refreshTasks();
},
refreshTasks: async () => {
set({ loading: true });
try {
const status = get().tab === 'pending' ? 'pending' : 'completed';
const resp = await actionInboxApi.list({ status, page: 1, page_size: 50 });
const tasks = Array.isArray(resp?.data) ? resp.data : [];
set({ tasks, loading: false });
} catch {
set({ loading: false });
}
},
refreshStats: async () => {
try {
const stats = await actionInboxApi.stats();
set({ stats: stats ?? null });
} catch { /* ignore */ }
},
completeTask: (id) => {
const { tasks } = get();
const remaining = tasks.filter(t => t.id !== id);
const nextId = remaining.length > 0 ? remaining[0].id : null;
set({ tasks: remaining, selectedTaskId: nextId });
get().refreshStats();
},
}));