feat(web): 健康模块 13 页面按钮级权限控制 — AuthButton 包装
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

使用 AuthButton 声明式组件包装健康模块全部操作按钮:
- health.patient.manage: PatientList/PatientDetail/PatientTagManage
- health.appointment.manage: AppointmentList
- health.doctor.manage: DoctorList/DoctorSchedule
- health.follow-up.manage: FollowUpTaskList
- health.consultation.manage: ConsultationList/ConsultationDetail
- health.points.manage: OfflineEventList/PointsProductList/PointsOrderList/PointsRuleList
This commit is contained in:
iven
2026-04-25 23:33:32 +08:00
parent 69dcb8fee7
commit 69313a177e
13 changed files with 303 additions and 246 deletions

View File

@@ -26,6 +26,7 @@ import { doctorApi } from '../../api/health/doctors';
import { StatusTag } from './components/StatusTag'; import { StatusTag } from './components/StatusTag';
import { PatientSelect } from './components/PatientSelect'; import { PatientSelect } from './components/PatientSelect';
import { DoctorSelect } from './components/DoctorSelect'; import { DoctorSelect } from './components/DoctorSelect';
import { AuthButton } from '../../components/AuthButton';
/** 预约类型选项 */ /** 预约类型选项 */
const APPOINTMENT_TYPE_OPTIONS = [ const APPOINTMENT_TYPE_OPTIONS = [
@@ -305,6 +306,7 @@ export default function AppointmentList() {
return <span style={{ color: '#999' }}></span>; return <span style={{ color: '#999' }}></span>;
} }
return ( return (
<AuthButton code="health.appointment.manage">
<Dropdown <Dropdown
menu={{ menu={{
items: transitions.map((t) => ({ items: transitions.map((t) => ({
@@ -318,6 +320,7 @@ export default function AppointmentList() {
<DownOutlined /> <DownOutlined />
</Button> </Button>
</Dropdown> </Dropdown>
</AuthButton>
); );
}, },
}, },
@@ -352,9 +355,11 @@ export default function AppointmentList() {
</Space> </Space>
</Col> </Col>
<Col> <Col>
<AuthButton code="health.appointment.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>
</AuthButton>
</Col> </Col>
</Row> </Row>

View File

@@ -6,6 +6,7 @@ import { consultationApi, type Session, type Message } from '../../api/health/co
import { StatusTag } from './components/StatusTag'; import { StatusTag } from './components/StatusTag';
import { ImagePreview } from './components/ImagePreview'; import { ImagePreview } from './components/ImagePreview';
import { useThemeMode } from '../../hooks/useThemeMode'; import { useThemeMode } from '../../hooks/useThemeMode';
import { AuthButton } from '../../components/AuthButton';
const PAGE_SIZE = 30; const PAGE_SIZE = 30;
@@ -276,6 +277,7 @@ export default function ConsultationDetail() {
</Typography.Text> </Typography.Text>
)} )}
{session && !isClosed && ( {session && !isClosed && (
<AuthButton code="health.consultation.manage">
<Popconfirm <Popconfirm
title="确认关闭该咨询会话?" title="确认关闭该咨询会话?"
onConfirm={handleClose} onConfirm={handleClose}
@@ -291,6 +293,7 @@ export default function ConsultationDetail() {
</Button> </Button>
</Popconfirm> </Popconfirm>
</AuthButton>
)} )}
</div> </div>

View File

@@ -20,6 +20,7 @@ import { PatientSelect } from './components/PatientSelect';
import { DoctorSelect } from './components/DoctorSelect'; import { DoctorSelect } from './components/DoctorSelect';
import { ExportButton } from './components/ExportButton'; import { ExportButton } from './components/ExportButton';
import { useThemeMode } from '../../hooks/useThemeMode'; import { useThemeMode } from '../../hooks/useThemeMode';
import { AuthButton } from '../../components/AuthButton';
const STATUS_OPTIONS = [ const STATUS_OPTIONS = [
{ value: 'waiting', label: '等待中' }, { value: 'waiting', label: '等待中' },
@@ -258,6 +259,7 @@ export default function ConsultationList() {
key: 'actions', key: 'actions',
width: 120, width: 120,
render: (_: unknown, record: Session) => ( render: (_: unknown, record: Session) => (
<AuthButton code="health.consultation.manage">
<Space size={4}> <Space size={4}>
{record.status !== 'closed' && ( {record.status !== 'closed' && (
<Popconfirm <Popconfirm
@@ -278,6 +280,7 @@ export default function ConsultationList() {
</Popconfirm> </Popconfirm>
)} )}
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -305,6 +308,7 @@ export default function ConsultationList() {
value={query.status} value={query.status}
onChange={handleFilterChange} onChange={handleFilterChange}
/> />
<AuthButton code="health.consultation.manage">
<Button <Button
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined />}
@@ -315,6 +319,7 @@ export default function ConsultationList() {
> >
</Button> </Button>
</AuthButton>
<ExportButton <ExportButton
fetchUrl="/health/consultation-sessions/export" fetchUrl="/health/consultation-sessions/export"
params={exportParams} params={exportParams}

View File

@@ -22,6 +22,7 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { doctorApi, type Doctor, type CreateDoctorReq, type UpdateDoctorReq } from '../../api/health/doctors'; import { doctorApi, type Doctor, type CreateDoctorReq, type UpdateDoctorReq } from '../../api/health/doctors';
import { AuthButton } from '../../components/AuthButton';
/** 科室选项 — 可后续改为从字典接口获取 */ /** 科室选项 — 可后续改为从字典接口获取 */
const DEPARTMENT_OPTIONS = [ const DEPARTMENT_OPTIONS = [
@@ -232,6 +233,7 @@ export default function DoctorList() {
width: 140, width: 140,
fixed: 'right' as const, fixed: 'right' as const,
render: (_: unknown, record: Doctor) => ( render: (_: unknown, record: Doctor) => (
<AuthButton code="health.doctor.manage">
<Space size="small"> <Space size="small">
<Button <Button
type="link" type="link"
@@ -252,6 +254,7 @@ export default function DoctorList() {
</Button> </Button>
</Popconfirm> </Popconfirm>
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -284,9 +287,11 @@ export default function DoctorList() {
</Space> </Space>
</Col> </Col>
<Col> <Col>
<AuthButton code="health.doctor.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>
</AuthButton>
</Col> </Col>
</Row> </Row>

View File

@@ -30,6 +30,7 @@ import {
import { DoctorSelect } from './components/DoctorSelect'; import { DoctorSelect } from './components/DoctorSelect';
import { CalendarView, type ScheduleItem } from './components/CalendarView'; import { CalendarView, type ScheduleItem } from './components/CalendarView';
import { StatusTag } from './components/StatusTag'; import { StatusTag } from './components/StatusTag';
import { AuthButton } from '../../components/AuthButton';
/** 时段选项 */ /** 时段选项 */
const PERIOD_OPTIONS = [ const PERIOD_OPTIONS = [
@@ -258,6 +259,7 @@ export default function DoctorSchedule() {
key: 'action', key: 'action',
width: 120, width: 120,
render: (_: unknown, record: Schedule) => ( render: (_: unknown, record: Schedule) => (
<AuthButton code="health.doctor.manage">
<Space size="small"> <Space size="small">
<Button <Button
type="link" type="link"
@@ -268,6 +270,7 @@ export default function DoctorSchedule() {
</Button> </Button>
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -309,9 +312,11 @@ export default function DoctorSchedule() {
<Col flex="auto" /> <Col flex="auto" />
<Col> <Col>
{selectedDoctorId && ( {selectedDoctorId && (
<AuthButton code="health.doctor.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>
</AuthButton>
)} )}
</Col> </Col>
</Row> </Row>

View File

@@ -19,6 +19,7 @@ import { StatusTag } from './components/StatusTag';
import { PatientSelect } from './components/PatientSelect'; import { PatientSelect } from './components/PatientSelect';
import { DoctorSelect } from './components/DoctorSelect'; import { DoctorSelect } from './components/DoctorSelect';
import { useThemeMode } from '../../hooks/useThemeMode'; import { useThemeMode } from '../../hooks/useThemeMode';
import { AuthButton } from '../../components/AuthButton';
const STATUS_OPTIONS = [ const STATUS_OPTIONS = [
{ value: 'pending', label: '待处理' }, { value: 'pending', label: '待处理' },
@@ -289,6 +290,7 @@ export default function FollowUpTaskList() {
key: 'actions', key: 'actions',
width: 220, width: 220,
render: (_: unknown, record: FollowUpTask) => ( render: (_: unknown, record: FollowUpTask) => (
<AuthButton code="health.follow-up.manage">
<Space size={4}> <Space size={4}>
<Button <Button
type="link" type="link"
@@ -317,6 +319,7 @@ export default function FollowUpTaskList() {
</Button> </Button>
</Popconfirm> </Popconfirm>
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -344,6 +347,7 @@ export default function FollowUpTaskList() {
value={query.status} value={query.status}
onChange={(value) => handleFilterChange('status', value)} onChange={(value) => handleFilterChange('status', value)}
/> />
<AuthButton code="health.follow-up.manage">
<Button <Button
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined />}
@@ -354,6 +358,7 @@ export default function FollowUpTaskList() {
> >
</Button> </Button>
</AuthButton>
<span <span
style={{ style={{
fontSize: 13, fontSize: 13,

View File

@@ -30,6 +30,7 @@ import {
type OfflineEvent, type OfflineEvent,
type CreateOfflineEventReq, type CreateOfflineEventReq,
} from '../../api/health/points'; } from '../../api/health/points';
import { AuthButton } from '../../components/AuthButton';
/** 活动状态映射 */ /** 活动状态映射 */
const STATUS_MAP: Record<string, { text: string; color: string }> = { const STATUS_MAP: Record<string, { text: string; color: string }> = {
@@ -245,6 +246,7 @@ export default function OfflineEventList() {
key: 'action', key: 'action',
width: 200, width: 200,
render: (_: unknown, record: OfflineEvent) => ( render: (_: unknown, record: OfflineEvent) => (
<AuthButton code="health.points.manage">
<Space size="small"> <Space size="small">
<Button <Button
type="link" type="link"
@@ -274,6 +276,7 @@ export default function OfflineEventList() {
</Button> </Button>
</Popconfirm> </Popconfirm>
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -298,9 +301,11 @@ export default function OfflineEventList() {
</Space> </Space>
</Col> </Col>
<Col> <Col>
<AuthButton code="health.points.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>
</AuthButton>
</Col> </Col>
</Row> </Row>

View File

@@ -16,6 +16,7 @@ import {
} from 'antd'; } from 'antd';
import { ArrowLeftOutlined, EditOutlined } from '@ant-design/icons'; import { ArrowLeftOutlined, EditOutlined } from '@ant-design/icons';
import { patientApi } from '../../api/health/patients'; import { patientApi } from '../../api/health/patients';
import { AuthButton } from '../../components/AuthButton';
import type { import type {
PatientDetail as PatientDetailType, PatientDetail as PatientDetailType,
UpdatePatientReq, UpdatePatientReq,
@@ -187,9 +188,11 @@ export default function PatientDetail() {
</Space> </Space>
</div> </div>
</div> </div>
<AuthButton code="health.patient.manage">
<Button icon={<EditOutlined />} onClick={openEditModal}> <Button icon={<EditOutlined />} onClick={openEditModal}>
</Button> </Button>
</AuthButton>
</div> </div>
<Descriptions column={3} size="small"> <Descriptions column={3} size="small">
<Descriptions.Item label="性别"> <Descriptions.Item label="性别">

View File

@@ -27,6 +27,7 @@ import type {
import { StatusTag } from './components/StatusTag'; import { StatusTag } from './components/StatusTag';
import { GENDER_OPTIONS, BLOOD_TYPE_OPTIONS, STATUS_OPTIONS } from '../../constants/health'; import { GENDER_OPTIONS, BLOOD_TYPE_OPTIONS, STATUS_OPTIONS } from '../../constants/health';
import { useThemeMode } from '../../hooks/useThemeMode'; import { useThemeMode } from '../../hooks/useThemeMode';
import { AuthButton } from '../../components/AuthButton';
export default function PatientList() { export default function PatientList() {
const [patients, setPatients] = useState<PatientListItem[]>([]); const [patients, setPatients] = useState<PatientListItem[]>([]);
@@ -239,6 +240,7 @@ export default function PatientList() {
key: 'actions', key: 'actions',
width: 140, width: 140,
render: (_: unknown, record: PatientListItem) => ( render: (_: unknown, record: PatientListItem) => (
<AuthButton code="health.patient.manage">
<Space size={4}> <Space size={4}>
<Button <Button
size="small" size="small"
@@ -266,6 +268,7 @@ export default function PatientList() {
/> />
</Popconfirm> </Popconfirm>
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -301,9 +304,11 @@ export default function PatientList() {
options={STATUS_OPTIONS} options={STATUS_OPTIONS}
style={{ width: 130, borderRadius: 8 }} style={{ width: 130, borderRadius: 8 }}
/> />
<AuthButton code="health.patient.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreateModal}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreateModal}>
</Button> </Button>
</AuthButton>
</Space> </Space>
</div> </div>

View File

@@ -14,6 +14,7 @@ import { TagsOutlined, AppstoreOutlined } from '@ant-design/icons';
import { patientApi, type TagItem } from '../../api/health/patients'; import { patientApi, type TagItem } from '../../api/health/patients';
import type { PatientListItem } from '../../api/health/patients'; import type { PatientListItem } from '../../api/health/patients';
import { useThemeMode } from '../../hooks/useThemeMode'; import { useThemeMode } from '../../hooks/useThemeMode';
import { AuthButton } from '../../components/AuthButton';
export default function PatientTagManage() { export default function PatientTagManage() {
const [patients, setPatients] = useState<PatientListItem[]>([]); const [patients, setPatients] = useState<PatientListItem[]>([]);
@@ -178,6 +179,7 @@ export default function PatientTagManage() {
key: 'actions', key: 'actions',
width: 120, width: 120,
render: (_: unknown, record: PatientListItem) => ( render: (_: unknown, record: PatientListItem) => (
<AuthButton code="health.patient.manage">
<Button <Button
size="small" size="small"
type="link" type="link"
@@ -186,6 +188,7 @@ export default function PatientTagManage() {
> >
</Button> </Button>
</AuthButton>
), ),
}, },
]; ];

View File

@@ -23,6 +23,7 @@ import {
type PointsOrder, type PointsOrder,
} from '../../api/health/points'; } from '../../api/health/points';
import { patientApi } from '../../api/health/patients'; import { patientApi } from '../../api/health/patients';
import { AuthButton } from '../../components/AuthButton';
/** 订单状态映射 */ /** 订单状态映射 */
const STATUS_MAP: Record<string, { text: string; color: string }> = { const STATUS_MAP: Record<string, { text: string; color: string }> = {
@@ -228,6 +229,7 @@ export default function PointsOrderList() {
</Space> </Space>
</Col> </Col>
<Col> <Col>
<AuthButton code="health.points.manage">
<Button <Button
type="primary" type="primary"
icon={<CheckCircleOutlined />} icon={<CheckCircleOutlined />}
@@ -235,6 +237,7 @@ export default function PointsOrderList() {
> >
</Button> </Button>
</AuthButton>
</Col> </Col>
</Row> </Row>

View File

@@ -26,6 +26,7 @@ import {
type PointsProduct, type PointsProduct,
type CreatePointsProductReq, type CreatePointsProductReq,
} from '../../api/health/points'; } from '../../api/health/points';
import { AuthButton } from '../../components/AuthButton';
/** 商品类型映射 */ /** 商品类型映射 */
const PRODUCT_TYPES: Record<string, string> = { const PRODUCT_TYPES: Record<string, string> = {
@@ -212,6 +213,7 @@ export default function PointsProductList() {
key: 'action', key: 'action',
width: 140, width: 140,
render: (_: unknown, record: PointsProduct) => ( render: (_: unknown, record: PointsProduct) => (
<AuthButton code="health.points.manage">
<Space size="small"> <Space size="small">
<Button <Button
type="link" type="link"
@@ -229,6 +231,7 @@ export default function PointsProductList() {
onChange={() => handleToggleActive(record)} onChange={() => handleToggleActive(record)}
/> />
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -253,9 +256,11 @@ export default function PointsProductList() {
</Space> </Space>
</Col> </Col>
<Col> <Col>
<AuthButton code="health.points.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>
</AuthButton>
</Col> </Col>
</Row> </Row>

View File

@@ -26,6 +26,7 @@ import {
type PointsRule, type PointsRule,
type CreatePointsRuleReq, type CreatePointsRuleReq,
} from '../../api/health/points'; } from '../../api/health/points';
import { AuthButton } from '../../components/AuthButton';
/** 事件类型映射 */ /** 事件类型映射 */
const EVENT_TYPES: Record<string, string> = { const EVENT_TYPES: Record<string, string> = {
@@ -216,6 +217,7 @@ export default function PointsRuleList() {
key: 'action', key: 'action',
width: 200, width: 200,
render: (_: unknown, record: PointsRule) => ( render: (_: unknown, record: PointsRule) => (
<AuthButton code="health.points.manage">
<Space size="small"> <Space size="small">
<Button <Button
type="link" type="link"
@@ -233,6 +235,7 @@ export default function PointsRuleList() {
onChange={() => handleToggleActive(record)} onChange={() => handleToggleActive(record)}
/> />
</Space> </Space>
</AuthButton>
), ),
}, },
]; ];
@@ -247,9 +250,11 @@ export default function PointsRuleList() {
</span> </span>
</Col> </Col>
<Col> <Col>
<AuthButton code="health.points.manage">
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>
</AuthButton>
</Col> </Col>
</Row> </Row>