Commit f350fadd authored by yuguo's avatar yuguo

fix

parent 52e57c72
......@@ -44,7 +44,7 @@ const LoginPage: React.FC = () => {
key: 'phone',
label: '手机号登录',
children: (
<Form onFinish={handleLogin} size="middle">
<Form onFinish={handleLogin} size="large" className="mt-4">
<Form.Item
name="phone"
rules={[
......@@ -52,14 +52,22 @@ const LoginPage: React.FC = () => {
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号' },
]}
>
<Input prefix={<MobileOutlined className="text-primary/60" />} placeholder="手机号" />
<Input
prefix={<MobileOutlined className="text-gray-400" />}
placeholder="手机号"
className="h-11 rounded-lg"
/>
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password prefix={<LockOutlined className="text-primary/60" />} placeholder="密码" />
<Input.Password
prefix={<LockOutlined className="text-gray-400" />}
placeholder="密码"
className="h-11 rounded-lg"
/>
</Form.Item>
<Form.Item>
<Form.Item className="mb-0 mt-2">
<Button type="primary" htmlType="submit" loading={loading} block
className="h-10! rounded-lg! font-semibold! text-sm! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! hover:from-[#40a9ff]! hover:to-[#1890ff]! shadow-md! shadow-blue-200!">
className="h-11 rounded-lg font-semibold text-base shadow-lg shadow-blue-200/50">
登录
</Button>
</Form.Item>
......@@ -70,16 +78,24 @@ const LoginPage: React.FC = () => {
key: 'username',
label: '账号登录',
children: (
<Form onFinish={handleLogin} size="middle">
<Form onFinish={handleLogin} size="large" className="mt-4">
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
<Input prefix={<UserOutlined className="text-primary/60" />} placeholder="用户名" />
<Input
prefix={<UserOutlined className="text-gray-400" />}
placeholder="用户名"
className="h-11 rounded-lg"
/>
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password prefix={<LockOutlined className="text-primary/60" />} placeholder="密码" />
<Input.Password
prefix={<LockOutlined className="text-gray-400" />}
placeholder="密码"
className="h-11 rounded-lg"
/>
</Form.Item>
<Form.Item>
<Form.Item className="mb-0 mt-2">
<Button type="primary" htmlType="submit" loading={loading} block
className="h-10! rounded-lg! font-semibold! text-sm! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! hover:from-[#40a9ff]! hover:to-[#1890ff]! shadow-md! shadow-blue-200!">
className="h-11 rounded-lg font-semibold text-base shadow-lg shadow-blue-200/50">
登录
</Button>
</Form.Item>
......@@ -89,7 +105,7 @@ const LoginPage: React.FC = () => {
];
return (
<div className="fixed inset-0 flex items-center justify-center overflow-hidden"
<div className="fixed inset-0 flex items-center justify-center overflow-auto py-8"
style={{ background: 'linear-gradient(135deg, #001529 0%, #003a8c 40%, #0050b3 70%, #1890ff 100%)' }}>
{/* 装饰粒子 */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
......@@ -99,8 +115,8 @@ const LoginPage: React.FC = () => {
</div>
<div className="relative z-10 flex w-full max-w-[960px] mx-4 rounded-2xl overflow-hidden shadow-2xl shadow-black/30">
{/* 左侧品牌区 */}
<div className="flex flex-col justify-between w-[420px] shrink-0 p-8 text-white"
{/* 左侧品牌区 - 仅在 md 及以上屏幕显示 */}
<div className="hidden md:flex flex-col justify-between w-[420px] shrink-0 p-8 text-white"
style={{ background: 'linear-gradient(180deg, rgba(0,58,140,0.9) 0%, rgba(0,80,179,0.85) 50%, rgba(24,144,255,0.8) 100%)', backdropFilter: 'blur(20px)' }}>
<div>
<div className="flex items-center gap-3 mb-8">
......@@ -155,29 +171,39 @@ const LoginPage: React.FC = () => {
</div>
{/* 右侧登录区 */}
<div className="flex-1 bg-white p-8 flex flex-col justify-center min-h-[520px]">
<div className="mb-6">
<h3 className="text-xl font-bold text-gray-800 mb-1">欢迎登录</h3>
<div className="flex-1 bg-white p-8 md:p-10 flex flex-col justify-center min-h-[520px]">
{/* 移动端 Logo(仅在左侧面板隐藏时显示) */}
<div className="flex md:hidden items-center gap-2 mb-6">
<div className="w-9 h-9 rounded-xl bg-[#1890ff] flex items-center justify-center">
<MedicineBoxOutlined className="text-base text-white" />
</div>
<div className="text-base font-bold text-gray-800">互联网医院</div>
</div>
<div className="mb-8">
<h3 className="text-2xl font-bold text-gray-800 mb-2">欢迎登录</h3>
<p className="text-sm text-gray-400">登录以使用完整的医疗服务功能</p>
</div>
<Tabs items={tabItems} centered size="small" />
<div className="w-full max-w-[320px] mx-auto">
<Tabs items={tabItems} centered size="middle" className="login-tabs" />
<div className="text-center mt-4 text-sm">
<div className="text-center mt-6 text-sm">
<span className="text-gray-400">还没有账号?</span>
<Link href="/register" className="ml-1 font-medium">立即注册</Link>
<Link href="/register" className="ml-1 font-medium text-[#1890ff] hover:text-[#40a9ff]">立即注册</Link>
</div>
<div className="mt-6 pt-4 border-t border-gray-100 text-center">
<p className="text-xs text-gray-300">
<div className="mt-8 pt-4 border-t border-gray-100 text-center">
<p className="text-xs text-gray-400">
登录即表示同意
<a href="#" className="text-[#1890ff] mx-0.5">《用户服务协议》</a>
<a href="#" className="text-[#1890ff] mx-0.5">《隐私政策》</a>
<a href="#" className="text-[#1890ff] mx-0.5 hover:underline">《用户服务协议》</a>
<a href="#" className="text-[#1890ff] mx-0.5 hover:underline">《隐私政策》</a>
</p>
</div>
</div>
</div>
</div>
</div>
);
};
......
......@@ -72,7 +72,7 @@ const RegisterPage: React.FC = () => {
<div className="w-12 h-12 rounded-xl bg-linear-to-br from-[#1890ff] to-[#096dd9] flex items-center justify-center mx-auto mb-3">
<MedicineBoxOutlined className="text-xl text-white" />
</div>
<Title level={4} className="mb-1! text-gray-800!">注册互联网医院</Title>
<Title level={4} className="mb-1 text-gray-800">注册互联网医院</Title>
<Text type="secondary" className="text-xs">选择您的身份开始注册</Text>
</div>
......@@ -81,7 +81,7 @@ const RegisterPage: React.FC = () => {
{currentStep === 0 && (
<div>
<Paragraph className="text-center mb-4 text-gray-400 text-sm!">请选择您的注册身份</Paragraph>
<Paragraph className="text-center mb-4 text-gray-400 text-sm">请选择您的注册身份</Paragraph>
<Radio.Group value={registerType} onChange={(e) => setRegisterType(e.target.value)} className="w-full">
<Space direction="vertical" className="w-full">
<div className={`p-4 rounded-xl border-2 cursor-pointer transition-all ${registerType === 'patient' ? 'border-[#1890ff] bg-primary-bg' : 'border-gray-200 hover:border-blue-200'}`}
......@@ -92,8 +92,8 @@ const RegisterPage: React.FC = () => {
<UserOutlined className="text-[#1890ff]" />
</div>
<div>
<Text strong className="text-sm!">我是患者</Text><br />
<Text type="secondary" className="text-xs!">在线问诊、找医生、视频通话</Text>
<Text strong className="text-sm">我是患者</Text><br />
<Text type="secondary" className="text-xs">在线问诊、找医生、视频通话</Text>
</div>
</Space>
</Radio>
......@@ -106,8 +106,8 @@ const RegisterPage: React.FC = () => {
<MedicineBoxOutlined className="text-[#1890ff]" />
</div>
<div>
<Text strong className="text-sm!">我是医生</Text><br />
<Text type="secondary" className="text-xs!">在线接诊、管理排班、服务患者</Text>
<Text strong className="text-sm">我是医生</Text><br />
<Text type="secondary" className="text-xs">在线接诊、管理排班、服务患者</Text>
</div>
</Space>
</Radio>
......@@ -115,7 +115,7 @@ const RegisterPage: React.FC = () => {
</Space>
</Radio.Group>
<Button type="primary" block
className="mt-5! h-10! rounded-lg! font-semibold! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! shadow-md! shadow-blue-200!"
className="mt-5 h-10 rounded-lg font-semibold bg-linear-to-r from-[#1890ff] to-[#096dd9] border-none shadow-md shadow-blue-200"
onClick={() => setCurrentStep(1)}>下一步</Button>
</div>
)}
......@@ -158,8 +158,8 @@ const RegisterPage: React.FC = () => {
<Form.Item>
<Space className="w-full" direction="vertical">
<Button type="primary" htmlType="submit" loading={loading} block
className="h-10! rounded-lg! font-semibold! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! shadow-md! shadow-blue-200!">注册</Button>
<Button block className="rounded-lg!" onClick={() => setCurrentStep(0)}>上一步</Button>
className="h-10 rounded-lg font-semibold bg-linear-to-r from-[#1890ff] to-[#096dd9] border-none shadow-md shadow-blue-200">注册</Button>
<Button block className="rounded-lg" onClick={() => setCurrentStep(0)}>上一步</Button>
</Space>
</Form.Item>
</Form>
......@@ -170,7 +170,7 @@ const RegisterPage: React.FC = () => {
<div className="w-16 h-16 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4">
<CheckCircleOutlined className="text-3xl text-green-500" />
</div>
<Title level={3} className="mt-4!">注册成功!</Title>
<Title level={3} className="mt-4">注册成功!</Title>
<Paragraph type="secondary">正在跳转到{registerType === 'doctor' ? '医生工作台' : '首页'}...</Paragraph>
</div>
)}
......
/* 将 antd CSS-in-JS 层声明在最前,使其优先级低于 Tailwind utilities */
@layer antd;
@import "tailwindcss";
@theme inline {
......
......@@ -2,6 +2,7 @@
import React from 'react';
import { ConfigProvider, App as AntdApp } from 'antd';
import { StyleProvider } from '@ant-design/cssinjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import zhCN from 'antd/locale/zh_CN';
import 'dayjs/locale/zh-cn';
......@@ -18,6 +19,8 @@ const queryClient = new QueryClient({
export default function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{/* layer 模式:将 antd CSS-in-JS 注入到 @layer antd,使 Tailwind utilities 可以正常覆盖 antd 样式 */}
<StyleProvider layer>
<ConfigProvider
locale={zhCN}
theme={{
......@@ -34,6 +37,7 @@ export default function Providers({ children }: { children: React.ReactNode }) {
{children}
</AntdApp>
</ConfigProvider>
</StyleProvider>
</QueryClientProvider>
);
}
'use client';
import React, { useState, useEffect, useCallback } from 'react';
import {
Card, Table, Input, Select, Button, Space, Tag, Avatar, Modal, message,
Typography, Form, InputNumber, Row, Col,
} from 'antd';
import {
SearchOutlined, UserOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { adminApi } from '../../../api/admin';
import type { UserInfo } from '../../../api/user';
const { Text } = Typography;
const AdminPatientsPage: React.FC = () => {
const [loading, setLoading] = useState(false);
const [patients, setPatients] = useState<UserInfo[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [keyword, setKeyword] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('');
const [modalVisible, setModalVisible] = useState(false);
const [modalType, setModalType] = useState<'add' | 'edit'>('add');
const [editingRecord, setEditingRecord] = useState<UserInfo | null>(null);
const [modalForm] = Form.useForm();
const [modalLoading, setModalLoading] = useState(false);
const fetchPatients = useCallback(async () => {
setLoading(true);
try {
const res = await adminApi.getPatientList({ keyword, status: statusFilter, page, page_size: pageSize });
setPatients(res.data.list || []);
setTotal(res.data.total || 0);
} catch {
message.error('获取患者列表失败');
} finally {
setLoading(false);
}
}, [keyword, statusFilter, page, pageSize]);
useEffect(() => {
fetchPatients();
}, [fetchPatients]);
const handleSearch = () => {
setPage(1);
fetchPatients();
};
const handleToggleStatus = (record: UserInfo) => {
const newStatus = record.status === 'active' ? 'disabled' : 'active';
Modal.confirm({
title: `确认${newStatus === 'disabled' ? '禁用' : '启用'}该患者?`,
content: `患者:${record.real_name || record.phone}`,
onOk: async () => {
try {
await adminApi.updatePatientStatus(record.id, newStatus);
message.success('操作成功');
fetchPatients();
} catch {
message.error('操作失败');
}
},
});
};
const handleResetPassword = (record: UserInfo) => {
Modal.confirm({
title: '确认重置密码?',
content: `将重置患者 ${record.real_name || record.phone} 的密码为默认密码`,
onOk: async () => {
try {
await adminApi.resetPatientPassword(record.id);
message.success('密码已重置为 123456');
} catch {
message.error('重置失败');
}
},
});
};
const handleAdd = () => {
setModalType('add');
setEditingRecord(null);
modalForm.resetFields();
setModalVisible(true);
};
const handleEdit = (record: UserInfo) => {
setModalType('edit');
setEditingRecord(record);
modalForm.setFieldsValue({
real_name: record.real_name,
phone: record.phone,
gender: record.gender,
age: record.age,
});
setModalVisible(true);
};
const handleDelete = (record: UserInfo) => {
Modal.confirm({
title: '确认删除该患者?',
content: `患者:${record.real_name || record.phone},删除后不可恢复`,
okType: 'danger',
onOk: async () => {
try {
await adminApi.deletePatient(record.id);
message.success('删除成功');
fetchPatients();
} catch {
message.error('删除失败');
}
},
});
};
const handleModalSubmit = async () => {
try {
const values = await modalForm.validateFields();
setModalLoading(true);
if (modalType === 'add') {
await adminApi.createPatient({
phone: values.phone,
password: values.password || '123456',
real_name: values.real_name,
gender: values.gender,
age: values.age,
});
message.success('患者添加成功');
} else if (editingRecord) {
await adminApi.updatePatient(editingRecord.id, {
real_name: values.real_name,
phone: values.phone,
gender: values.gender,
age: values.age,
});
message.success('患者信息更新成功');
}
setModalVisible(false);
modalForm.resetFields();
fetchPatients();
} catch (error: any) {
if (!error?.errorFields) {
message.error('操作失败');
}
} finally {
setModalLoading(false);
}
};
const columns: ColumnsType<UserInfo> = [
{
title: '患者',
key: 'patient',
render: (_, record) => (
<Space>
<Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#1890ff' }} />
<div>
<Text strong>{record.real_name || '未填写'}</Text>
<br />
<Text type="secondary" style={{ fontSize: 12 }}>{record.phone}</Text>
</div>
</Space>
),
},
{ title: '性别', dataIndex: 'gender', key: 'gender', width: 80, render: (v: string) => v || '-' },
{ title: '年龄', dataIndex: 'age', key: 'age', width: 80, render: (v: number) => v || '-' },
{
title: '实名认证', dataIndex: 'is_verified', key: 'is_verified',
render: (verified: boolean) => <Tag color={verified ? 'green' : 'default'}>{verified ? '已认证' : '未认证'}</Tag>,
},
{
title: '状态', dataIndex: 'status', key: 'status',
render: (status: string) => <Tag color={status === 'active' ? 'green' : 'red'}>{status === 'active' ? '正常' : '已禁用'}</Tag>,
},
{
title: '注册时间', dataIndex: 'created_at', key: 'created_at',
render: (time: string) => time ? new Date(time).toLocaleString('zh-CN') : '-',
},
{
title: '操作', key: 'action', width: 260,
render: (_, record) => (
<Space size={0}>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
<Button type="link" size="small" danger={record.status === 'active'} onClick={() => handleToggleStatus(record)}>
{record.status === 'active' ? '禁用' : '启用'}
</Button>
<Button type="link" size="small" onClick={() => handleResetPassword(record)}>重置密码</Button>
<Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}>删除</Button>
</Space>
),
},
];
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">患者管理</h4>
<span className="text-xs text-gray-400">{total} 条记录</span>
</div>
<Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<Input placeholder="搜索姓名/手机号" prefix={<SearchOutlined />} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onPressEnter={handleSearch} style={{ width: 180 }} size="small" />
<Select placeholder="状态" allowClear size="small" value={statusFilter || undefined}
onChange={(v) => setStatusFilter(v || '')} style={{ width: 100 }}
options={[{ label: '正常', value: 'active' }, { label: '已禁用', value: 'disabled' }]} />
<Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
<Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setStatusFilter(''); setPage(1); }}>重置</Button>
</div>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加患者</Button>
</div>
</Card>
<Card size="small">
<Table columns={columns} dataSource={patients} rowKey="id" loading={loading} size="small"
pagination={{
current: page, pageSize, total, showTotal: (t) => `共 ${t} 条`, size: 'small',
onChange: (p, ps) => { setPage(p); setPageSize(ps); },
}} />
</Card>
<Modal title={modalType === 'add' ? '添加患者' : '编辑患者'} open={modalVisible}
onOk={handleModalSubmit} onCancel={() => setModalVisible(false)} confirmLoading={modalLoading} destroyOnClose width={440}>
<Form form={modalForm} layout="vertical" size="small">
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}>
<Input placeholder="患者姓名" />
</Form.Item>
<Form.Item name="phone" label="手机号" rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机号" />
</Form.Item>
{modalType === 'add' && (
<Form.Item name="password" label="初始密码">
<Input.Password placeholder="默认 123456" />
</Form.Item>
)}
<Row gutter={12}>
<Col span={12}>
<Form.Item name="gender" label="性别">
<Select placeholder="选择性别" allowClear options={[{ label: '', value: '' }, { label: '', value: '' }]} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="age" label="年龄">
<InputNumber style={{ width: '100%' }} min={1} max={150} placeholder="年龄" />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</div>
);
};
export default AdminPatientsPage;
......@@ -13,7 +13,7 @@ const AdminStatisticsPage: React.FC = () => {
<h4 className="text-sm font-bold text-gray-800 m-0">数据统计</h4>
<div className="flex items-center gap-2">
<Select defaultValue="month" size="small" style={{ width: 100 }}
options={[{ label: '??, value: 'week' }, { label: '?0?, value: 'month' }, { label: '?0?, value: 'quarter' }, { label: '??, value: 'year' }]} />
options={[{ label: '本周', value: 'week' }, { label: '本月', value: 'month' }, { label: '本季', value: 'quarter' }, { label: '本年', value: 'year' }]} />
<DatePicker.RangePicker size="small" />
</div>
</div>
......@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => {
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 12.5%</span>} /></Card>
</Col>
<Col xs={12} sm={6}>
<Card size="small"><Statistic title="问诊量 value={1280} prefix={<MessageOutlined />}
<Card size="small"><Statistic title="问诊量" value={1280} prefix={<MessageOutlined />}
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 8.3%</span>} /></Card>
</Col>
<Col xs={12} sm={6}>
<Card size="small"><Statistic title="总收入 value={56800} prefix={<DollarOutlined />} precision={0}
<Card size="small"><Statistic title="总收入" value={56800} prefix={<DollarOutlined />} precision={0}
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 15.2%</span>} /></Card>
</Col>
<Col xs={12} sm={6}>
......
......@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo {
}
const roleMap: Record<string, { text: string; color: string }> = {
patient: { text: '患者, color: 'blue' },
patient: { text: '患者', color: 'blue' },
doctor: { text: '医生', color: 'green' },
admin: { text: '管理员, color: 'purple' },
admin: { text: '管理员', color: 'purple' },
};
const AdminUsersPage: React.FC = () => {
......@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => {
onOk: async () => {
try {
await adminApi.resetUserPassword(userId);
message.success('密码已重);
message.success('密码已重');
} catch (error) {
message.error('重置密码失败');
}
......@@ -78,7 +78,7 @@ const AdminUsersPage: React.FC = () => {
onOk: async () => {
try {
await adminApi.updateUserStatus(userId, newStatus);
message.success(`?{action}`);
message.success(`${action}`);
fetchUsers();
} catch (error) {
message.error(`${action}失败`);
......@@ -118,12 +118,12 @@ const AdminUsersPage: React.FC = () => {
render: (v: boolean) => v ? <Tag color="success">已认证</Tag> : <Tag color="warning">未认证</Tag>,
},
{
title: '状态,
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁}
{status === 'active' ? '正常' : '已禁'}
</Tag>
),
},
......@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => {
<Card size="small">
<div className="flex flex-wrap items-center gap-2">
<Input placeholder="搜索用户名/手机号 prefix={<SearchOutlined />} value={keyword}
<Input placeholder="搜索用户名/手机号" prefix={<SearchOutlined />} value={keyword}
onChange={(e) => setKeyword(e.target.value)} style={{ width: 180 }} size="small" />
<Select placeholder="角色" style={{ width: 100 }} size="small" allowClear value={roleFilter}
onChange={(v) => setRoleFilter(v)} options={[
{ label: '患者, value: 'patient' }, { label: '医生', value: 'doctor' }, { label: '管理员, value: 'admin' },
{ label: '患者', value: 'patient' }, { label: '医生', value: 'doctor' }, { label: '管理员', value: 'admin' },
]} />
<Select placeholder="状态 style={{ width: 100 }} size="small" allowClear
options={[{ label: '正常', value: 'active' }, { label: '已禁用, value: 'disabled' }]} />
<Select placeholder="状态" style={{ width: 100 }} size="small" allowClear
options={[{ label: '正常', value: 'active' }, { label: '已禁用', value: 'disabled' }]} />
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
</div>
</Card>
......@@ -178,7 +178,7 @@ const AdminUsersPage: React.FC = () => {
<Table columns={columns} dataSource={usersData} rowKey="id" loading={loading} size="small"
pagination={{
current: page, pageSize, total, size: 'small', showSizeChanger: true,
showTotal: (t) => `?${t} 条`,
showTotal: (t) => `${t} 条`,
onChange: (p, ps) => { setPage(p); setPageSize(ps); },
}}
/>
......
......@@ -94,7 +94,7 @@ const DoctorCertificationPage: React.FC = () => {
<div className="w-14 h-14 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4">
<CheckCircleOutlined className="text-2xl text-green-500" />
</div>
<h3 className="text-base font-bold mb-2">认证申请已提</h3>
<h3 className="text-base font-bold mb-2">认证申请已提</h3>
<Paragraph type="secondary" className="text-xs!">
您的资质认证申请已成功提交,管理员将在 1-3 个工作日内完成审核。 </Paragraph>
<Button type="primary" size="small" onClick={() => router.push('/doctor/workbench')}>返回工作台</Button>
......@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => {
<Card size="small">
<Form form={form} layout="vertical" size="small" onFinish={handleSubmit}>
<Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证书 }]}>
<Input placeholder="请输入医师执业证书 />
<Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证书' }]}>
<Input placeholder="请输入医师执业证书号" />
</Form.Item>
<Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}>
<Select placeholder="请选择职称">
<Select.Option value="主任医师">主任医师</Select.Option>
<Select.Option value="副主任医>副主任医院/Select.Option>
<Select.Option value="副主任医师">副主任医师</Select.Option>
<Select.Option value="主治医师">主治医师</Select.Option>
<Select.Option value="住院医师">住院医师</Select.Option>
</Select>
</Form.Item>
<Form.Item name="hospital" label="所属医院 rules={[{ required: true, message: '请输入所属医院 }]}>
<Form.Item name="hospital" label="所属医院" rules={[{ required: true, message: '请输入所属医院' }]}>
<Input placeholder="请输入您所在的医院全称" />
</Form.Item>
<Form.Item name="department_name" label="科室" rules={[{ required: true, message: '请选择科室' }]}>
......@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => {
</Select>
</Form.Item>
<Form.Item label="执业证照片 required>
<Form.Item label="执业证照片" required>
<Upload
listType="picture-card"
fileList={licenseFileList}
......@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => {
maxCount={1}
>
{licenseFileList.length === 0 && (
<div><UploadOutlined /><div className="mt-1 text-xs">上传执业</div></div>
<div><UploadOutlined /><div className="mt-1 text-xs">上传执业</div></div>
)}
</Upload>
<Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text>
</Form.Item>
<Form.Item label="资格证照片 required>
<Form.Item label="资格证照片" required>
<Upload
listType="picture-card"
fileList={qualificationFileList}
......@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => {
maxCount={1}
>
{qualificationFileList.length === 0 && (
<div><UploadOutlined /><div className="mt-1 text-xs">上传资格</div></div>
<div><UploadOutlined /><div className="mt-1 text-xs">上传资格</div></div>
)}
</Upload>
<Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text>
......
......@@ -36,13 +36,13 @@ const mockData: ChronicPrescription[] = [
id: '1',
patient_name: '张三',
patient_age: 58,
patient_gender: '?,
patient_gender: '',
disease: '2型糖尿病',
last_prescription_date: '2026-01-25',
ai_draft: {
drugs: [
{ name: '二甲双胍缓释, spec: '0.5g/?, dosage: '每次1?, frequency: '每日2?, days: 30 },
{ name: '格列美脲, spec: '2mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
{ name: '二甲双胍缓释', spec: '0.5g/片', dosage: '每次1片', frequency: '每日2次', days: 30 },
{ name: '格列美脲', spec: '2mg/片', dosage: '每次1片', frequency: '每日1次', days: 30 },
],
note: '患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%',
},
......@@ -53,13 +53,13 @@ const mockData: ChronicPrescription[] = [
id: '2',
patient_name: '李四',
patient_age: 65,
patient_gender: '?,
disease: '高血压,
patient_gender: '',
disease: '高血压',
last_prescription_date: '2026-01-20',
ai_draft: {
drugs: [
{ name: '氨氯地平, spec: '5mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
{ name: '缬沙坦胶囊, spec: '80mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
{ name: '氨氯地平', spec: '5mg/片', dosage: '每次1片', frequency: '每日1次', days: 30 },
{ name: '缬沙坦胶囊', spec: '80mg/粒', dosage: '每次1粒', frequency: '每日1次', days: 30 },
],
note: '患者血压控制稳定,建议继续联合用药方案。近期血压 130/85mmHg',
},
......@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => {
const handleApprove = (id: string) => {
setData(data.map((d) => (d.id === id ? { ...d, status: 'approved' as const } : d)));
message.success('续方已审核通过并签名发送);
message.success('续方已审核通过并签名发送');
setDetailModalVisible(false);
};
......@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => {
Modal.confirm({
title: '拒绝续方申请',
content: (
<TextArea placeholder="请输入拒绝理.." rows={3} />
<TextArea placeholder="请输入拒绝理由..." rows={3} />
),
onOk: () => {
setData(data.map((d) => (d.id === id ? { ...d, status: 'rejected' as const } : d)));
message.info('续方申请已拒);
message.info('续方申请已拒');
setDetailModalVisible(false);
},
});
......@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => {
const getStatusTag = (status: string) => {
const map: Record<string, { color: string; text: string }> = {
pending: { color: 'orange', text: '待审核 },
pending: { color: 'orange', text: '待审核' },
approved: { color: 'green', text: '已通过' },
rejected: { color: 'red', text: '已拒绝 },
modified: { color: 'blue', text: '已修 },
rejected: { color: 'red', text: '已拒绝' },
modified: { color: 'blue', text: '已修' },
};
const s = map[status] || { color: 'default', text: status };
return <Tag color={s.color}>{s.text}</Tag>;
};
const columns = [
{ title: '患者姓名, dataIndex: 'patient_name', key: 'patient_name', width: 100 },
{ title: '患者姓名', dataIndex: 'patient_name', key: 'patient_name', width: 100 },
{
title: '基本信息',
key: 'info',
......@@ -119,15 +119,15 @@ const ChronicReviewPage: React.FC = () => {
render: (_: any, r: ChronicPrescription) => `${r.patient_gender} / ${r.patient_age}岁`,
},
{ title: '慢病诊断', dataIndex: 'disease', key: 'disease', width: 120 },
{ title: '上次开, dataIndex: 'last_prescription_date', key: 'last_prescription_date', width: 120 },
{ title: '上次开', dataIndex: 'last_prescription_date', key: 'last_prescription_date', width: 120 },
{
title: '药品数量',
key: 'drug_count',
width: 80,
render: (_: any, r: ChronicPrescription) => <Tag color="blue">{r.ai_draft.drugs.length} ?/Tag>,
render: (_: any, r: ChronicPrescription) => <Tag color="blue">{r.ai_draft.drugs.length} </Tag>,
},
{
title: '状态,
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
......@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => {
{currentRecord && (
<div>
<Descriptions bordered size="small" column={2} style={{ marginBottom: 16 }}>
<Descriptions.Item label="患者姓>{currentRecord.patient_name}</Descriptions.Item>
<Descriptions.Item label="性别/年龄">{currentRecord.patient_gender} / {currentRecord.patient_age}?/Descriptions.Item>
<Descriptions.Item label="患者姓名">{currentRecord.patient_name}</Descriptions.Item>
<Descriptions.Item label="性别/年龄">{currentRecord.patient_gender} / {currentRecord.patient_age}</Descriptions.Item>
<Descriptions.Item label="慢病诊断">{currentRecord.disease}</Descriptions.Item>
<Descriptions.Item label="上次开>{currentRecord.last_prescription_date}</Descriptions.Item>
<Descriptions.Item label="上次开方">{currentRecord.last_prescription_date}</Descriptions.Item>
</Descriptions>
<Card title="AI 续方草" size="small" style={{ marginBottom: 16, borderColor: '#52c41a' }}>
<Card title="AI 续方草稿" size="small" style={{ marginBottom: 16, borderColor: '#52c41a' }}>
<Table
size="small"
pagination={false}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment