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:
@@ -4,12 +4,13 @@ import { NurseDashboard } from './StatisticsDashboard/NurseDashboard';
|
||||
import { AdminDashboard } from './StatisticsDashboard/AdminDashboard';
|
||||
import { OperatorDashboard } from './StatisticsDashboard/OperatorDashboard';
|
||||
|
||||
const DASHBOARD_MAP = {
|
||||
const DASHBOARD_MAP: Record<string, React.FC> = {
|
||||
doctor: DoctorDashboard,
|
||||
health_manager: NurseDashboard,
|
||||
nurse: NurseDashboard,
|
||||
admin: AdminDashboard,
|
||||
operator: OperatorDashboard,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export default function StatisticsDashboard() {
|
||||
const role = useDashboardRole();
|
||||
|
||||
141
apps/web/src/pages/health/components/PointsAccountTab.tsx
Normal file
141
apps/web/src/pages/health/components/PointsAccountTab.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Table, Tag, Statistic, Row, Col, Card, Empty } from 'antd';
|
||||
import {
|
||||
pointsAdminApi,
|
||||
type PointsAccountDetail,
|
||||
type PointsTransactionDetail,
|
||||
} from '../../../api/health/points';
|
||||
import { usePaginatedData } from '../../../hooks/usePaginatedData';
|
||||
import { handleApiError } from '../../../api/client';
|
||||
|
||||
interface Props {
|
||||
patientId: string;
|
||||
}
|
||||
|
||||
const TYPE_MAP: Record<string, { color: string; label: string }> = {
|
||||
earn: { color: 'green', label: '获得' },
|
||||
spend: { color: 'orange', label: '消费' },
|
||||
expire: { color: 'default', label: '过期' },
|
||||
adjust: { color: 'blue', label: '调整' },
|
||||
checkin: { color: 'cyan', label: '签到' },
|
||||
};
|
||||
|
||||
export function PointsAccountTab({ patientId }: Props) {
|
||||
const [account, setAccount] = useState<PointsAccountDetail | null>(null);
|
||||
const [accountLoading, setAccountLoading] = useState(true);
|
||||
|
||||
const fetchTransactions = useCallback(
|
||||
async (page: number, pageSize: number) => {
|
||||
if (!account) {
|
||||
try {
|
||||
const acc = await pointsAdminApi.getPatientAccount(patientId);
|
||||
setAccount(acc);
|
||||
} catch (err) {
|
||||
handleApiError(err, '加载积分账户失败');
|
||||
} finally {
|
||||
setAccountLoading(false);
|
||||
}
|
||||
}
|
||||
return pointsAdminApi.listPatientTransactions(patientId, { page, page_size: pageSize });
|
||||
},
|
||||
[patientId, account],
|
||||
);
|
||||
|
||||
const { data, total, page, loading, refresh } = usePaginatedData<PointsTransactionDetail>(
|
||||
fetchTransactions,
|
||||
10,
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 170,
|
||||
render: (v: string) => new Date(v).toLocaleString('zh-CN'),
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'transaction_type',
|
||||
key: 'transaction_type',
|
||||
width: 100,
|
||||
render: (v: string) => {
|
||||
const m = TYPE_MAP[v];
|
||||
return m ? <Tag color={m.color}>{m.label}</Tag> : <Tag>{v}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '变动',
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
width: 100,
|
||||
render: (v: number) => (
|
||||
<span style={{ color: v > 0 ? '#52c41a' : v < 0 ? '#ff4d4f' : undefined }}>
|
||||
{v > 0 ? `+${v}` : v}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '余额',
|
||||
dataIndex: 'balance_after',
|
||||
key: 'balance_after',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||
<Col span={6}>
|
||||
<Card size="small" loading={accountLoading}>
|
||||
<Statistic title="当前余额" value={account?.balance ?? 0} suffix="分" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small" loading={accountLoading}>
|
||||
<Statistic title="累计获得" value={account?.total_earned ?? 0} suffix="分" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small" loading={accountLoading}>
|
||||
<Statistic title="累计消费" value={account?.total_spent ?? 0} suffix="分" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card size="small" loading={accountLoading}>
|
||||
<Statistic title="累计过期" value={account?.total_expired ?? 0} suffix="分" />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{!account && !accountLoading ? (
|
||||
<Empty description="暂无积分记录" />
|
||||
) : (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
size="small"
|
||||
pagination={{
|
||||
current: page,
|
||||
total,
|
||||
pageSize: 10,
|
||||
onChange: (p) => refresh(p),
|
||||
showTotal: (t) => `共 ${t} 条`,
|
||||
style: { margin: 0 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user