Commit 7880e29e authored by yuguo's avatar yuguo

fix

parent 46cbe422
...@@ -7,7 +7,12 @@ ...@@ -7,7 +7,12 @@
"Bash(taskkill:*)", "Bash(taskkill:*)",
"Bash(powershell:*)", "Bash(powershell:*)",
"Bash(curl:*)", "Bash(curl:*)",
"Bash(netstat:*)" "Bash(netstat:*)",
"Bash(cat:*)",
"Bash(node:*)",
"Bash(ls:*)",
"Bash(npx tailwindcss:*)",
"Bash(npx next:*)"
] ]
} }
} }
const fs = require('fs');
const path = require('path');
function walk(dir) {
const files = [];
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, f.name);
if (f.isDirectory()) files.push(...walk(p));
else if (f.name.endsWith('.tsx') || f.name.endsWith('.ts')) files.push(p);
}
return files;
}
const pagesDir = path.join(__dirname, 'src', 'pages');
const files = walk(pagesDir);
const corrupt = [];
for (const f of files) {
const text = fs.readFileSync(f, 'utf8');
// Detect: garbled chars (Hebrew/Arabic range from GBK mis-decode),
// or Chinese followed by ? then closing tag, or replacement chars
const hasGarbled = /[\u0200-\u06FF]{2,}/.test(text) ||
/[\u4e00-\u9fff]\?[\/<'",}\]);\s]/.test(text) ||
text.includes('\ufffd');
if (hasGarbled) {
corrupt.push(path.relative(__dirname, f).replace(/\\/g, '/'));
}
}
corrupt.sort();
console.log(JSON.stringify(corrupt, null, 2));
console.log('Total:', corrupt.length);
This diff is collapsed.
This diff is collapsed.
const fs = require('fs');
const path = require('path');
function walk(dir) {
const files = [];
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, f.name);
if (f.isDirectory()) files.push(...walk(p));
else if (f.name.endsWith('.tsx') || f.name.endsWith('.ts')) files.push(p);
}
return files;
}
// Step 1: First restore all files from git or original source
// Since we can't do that, we need to re-read the original files
// But the files in src/pages ARE the damaged ones.
//
// The user says the original files are in src/pages.
// Wait - maybe they haven't been damaged yet and the damage only shows
// in some files. Let me check each file for corruption signs.
const pagesDir = path.join(__dirname, 'src', 'pages');
const files = walk(pagesDir);
let corruptCount = 0;
let fixedCount = 0;
for (const f of files) {
const buf = fs.readFileSync(f);
const text = buf.toString('utf8');
// Check for corruption: replacement chars or garbled text patterns
const hasCorruption = text.includes('\ufffd') ||
/[\u0200-\u06FF]{3,}/.test(text) || // Hebrew/Arabic blocks (garbled Chinese)
/[\u4e00-\u9fff]\?\/[a-z]/.test(text); // Chinese?/tag pattern
if (!hasCorruption) continue;
corruptCount++;
console.log('CORRUPT: ' + path.relative(__dirname, f));
}
console.log('\nTotal corrupt files: ' + corruptCount);
console.log('Total files scanned: ' + files.length);
'use client';
import React from 'react';
import { Card, Empty, Button } from 'antd';
import { useRouter } from 'next/navigation';
import { ArrowLeftOutlined } from '@ant-design/icons';
interface PagePlaceholderProps {
title?: string;
description?: string;
}
export default function PagePlaceholder({
title = '页面开发中',
description = '该页面正在开发中,敬请期待'
}: PagePlaceholderProps) {
const router = useRouter();
return (
<Card>
<Empty
description={
<div>
<div className="text-lg font-semibold mb-2">{title}</div>
<div className="text-gray-500">{description}</div>
</div>
}
>
<Button icon={<ArrowLeftOutlined />} onClick={() => router.back()}>
返回
</Button>
</Empty>
</Card>
);
}
This diff is collapsed.
...@@ -29,7 +29,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -29,7 +29,7 @@ const AdminAdminsPage: React.FC = () => {
setAdmins(res.data.list || []); setAdmins(res.data.list || []);
setTotal(res.data.total || 0); setTotal(res.data.total || 0);
} catch (error) { } catch (error) {
message.error('获取管理员列表失?); message.error('获取管理员列表失);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -63,7 +63,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -63,7 +63,7 @@ const AdminAdminsPage: React.FC = () => {
const handleResetPassword = (record: UserInfo) => { const handleResetPassword = (record: UserInfo) => {
Modal.confirm({ Modal.confirm({
title: '确认重置密码?, title: '确认重置密码,
content: `将重置管理员 ${record.real_name || record.phone} 的密码为默认密码`, content: `将重置管理员 ${record.real_name || record.phone} 的密码为默认密码`,
onOk: async () => { onOk: async () => {
try { try {
...@@ -97,7 +97,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -97,7 +97,7 @@ const AdminAdminsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('管理员信息更新成?); message.success('管理员信息更新成);
setEditModalVisible(false); setEditModalVisible(false);
editForm.resetFields(); editForm.resetFields();
fetchAdmins(); fetchAdmins();
...@@ -110,7 +110,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -110,7 +110,7 @@ const AdminAdminsPage: React.FC = () => {
const handleDeleteAdmin = (record: UserInfo) => { const handleDeleteAdmin = (record: UserInfo) => {
Modal.confirm({ Modal.confirm({
title: '确认删除该管理员?, title: '确认删除该管理员,
content: `管理员:${record.real_name || record.phone},删除后不可恢复`, content: `管理员:${record.real_name || record.phone},删除后不可恢复`,
okType: 'danger', okType: 'danger',
onOk: async () => { onOk: async () => {
...@@ -135,7 +135,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -135,7 +135,7 @@ const AdminAdminsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('管理员添加成?); message.success('管理员添加成);
setAddModalVisible(false); setAddModalVisible(false);
addForm.resetFields(); addForm.resetFields();
fetchAdmins(); fetchAdmins();
...@@ -148,13 +148,13 @@ const AdminAdminsPage: React.FC = () => { ...@@ -148,13 +148,13 @@ const AdminAdminsPage: React.FC = () => {
const columns: ColumnsType<UserInfo> = [ const columns: ColumnsType<UserInfo> = [
{ {
title: '管理?, title: '管理,
key: 'admin', key: 'admin',
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
<Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#722ed1' }} /> <Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#722ed1' }} />
<div> <div>
<Text strong>{record.real_name || '未填?}</Text> <Text strong>{record.real_name || '未填}</Text>
<br /> <br />
<Text type="secondary" style={{ fontSize: 12 }}>@{record.phone}</Text> <Text type="secondary" style={{ fontSize: 12 }}>@{record.phone}</Text>
</div> </div>
...@@ -162,7 +162,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -162,7 +162,7 @@ const AdminAdminsPage: React.FC = () => {
), ),
}, },
{ {
title: '用户?, title: '用户,
dataIndex: 'phone', dataIndex: 'phone',
key: 'phone', key: 'phone',
}, },
...@@ -181,12 +181,12 @@ const AdminAdminsPage: React.FC = () => { ...@@ -181,12 +181,12 @@ const AdminAdminsPage: React.FC = () => {
render: (age: number) => age || '-', render: (age: number) => age || '-',
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -227,19 +227,19 @@ const AdminAdminsPage: React.FC = () => { ...@@ -227,19 +227,19 @@ const AdminAdminsPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">管理员管?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">管理员管</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2"> <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)} onPressEnter={handleSearch} style={{ width: 180 }} size="small" /> onChange={(e) => setKeyword(e.target.value)} onPressEnter={handleSearch} style={{ width: 180 }} size="small" />
<Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button> <Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
<Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setPage(1); fetchAdmins(); }}>重置</Button> <Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setPage(1); fetchAdmins(); }}>重置</Button>
</div> </div>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={() => setAddModalVisible(true)}>添加管理?/Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={() => setAddModalVisible(true)}>添加管理</Button>
</div> </div>
</Card> </Card>
...@@ -261,13 +261,13 @@ const AdminAdminsPage: React.FC = () => { ...@@ -261,13 +261,13 @@ const AdminAdminsPage: React.FC = () => {
/> />
</Card> </Card>
<Modal title="添加管理? open={addModalVisible} onCancel={() => setAddModalVisible(false)} footer={null} width={450}> <Modal title="添加管理 open={addModalVisible} onCancel={() => setAddModalVisible(false)} footer={null} width={450}>
<Form form={addForm} layout="vertical" size="small" onFinish={handleAddAdmin}> <Form form={addForm} layout="vertical" size="small" onFinish={handleAddAdmin}>
<Form.Item name="username" label="用户? rules={[{ required: true, message: '请输入用户名' }]}> <Form.Item name="username" label="用户 rules={[{ required: true, message: '请输入用户名' }]}>
<Input placeholder="用户名(用于登录? /> <Input placeholder="用户名(用于登录 />
</Form.Item> </Form.Item>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓 }]}>
<Input placeholder="管理员姓? /> <Input placeholder="管理员姓 />
</Form.Item> </Form.Item>
<Form.Item name="gender" label="性别"> <Form.Item name="gender" label="性别">
<Input placeholder="性别" /> <Input placeholder="性别" />
...@@ -290,18 +290,18 @@ const AdminAdminsPage: React.FC = () => { ...@@ -290,18 +290,18 @@ const AdminAdminsPage: React.FC = () => {
</Modal> </Modal>
<Modal <Modal
title="编辑管理? title="编辑管理
open={editModalVisible} open={editModalVisible}
onCancel={() => setEditModalVisible(false)} onCancel={() => setEditModalVisible(false)}
footer={null} footer={null}
width={500} width={500}
> >
<Form form={editForm} layout="vertical" onFinish={handleEditSubmit}> <Form form={editForm} layout="vertical" onFinish={handleEditSubmit}>
<Form.Item name="username" label="用户? rules={[{ required: true, message: '请输入用户名' }]}> <Form.Item name="username" label="用户 rules={[{ required: true, message: '请输入用户名' }]}>
<Input placeholder="用户? /> <Input placeholder="用户 />
</Form.Item> </Form.Item>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓 }]}>
<Input placeholder="管理员姓? /> <Input placeholder="管理员姓 />
</Form.Item> </Form.Item>
<Form.Item name="gender" label="性别"> <Form.Item name="gender" label="性别">
<Input placeholder="性别" /> <Input placeholder="性别" />
......
...@@ -40,17 +40,17 @@ interface ReportItem { ...@@ -40,17 +40,17 @@ interface ReportItem {
} }
const mockLogs: AuditLog[] = [ const mockLogs: AuditLog[] = [
{ id: '1', timestamp: '2026-02-25 18:30:15', user: '管理?, role: 'admin', action: '审核通过', module: '医生管理', ip: '192.168.1.100', detail: '审核通过医生 周医?的资质认证申?, level: 'info' }, { id: '1', timestamp: '2026-02-25 18:30:15', user: '管理员, role: 'admin', action: '审核通过', module: '医生管理', ip: '192.168.1.100', detail: '审核通过医生 周医?的资质认证申, level: 'info' },
{ id: '2', timestamp: '2026-02-25 17:45:22', user: '陈医?, role: 'doctor', action: '开具处?, module: '处方管理', ip: '192.168.1.105', detail: '为患者张三开具处?RX20260225001', level: 'info' }, { id: '2', timestamp: '2026-02-25 17:45:22', user: '陈医院, role: 'doctor', action: '开具处方, module: '处方管理', ip: '192.168.1.105', detail: '为患者张三开具处?RX20260225001', level: 'info' },
{ id: '3', timestamp: '2026-02-25 16:20:08', user: '管理?, role: 'admin', action: '修改配置', module: 'AI配置', ip: '192.168.1.100', detail: '修改 AI 模型 temperature 参数?0.5 ?0.3', level: 'warning' }, { id: '3', timestamp: '2026-02-25 16:20:08', user: '管理, role: 'admin', action: '修改配置', module: 'AI配置', ip: '192.168.1.100', detail: '修改 AI 模型 temperature 参数?0.5 ?0.3', level: 'warning' },
{ id: '4', timestamp: '2026-02-25 15:10:33', user: '未知用户', role: 'unknown', action: '登录失败', module: '系统安全', ip: '10.0.0.55', detail: '连续3次密码错误,账户已临时锁?, level: 'danger' }, { id: '4', timestamp: '2026-02-25 15:10:33', user: '未知用户', role: 'unknown', action: '登录失败', module: '系统安全', ip: '10.0.0.55', detail: '连续3次密码错误,账户已临时锁, level: 'danger' },
{ id: '5', timestamp: '2026-02-25 14:05:11', user: '管理?, role: 'admin', action: '数据导出', module: '数据管理', ip: '192.168.1.100', detail: '导出本月问诊数据报表', level: 'info' }, { id: '5', timestamp: '2026-02-25 14:05:11', user: '管理, role: 'admin', action: '数据导出', module: '数据管理', ip: '192.168.1.100', detail: '导出本月问诊数据报表', level: 'info' },
{ id: '6', timestamp: '2026-02-25 11:30:45', user: '刘医?, role: 'doctor', action: '异常处方', module: '处方监管', ip: '192.168.1.108', detail: '处方 RX20260225004 被系统标记为异常:抗生素使用不符合指?, level: 'danger' }, { id: '6', timestamp: '2026-02-25 11:30:45', user: '刘医, role: 'doctor', action: '异常处方', module: '处方监管', ip: '192.168.1.108', detail: '处方 RX20260225004 被系统标记为异常:抗生素使用不符合指, level: 'danger' },
]; ];
const mockReports: ReportItem[] = [ const mockReports: ReportItem[] = [
{ id: '1', name: '2026?月运营月?, type: '月度报告', period: '2026-02', status: 'pending' }, { id: '1', name: '2026?月运营月, type: '月度报告', period: '2026-02', status: 'pending' },
{ id: '2', name: '2026?月运营月?, type: '月度报告', period: '2026-01', status: 'submitted', generated_at: '2026-02-05', submitted_at: '2026-02-10' }, { id: '2', name: '2026?月运营月, type: '月度报告', period: '2026-01', status: 'submitted', generated_at: '2026-02-05', submitted_at: '2026-02-10' },
{ id: '3', name: '2025Q4季度合规报告', type: '季度报告', period: '2025-Q4', status: 'submitted', generated_at: '2026-01-10', submitted_at: '2026-01-15' }, { id: '3', name: '2025Q4季度合规报告', type: '季度报告', period: '2025-Q4', status: 'submitted', generated_at: '2026-01-10', submitted_at: '2026-01-15' },
{ id: '4', name: '处方用药合规专项报告', type: '专项报告', period: '2026-02', status: 'generated', generated_at: '2026-02-20' }, { id: '4', name: '处方用药合规专项报告', type: '专项报告', period: '2026-02', status: 'generated', generated_at: '2026-02-20' },
{ id: '5', name: '等保三级自查报告', type: '安全报告', period: '2026-02', status: 'pending' }, { id: '5', name: '等保三级自查报告', type: '安全报告', period: '2026-02', status: 'pending' },
...@@ -75,9 +75,9 @@ const AdminCompliancePage: React.FC = () => { ...@@ -75,9 +75,9 @@ const AdminCompliancePage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
switch (status) { switch (status) {
case 'generated': return <Tag color="blue">已生?/Tag>; case 'generated': return <Tag color="blue">已生</Tag>;
case 'pending': return <Tag color="orange">待生?/Tag>; case 'pending': return <Tag color="orange">待生</Tag>;
case 'submitted': return <Tag color="green">已上?/Tag>; case 'submitted': return <Tag color="green">已上</Tag>;
default: return <Tag>{status}</Tag>; default: return <Tag>{status}</Tag>;
} }
}; };
...@@ -88,7 +88,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -88,7 +88,7 @@ const AdminCompliancePage: React.FC = () => {
{ {
title: '角色', dataIndex: 'role', key: 'role', width: 80, title: '角色', dataIndex: 'role', key: 'role', width: 80,
render: (v: string) => { render: (v: string) => {
const map: Record<string, string> = { admin: '管理?, doctor: '医生', patient: '患?, unknown: '未知' }; const map: Record<string, string> = { admin: '管理, doctor: '医生', patient: '患者, unknown: '未知' };
return <Tag>{map[v] || v}</Tag>; return <Tag>{map[v] || v}</Tag>;
}, },
}, },
...@@ -103,7 +103,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -103,7 +103,7 @@ const AdminCompliancePage: React.FC = () => {
{ title: '报告名称', dataIndex: 'name', key: 'name', render: (v: string) => <Text strong>{v}</Text> }, { title: '报告名称', dataIndex: 'name', key: 'name', render: (v: string) => <Text strong>{v}</Text> },
{ title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => <Tag color="purple">{v}</Tag> }, { title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => <Tag color="purple">{v}</Tag> },
{ title: '周期', dataIndex: 'period', key: 'period', width: 100 }, { title: '周期', dataIndex: 'period', key: 'period', width: 100 },
{ title: '?, dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) }, { title: ', dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) },
{ title: '生成时间', dataIndex: 'generated_at', key: 'generated_at', width: 120, render: (v: string) => v || '-' }, { title: '生成时间', dataIndex: 'generated_at', key: 'generated_at', width: 120, render: (v: string) => v || '-' },
{ title: '上报时间', dataIndex: 'submitted_at', key: 'submitted_at', width: 120, render: (v: string) => v || '-' }, { title: '上报时间', dataIndex: 'submitted_at', key: 'submitted_at', width: 120, render: (v: string) => v || '-' },
{ {
...@@ -142,12 +142,12 @@ const AdminCompliancePage: React.FC = () => { ...@@ -142,12 +142,12 @@ const AdminCompliancePage: React.FC = () => {
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #52c41a' }}> <Card size="small" style={{ borderLeft: '3px solid #52c41a' }}>
<Statistic title="已上? value={mockReports.filter((r) => r.status === 'submitted').length} suffix="? /> <Statistic title="已上 value={mockReports.filter((r) => r.status === 'submitted').length} suffix="? />
</Card> </Card>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #722ed1' }}> <Card size="small" style={{ borderLeft: '3px solid #722ed1' }}>
<Statistic title="待生? value={mockReports.filter((r) => r.status === 'pending').length} valueStyle={{ color: '#fa8c16' }} suffix="? /> <Statistic title="待生 value={mockReports.filter((r) => r.status === 'pending').length} valueStyle={{ color: '#fa8c16' }} suffix="? />
</Card> </Card>
</Col> </Col>
</Row> </Row>
...@@ -181,10 +181,10 @@ const AdminCompliancePage: React.FC = () => { ...@@ -181,10 +181,10 @@ const AdminCompliancePage: React.FC = () => {
<Card size="small"> <Card size="small">
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{[ {[
{ title: '问诊数据', desc: '问诊记录、诊断、处?, icon: <FileSearchOutlined className="text-xl text-[#1890ff]" /> }, { title: '问诊数据', desc: '问诊记录、诊断、处, icon: <FileSearchOutlined className="text-xl text-[#1890ff]" /> },
{ title: '用户数据', desc: '患者和医生基本信息', icon: <UserOutlined className="text-xl text-green-500" /> }, { title: '用户数据', desc: '患者和医生基本信息', icon: <UserOutlined className="text-xl text-green-500" /> },
{ title: '处方数据', desc: '处方记录、用药统?, icon: <AuditOutlined className="text-xl text-purple-500" /> }, { title: '处方数据', desc: '处方记录、用药统, icon: <AuditOutlined className="text-xl text-purple-500" /> },
{ title: '系统日志', desc: '操作日志和审计记?, icon: <SettingOutlined className="text-xl text-orange-500" /> }, { title: '系统日志', desc: '操作日志和审计记, icon: <SettingOutlined className="text-xl text-orange-500" /> },
].map((item, i) => ( ].map((item, i) => (
<Col span={6} key={i}> <Col span={6} key={i}>
<Card size="small" hoverable className="text-center" onClick={() => setExportModalVisible(true)}> <Card size="small" hoverable className="text-center" onClick={() => setExportModalVisible(true)}>
...@@ -205,7 +205,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -205,7 +205,7 @@ const AdminCompliancePage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs text-gray-400">管理合规报告,支持下载和上报</span> <span className="text-xs text-gray-400">管理合规报告,支持下载和上报</span>
<Button type="primary" size="small">生成新报?/Button> <Button type="primary" size="small">生成新报</Button>
</div> </div>
<Table columns={reportColumns} dataSource={mockReports} rowKey="id" size="small" pagination={false} /> <Table columns={reportColumns} dataSource={mockReports} rowKey="id" size="small" pagination={false} />
</Card> </Card>
...@@ -229,7 +229,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -229,7 +229,7 @@ const AdminCompliancePage: React.FC = () => {
<div> <div>
<Text strong className="text-xs!">数据脱敏</Text> <Text strong className="text-xs!">数据脱敏</Text>
<Select defaultValue="partial" size="small" style={{ width: '100%', marginTop: 4 }} options={[ <Select defaultValue="partial" size="small" style={{ width: '100%', marginTop: 4 }} options={[
{ label: '部分脱敏(推荐)', value: 'partial' }, { label: '完全脱敏', value: 'full' }, { label: '不脱?, value: 'none' }, { label: '部分脱敏(推荐)', value: 'partial' }, { label: '完全脱敏', value: 'full' }, { label: '不脱, value: 'none' },
]} /> ]} />
</div> </div>
</div> </div>
......
...@@ -12,11 +12,11 @@ const { Title } = Typography; ...@@ -12,11 +12,11 @@ const { Title } = Typography;
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待支?, color: 'orange' }, pending: { text: '待支, color: 'orange' },
waiting: { text: '等待接诊', color: 'processing' }, waiting: { text: '等待接诊', color: 'processing' },
in_progress: { text: '进行?, color: 'blue' }, in_progress: { text: '进行, color: 'blue' },
completed: { text: '已完?, color: 'green' }, completed: { text: '已完, color: 'green' },
cancelled: { text: '已取?, color: 'red' }, cancelled: { text: '已取, color: 'red' },
}; };
const AdminConsultationsPage: React.FC = () => { const AdminConsultationsPage: React.FC = () => {
...@@ -70,7 +70,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -70,7 +70,7 @@ const AdminConsultationsPage: React.FC = () => {
render: (v: string) => v ? v.substring(0, 8) + '...' : '-', render: (v: string) => v ? v.substring(0, 8) + '...' : '-',
}, },
{ {
title: '?, title: ',
dataIndex: 'patient_name', dataIndex: 'patient_name',
key: 'patient_name', key: 'patient_name',
render: (name: string) => ( render: (name: string) => (
...@@ -84,7 +84,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -84,7 +84,7 @@ const AdminConsultationsPage: React.FC = () => {
title: '医生', title: '医生',
dataIndex: 'doctor_name', dataIndex: 'doctor_name',
key: 'doctor_name', key: 'doctor_name',
render: (name: string) => name || '未分?, render: (name: string) => name || '未分,
}, },
{ {
title: '类型', title: '类型',
...@@ -107,7 +107,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -107,7 +107,7 @@ const AdminConsultationsPage: React.FC = () => {
ellipsis: true, ellipsis: true,
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 100, width: 100,
...@@ -137,7 +137,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -137,7 +137,7 @@ const AdminConsultationsPage: React.FC = () => {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">问诊管理</h4> <h4 className="text-sm font-bold text-gray-800 m-0">问诊管理</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
...@@ -164,7 +164,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -164,7 +164,7 @@ const AdminConsultationsPage: React.FC = () => {
]} ]}
/> />
<Select <Select
placeholder="状? placeholder="状
style={{ width: 100 }} style={{ width: 100 }}
size="small" size="small"
allowClear allowClear
......
...@@ -40,7 +40,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -40,7 +40,7 @@ const AdminDashboardPage: React.FC = () => {
setStats(statsRes.data); setStats(statsRes.data);
setRecentReviews(reviewsRes.data.list?.slice(0, 3) || []); setRecentReviews(reviewsRes.data.list?.slice(0, 3) || []);
} catch (error) { } catch (error) {
console.error('获取仪表盘数据失?', error); console.error('获取仪表盘数据失', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -85,14 +85,14 @@ const AdminDashboardPage: React.FC = () => { ...@@ -85,14 +85,14 @@ const AdminDashboardPage: React.FC = () => {
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<div className="flex justify-between mb-1"> <div className="flex justify-between mb-1">
<Text className="text-xs!">医生在线?/Text> <Text className="text-xs!">医生在线</Text>
<Text strong className="text-xs!">{stats.total_doctors > 0 ? ((stats.online_doctors / stats.total_doctors) * 100).toFixed(0) : 0}%</Text> <Text strong className="text-xs!">{stats.total_doctors > 0 ? ((stats.online_doctors / stats.total_doctors) * 100).toFixed(0) : 0}%</Text>
</div> </div>
<Progress percent={stats.total_doctors > 0 ? Math.round((stats.online_doctors / stats.total_doctors) * 100) : 0} strokeColor="#52c41a" showInfo={false} size="small" /> <Progress percent={stats.total_doctors > 0 ? Math.round((stats.online_doctors / stats.total_doctors) * 100) : 0} strokeColor="#52c41a" showInfo={false} size="small" />
</div> </div>
<div> <div>
<div className="flex justify-between mb-1"> <div className="flex justify-between mb-1">
<Text className="text-xs!">今日完成?/Text> <Text className="text-xs!">今日完成</Text>
<Text strong className="text-xs!">{stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0}%</Text> <Text strong className="text-xs!">{stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0}%</Text>
</div> </div>
<Progress percent={stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0} strokeColor="#1890ff" showInfo={false} size="small" /> <Progress percent={stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0} strokeColor="#1890ff" showInfo={false} size="small" />
...@@ -110,7 +110,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -110,7 +110,7 @@ const AdminDashboardPage: React.FC = () => {
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Card <Card
title={<span className="text-xs font-semibold">待审核医?<Tag color="orange" className="ml-1">{stats.pending_doctor_reviews}</Tag></span>} title={<span className="text-xs font-semibold">待审核医<Tag color="orange" className="ml-1">{stats.pending_doctor_reviews}</Tag></span>}
size="small" size="small"
className="h-full!" className="h-full!"
> >
...@@ -124,7 +124,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -124,7 +124,7 @@ const AdminDashboardPage: React.FC = () => {
title={<span className="text-xs"><Text strong>{item.name}</Text> <Tag className="ml-1">{item.title}</Tag></span>} title={<span className="text-xs"><Text strong>{item.name}</Text> <Tag className="ml-1">{item.title}</Tag></span>}
description={<span className="text-[11px]">{item.department_name} · {item.submitted_at}</span>} description={<span className="text-[11px]">{item.department_name} · {item.submitted_at}</span>}
/> />
<Tag color="orange" className="text-[10px]!">待审?/Tag> <Tag color="orange" className="text-[10px]!">待审</Tag>
</List.Item> </List.Item>
)} )}
/> />
...@@ -138,7 +138,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -138,7 +138,7 @@ const AdminDashboardPage: React.FC = () => {
{ icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', label: '累计问诊', value: stats.total_consultations.toLocaleString() }, { icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', label: '累计问诊', value: stats.total_consultations.toLocaleString() },
{ icon: <RiseOutlined />, color: '#fa8c16', bg: '#fff7e6', label: '本月收入', value: `¥${(stats.revenue_month / 100).toLocaleString()}` }, { icon: <RiseOutlined />, color: '#fa8c16', bg: '#fff7e6', label: '本月收入', value: `¥${(stats.revenue_month / 100).toLocaleString()}` },
{ icon: <ClockCircleOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '平均响应', value: '3.2分钟' }, { icon: <ClockCircleOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '平均响应', value: '3.2分钟' },
{ icon: <AuditOutlined />, color: '#722ed1', bg: '#f9f0ff', label: '待审?, value: stats.pending_doctor_reviews }, { icon: <AuditOutlined />, color: '#722ed1', bg: '#f9f0ff', label: '待审, value: stats.pending_doctor_reviews },
].map((item, i) => ( ].map((item, i) => (
<Col xs={12} sm={6} key={i}> <Col xs={12} sm={6} key={i}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center">
......
...@@ -25,7 +25,7 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -25,7 +25,7 @@ const AdminDepartmentsPage: React.FC = () => {
const res = await adminApi.getDepartmentList(); const res = await adminApi.getDepartmentList();
setDepartments(res.data || []); setDepartments(res.data || []);
} catch (error) { } catch (error) {
message.error('鑾峰彇绉戝鍒楄〃澶辫触'); message.error('获取种戝列表失败');
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -49,15 +49,15 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -49,15 +49,15 @@ const AdminDepartmentsPage: React.FC = () => {
const handleDelete = (record: Department) => { const handleDelete = (record: Department) => {
Modal.confirm({ Modal.confirm({
title: '纭鍒犻櫎', title: '确删除',
content: `纭畾瑕佸垹闄ょ瀹?"${record.name}" 鍚楋紵璇ユ搷浣滀笉鍙仮澶嶃€俙, content: `确畾瑕佸垹闄ょ瀹"${record.name}" 鍚楋紵璇ユ搷浣滀笉号仮澶嶃€俙,
onOk: async () => { onOk: async () => {
try { try {
await adminApi.deleteDepartment(record.id); await adminApi.deleteDepartment(record.id);
message.success('宸插垹闄?); message.success('已删除);
fetchDepartments(); fetchDepartments();
} catch (error) { } catch (error) {
message.error('鍒犻櫎澶辫触'); message.error('删除失败');
} }
}, },
}); });
...@@ -68,15 +68,15 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -68,15 +68,15 @@ const AdminDepartmentsPage: React.FC = () => {
try { try {
if (editingDept) { if (editingDept) {
await adminApi.updateDepartment(editingDept.id, values); await adminApi.updateDepartment(editingDept.id, values);
message.success('绉戝鏇存柊鎴愬姛'); message.success('种戝鏇存柊成功');
} else { } else {
await adminApi.createDepartment(values); await adminApi.createDepartment(values);
message.success('绉戝鍒涘缓鎴愬姛'); message.success('种戝鍒涘缓成功');
} }
setIsModalOpen(false); setIsModalOpen(false);
fetchDepartments(); fetchDepartments();
} catch (error) { } catch (error) {
message.error('鎿嶄綔澶辫触'); message.error('操作失败');
} finally { } finally {
setSaveLoading(false); setSaveLoading(false);
} }
...@@ -84,33 +84,33 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -84,33 +84,33 @@ const AdminDepartmentsPage: React.FC = () => {
const columns: ColumnsType<Department> = [ const columns: ColumnsType<Department> = [
{ {
title: '鍥炬爣', title: '图标',
dataIndex: 'icon', dataIndex: 'icon',
key: 'icon', key: 'icon',
width: 60, width: 60,
render: (icon: string) => <span style={{ fontSize: 24 }}>{icon || '馃彞'}</span>, render: (icon: string) => <span style={{ fontSize: 24 }}>{icon || '🏥'}</span>,
}, },
{ {
title: '戝鍚嶇О', title: '戝鍚嶇О',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
render: (name: string) => <Text strong>{name}</Text>, render: (name: string) => <Text strong>{name}</Text>,
}, },
{ {
title: '鎺掑簭', title: '排序',
dataIndex: 'sort_order', dataIndex: 'sort_order',
key: 'sort_order', key: 'sort_order',
}, },
{ {
title: '鎿嶄綔', title: '操作',
key: 'action', key: 'action',
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
缂栬緫 编辑
</Button> </Button>
<Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}> <Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}>
鍒犻櫎 删除
</Button> </Button>
</Space> </Space>
), ),
...@@ -120,8 +120,8 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -120,8 +120,8 @@ const AdminDepartmentsPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">戝绠$悊</h4> <h4 className="text-sm font-bold text-gray-800 m-0">戝绠$悊</h4>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>娣诲姞绉戝</Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加科室</Button>
</div> </div>
<Card size="small"> <Card size="small">
...@@ -136,7 +136,7 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -136,7 +136,7 @@ const AdminDepartmentsPage: React.FC = () => {
</Card> </Card>
<Modal <Modal
title={editingDept ? '缂栬緫绉戝' : '娣诲姞绉戝'} title={editingDept ? '编辑科室' : '添加科室'}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
onOk={() => form.submit()} onOk={() => form.submit()}
...@@ -144,13 +144,13 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -144,13 +144,13 @@ const AdminDepartmentsPage: React.FC = () => {
width={400} width={400}
> >
<Form form={form} onFinish={handleSave} layout="vertical" size="small"> <Form form={form} onFinish={handleSave} layout="vertical" size="small">
<Form.Item name="name" label="绉戝鍚嶇О" rules={[{ required: true, message: '璇疯緭鍏ョ瀹ゅ悕绉? }]}> <Form.Item name="name" label="种戝鍚嶇О" rules={[{ required: true, message: '请输入ョ瀹ゅ悕种 }]}>
<Input placeholder="璇疯緭鍏ョ瀹ゅ悕绉? /> <Input placeholder="请输入ョ瀹ゅ悕种 />
</Form.Item> </Form.Item>
<Form.Item name="icon" label="鍥炬爣"> <Form.Item name="icon" label="图标">
<Input placeholder="璇疯緭鍏moji鍥炬爣" /> <Input placeholder="请输入moji图标" />
</Form.Item> </Form.Item>
<Form.Item name="sort_order" label="鎺掑簭鍙? initialValue={1}> <Form.Item name="sort_order" label="排序号 initialValue={1}>
<InputNumber min={1} style={{ width: '100%' }} /> <InputNumber min={1} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Form> </Form>
......
...@@ -7,9 +7,9 @@ import { adminApi, type DoctorReviewItem } from '../../../api/admin'; ...@@ -7,9 +7,9 @@ import { adminApi, type DoctorReviewItem } from '../../../api/admin';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待审?, color: 'orange' }, pending: { text: '待审, color: 'orange' },
approved: { text: '已通过', color: 'green' }, approved: { text: '已通过', color: 'green' },
rejected: { text: '已拒?, color: 'red' }, rejected: { text: '已拒, color: 'red' },
}; };
const AdminDoctorReviewPage: React.FC = () => { const AdminDoctorReviewPage: React.FC = () => {
...@@ -56,8 +56,8 @@ const AdminDoctorReviewPage: React.FC = () => { ...@@ -56,8 +56,8 @@ const AdminDoctorReviewPage: React.FC = () => {
icon: <CloseCircleOutlined style={{ color: '#ff4d4f' }} />, icon: <CloseCircleOutlined style={{ color: '#ff4d4f' }} />,
onOk: async () => { onOk: async () => {
try { try {
await adminApi.rejectDoctorReview(record.id, '资质不符合要?); await adminApi.rejectDoctorReview(record.id, '资质不符合要);
message.success('已拒?); message.success('已拒);
fetchReviews(); fetchReviews();
} catch (error) { } catch (error) {
message.error('操作失败'); message.error('操作失败');
...@@ -76,10 +76,10 @@ const AdminDoctorReviewPage: React.FC = () => { ...@@ -76,10 +76,10 @@ const AdminDoctorReviewPage: React.FC = () => {
<Descriptions.Item label="电话">{record.phone}</Descriptions.Item> <Descriptions.Item label="电话">{record.phone}</Descriptions.Item>
<Descriptions.Item label="执业证号">{record.license_no}</Descriptions.Item> <Descriptions.Item label="执业证号">{record.license_no}</Descriptions.Item>
<Descriptions.Item label="职称">{record.title}</Descriptions.Item> <Descriptions.Item label="职称">{record.title}</Descriptions.Item>
<Descriptions.Item label="所属医?>{record.hospital}</Descriptions.Item> <Descriptions.Item label="所属医>{record.hospital}</Descriptions.Item>
<Descriptions.Item label="科室">{record.department_name}</Descriptions.Item> <Descriptions.Item label="科室">{record.department_name}</Descriptions.Item>
<Descriptions.Item label="提交时间">{record.submitted_at}</Descriptions.Item> <Descriptions.Item label="提交时间">{record.submitted_at}</Descriptions.Item>
<Descriptions.Item label="审核状?> <Descriptions.Item label="审核状>
<Tag color={statusMap[record.status].color}> <Tag color={statusMap[record.status].color}>
{statusMap[record.status].text} {statusMap[record.status].text}
</Tag> </Tag>
...@@ -131,7 +131,7 @@ const AdminDoctorReviewPage: React.FC = () => { ...@@ -131,7 +131,7 @@ const AdminDoctorReviewPage: React.FC = () => {
key: 'submitted_at', key: 'submitted_at',
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => { render: (status: string) => {
......
...@@ -89,12 +89,12 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -89,12 +89,12 @@ const AdminDoctorsPage: React.FC = () => {
const handleReject = async () => { const handleReject = async () => {
if (!currentDoctor?.review_id || !rejectReason) { if (!currentDoctor?.review_id || !rejectReason) {
message.error('请输入拒绝原?); message.error('请输入拒绝原);
return; return;
} }
try { try {
await adminApi.rejectDoctorReview(currentDoctor.review_id, rejectReason); await adminApi.rejectDoctorReview(currentDoctor.review_id, rejectReason);
message.success('已拒?); message.success('已拒);
setReviewModalVisible(false); setReviewModalVisible(false);
setRejectReason(''); setRejectReason('');
fetchDoctors(); fetchDoctors();
...@@ -122,7 +122,7 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -122,7 +122,7 @@ const AdminDoctorsPage: React.FC = () => {
const handleResetPassword = (record: DoctorItem) => { const handleResetPassword = (record: DoctorItem) => {
Modal.confirm({ Modal.confirm({
title: '确认重置密码?, title: '确认重置密码,
content: `将重置医?${record.name} 的密码为默认密码 123456`, content: `将重置医?${record.name} 的密码为默认密码 123456`,
onOk: async () => { onOk: async () => {
try { try {
...@@ -137,10 +137,10 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -137,10 +137,10 @@ const AdminDoctorsPage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
const statusMap: Record<string, { color: string; text: string }> = { const statusMap: Record<string, { color: string; text: string }> = {
not_submitted: { color: 'default', text: '未提交认? }, not_submitted: { color: 'default', text: '未提交认 },
pending: { color: 'orange', text: '待审? }, pending: { color: 'orange', text: '待审 },
approved: { color: 'green', text: '已认? }, approved: { color: 'green', text: '已认 },
rejected: { color: 'red', text: '已拒? }, rejected: { color: 'red', text: '已拒 },
}; };
const config = statusMap[status] || { color: 'default', text: status }; const config = statusMap[status] || { color: 'default', text: status };
return <Tag color={config.color}>{config.text}</Tag>; return <Tag color={config.color}>{config.text}</Tag>;
...@@ -275,20 +275,20 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -275,20 +275,20 @@ const AdminDoctorsPage: React.FC = () => {
render: (hospital: string) => hospital || '-', render: (hospital: string) => hospital || '-',
}, },
{ {
title: '认证状?, title: '认证状,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 120, width: 120,
render: (status: string) => getStatusTag(status), render: (status: string) => getStatusTag(status),
}, },
{ {
title: '账号状?, title: '账号状,
dataIndex: 'user_status', dataIndex: 'user_status',
key: 'user_status', key: 'user_status',
width: 100, width: 100,
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -336,14 +336,14 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -336,14 +336,14 @@ const AdminDoctorsPage: React.FC = () => {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">医生管理</h4> <h4 className="text-sm font-bold text-gray-800 m-0">医生管理</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
placeholder="搜索姓名/手机? placeholder="搜索姓名/手机
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
value={keyword} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
...@@ -384,10 +384,10 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -384,10 +384,10 @@ const AdminDoctorsPage: React.FC = () => {
width={500} width={500}
> >
<Form form={addForm} layout="vertical" size="small" onFinish={handleAddDoctor}> <Form form={addForm} layout="vertical" size="small" onFinish={handleAddDoctor}>
<Form.Item name="phone" label="手机? rules={[{ required: true, message: '请输入手机号' }]}> <Form.Item name="phone" label="手机 rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机? /> <Input placeholder="手机 />
</Form.Item> </Form.Item>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓 }]}>
<Input placeholder="医生姓名" /> <Input placeholder="医生姓名" />
</Form.Item> </Form.Item>
<Form.Item name="gender" label="性别"> <Form.Item name="gender" label="性别">
...@@ -399,17 +399,17 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -399,17 +399,17 @@ const AdminDoctorsPage: React.FC = () => {
<Form.Item name="password" label="初始密码"> <Form.Item name="password" label="初始密码">
<Input.Password placeholder="默认 123456" /> <Input.Password placeholder="默认 123456" />
</Form.Item> </Form.Item>
<Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证? }]}> <Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证 }]}>
<Input placeholder="执业证号" /> <Input placeholder="执业证号" />
</Form.Item> </Form.Item>
<Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}> <Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}>
<Input placeholder="如:主治医师" /> <Input placeholder="如:主治医师" />
</Form.Item> </Form.Item>
<Form.Item name="hospital" label="所属医? rules={[{ required: true, message: '请输入医? }]}> <Form.Item name="hospital" label="所属医院 rules={[{ required: true, message: '请输入医院 }]}>
<Input placeholder="所属医? /> <Input placeholder="所属医 />
</Form.Item> </Form.Item>
<Form.Item name="introduction" label="简?> <Form.Item name="introduction" label="简>
<Input.TextArea rows={3} placeholder="医生简? /> <Input.TextArea rows={3} placeholder="医生简 />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Space> <Space>
...@@ -433,16 +433,16 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -433,16 +433,16 @@ const AdminDoctorsPage: React.FC = () => {
<div> <div>
<Descriptions column={2} bordered> <Descriptions column={2} bordered>
<Descriptions.Item label="姓名">{currentDoctor.name}</Descriptions.Item> <Descriptions.Item label="姓名">{currentDoctor.name}</Descriptions.Item>
<Descriptions.Item label="手机?>{currentDoctor.phone}</Descriptions.Item> <Descriptions.Item label="手机>{currentDoctor.phone}</Descriptions.Item>
<Descriptions.Item label="执业证号">{currentDoctor.license_no}</Descriptions.Item> <Descriptions.Item label="执业证号">{currentDoctor.license_no}</Descriptions.Item>
<Descriptions.Item label="职称">{currentDoctor.title}</Descriptions.Item> <Descriptions.Item label="职称">{currentDoctor.title}</Descriptions.Item>
<Descriptions.Item label="所属医? span={2}>{currentDoctor.hospital}</Descriptions.Item> <Descriptions.Item label="所属医 span={2}>{currentDoctor.hospital}</Descriptions.Item>
<Descriptions.Item label="科室" span={2}>{currentDoctor.department_name}</Descriptions.Item> <Descriptions.Item label="科室" span={2}>{currentDoctor.department_name}</Descriptions.Item>
</Descriptions> </Descriptions>
{(currentDoctor.license_image || currentDoctor.qualification_image) && ( {(currentDoctor.license_image || currentDoctor.qualification_image) && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<Text strong>证照材料?/Text> <Text strong>证照材料</Text>
<div style={{ marginTop: 8 }}> <div style={{ marginTop: 8 }}>
<Space> <Space>
{currentDoctor.license_image && ( {currentDoctor.license_image && (
...@@ -507,20 +507,20 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -507,20 +507,20 @@ const AdminDoctorsPage: React.FC = () => {
width={600} width={600}
> >
<Form form={editForm} layout="vertical" onFinish={handleEditSubmit}> <Form form={editForm} layout="vertical" onFinish={handleEditSubmit}>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓 }]}>
<Input placeholder="医生姓名" /> <Input placeholder="医生姓名" />
</Form.Item> </Form.Item>
<Form.Item name="phone" label="手机? rules={[{ required: true, message: '请输入手机号' }]}> <Form.Item name="phone" label="手机 rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机? /> <Input placeholder="手机 />
</Form.Item> </Form.Item>
<Form.Item name="title" label="职称"> <Form.Item name="title" label="职称">
<Input placeholder="如:主治医师" /> <Input placeholder="如:主治医师" />
</Form.Item> </Form.Item>
<Form.Item name="hospital" label="所属医?> <Form.Item name="hospital" label="所属医>
<Input placeholder="所属医? /> <Input placeholder="所属医 />
</Form.Item> </Form.Item>
<Form.Item name="introduction" label="简?> <Form.Item name="introduction" label="简>
<Input.TextArea rows={3} placeholder="医生简? /> <Input.TextArea rows={3} placeholder="医生简 />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Space> <Space>
......
...@@ -30,7 +30,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -30,7 +30,7 @@ const AdminPatientsPage: React.FC = () => {
setPatients(res.data.list || []); setPatients(res.data.list || []);
setTotal(res.data.total || 0); setTotal(res.data.total || 0);
} catch (error) { } catch (error) {
message.error('获取患者列表失?); message.error('获取患者列表失);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -64,7 +64,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -64,7 +64,7 @@ const AdminPatientsPage: React.FC = () => {
const handleResetPassword = (record: UserInfo) => { const handleResetPassword = (record: UserInfo) => {
Modal.confirm({ Modal.confirm({
title: '确认重置密码?, title: '确认重置密码,
content: `将重置患?${record.real_name || record.phone} 的密码为默认密码`, content: `将重置患?${record.real_name || record.phone} 的密码为默认密码`,
onOk: async () => { onOk: async () => {
try { try {
...@@ -126,7 +126,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -126,7 +126,7 @@ const AdminPatientsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('患者添加成?); message.success('患者添加成);
} else if (editingRecord) { } else if (editingRecord) {
await adminApi.updatePatient(editingRecord.id, { await adminApi.updatePatient(editingRecord.id, {
real_name: values.real_name, real_name: values.real_name,
...@@ -134,7 +134,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -134,7 +134,7 @@ const AdminPatientsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('患者信息更新成?); message.success('患者信息更新成);
} }
setModalVisible(false); setModalVisible(false);
modalForm.resetFields(); modalForm.resetFields();
...@@ -150,13 +150,13 @@ const AdminPatientsPage: React.FC = () => { ...@@ -150,13 +150,13 @@ const AdminPatientsPage: React.FC = () => {
const columns: ColumnsType<UserInfo> = [ const columns: ColumnsType<UserInfo> = [
{ {
title: '?, title: ',
key: 'patient', key: 'patient',
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
<Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#1890ff' }} /> <Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#1890ff' }} />
<div> <div>
<Text strong>{record.real_name || '未填?}</Text> <Text strong>{record.real_name || '未填}</Text>
<br /> <br />
<Text type="secondary" style={{ fontSize: 12 }}>{record.phone}</Text> <Text type="secondary" style={{ fontSize: 12 }}>{record.phone}</Text>
</div> </div>
...@@ -182,16 +182,16 @@ const AdminPatientsPage: React.FC = () => { ...@@ -182,16 +182,16 @@ const AdminPatientsPage: React.FC = () => {
dataIndex: 'is_verified', dataIndex: 'is_verified',
key: 'is_verified', key: 'is_verified',
render: (verified: boolean) => ( render: (verified: boolean) => (
<Tag color={verified ? 'green' : 'default'}>{verified ? '已认? : '未认?}</Tag> <Tag color={verified ? 'green' : 'default'}>{verified ? '已认证 : '未认证}</Tag>
), ),
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -232,15 +232,15 @@ const AdminPatientsPage: React.FC = () => { ...@@ -232,15 +232,15 @@ const AdminPatientsPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">患者管?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">患者管</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
placeholder="搜索姓名/手机? placeholder="搜索姓名/手机
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
value={keyword} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
...@@ -249,7 +249,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -249,7 +249,7 @@ const AdminPatientsPage: React.FC = () => {
size="small" size="small"
/> />
<Select <Select
placeholder="状? placeholder="状
allowClear allowClear
size="small" size="small"
value={statusFilter || undefined} value={statusFilter || undefined}
...@@ -257,13 +257,13 @@ const AdminPatientsPage: React.FC = () => { ...@@ -257,13 +257,13 @@ const AdminPatientsPage: React.FC = () => {
style={{ width: 100 }} style={{ width: 100 }}
options={[ options={[
{ label: '正常', value: 'active' }, { label: '正常', value: 'active' },
{ label: '已禁?, value: 'disabled' }, { label: '已禁, value: 'disabled' },
]} ]}
/> />
<Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button> <Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
<Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setStatusFilter(''); setPage(1); fetchPatients(); }}>重置</Button> <Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setStatusFilter(''); setPage(1); fetchPatients(); }}>重置</Button>
</div> </div>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加患?/Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加患</Button>
</div> </div>
</Card> </Card>
...@@ -286,7 +286,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -286,7 +286,7 @@ const AdminPatientsPage: React.FC = () => {
</Card> </Card>
<Modal <Modal
title={modalType === 'add' ? '添加患? : '编辑患?} title={modalType === 'add' ? '添加患者 : '编辑患}
open={modalVisible} open={modalVisible}
onOk={handleModalSubmit} onOk={handleModalSubmit}
onCancel={() => setModalVisible(false)} onCancel={() => setModalVisible(false)}
...@@ -295,11 +295,11 @@ const AdminPatientsPage: React.FC = () => { ...@@ -295,11 +295,11 @@ const AdminPatientsPage: React.FC = () => {
width={440} width={440}
> >
<Form form={modalForm} layout="vertical" size="small"> <Form form={modalForm} layout="vertical" size="small">
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓 }]}>
<Input placeholder="患者姓? /> <Input placeholder="患者姓 />
</Form.Item> </Form.Item>
<Form.Item name="phone" label="手机? rules={[{ required: true, message: '请输入手机号' }]}> <Form.Item name="phone" label="手机 rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机? /> <Input placeholder="手机 />
</Form.Item> </Form.Item>
{modalType === 'add' && ( {modalType === 'add' && (
<Form.Item name="password" label="初始密码"> <Form.Item name="password" label="初始密码">
......
This diff is collapsed.
...@@ -76,7 +76,7 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -76,7 +76,7 @@ const AdminPrescriptionPage: React.FC = () => {
if (!currentRecord) return; if (!currentRecord) return;
try { try {
await prescriptionAdminApi.review(currentRecord.id, { action: 'reject', reason: rejectReason }); await prescriptionAdminApi.review(currentRecord.id, { action: 'reject', reason: rejectReason });
message.success('处方已驳?); message.success('处方已驳);
setRejectModalVisible(false); setRejectModalVisible(false);
setDetailVisible(false); setDetailVisible(false);
setRejectReason(''); setRejectReason('');
...@@ -95,7 +95,7 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -95,7 +95,7 @@ const AdminPrescriptionPage: React.FC = () => {
const columns = [ const columns = [
{ title: '处方编号', dataIndex: 'prescription_no', key: 'prescription_no', width: 160 }, { title: '处方编号', dataIndex: 'prescription_no', key: 'prescription_no', width: 160 },
{ title: '?, dataIndex: 'patient_name', key: 'patient_name', width: 80 }, { title: ', dataIndex: 'patient_name', key: 'patient_name', width: 80 },
{ title: '医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 80 }, { title: '医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 80 },
{ title: '诊断', dataIndex: 'diagnosis', key: 'diagnosis', width: 140, ellipsis: true }, { title: '诊断', dataIndex: 'diagnosis', key: 'diagnosis', width: 140, ellipsis: true },
{ {
...@@ -108,9 +108,9 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -108,9 +108,9 @@ const AdminPrescriptionPage: React.FC = () => {
), ),
}, },
{ title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100, render: (v: number) => ${(v / 100).toFixed(2)}` }, { title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100, render: (v: number) => ${(v / 100).toFixed(2)}` },
{ title: '?, dataIndex: 'warning_level', key: 'warning_level', width: 100, render: (v: string) => getWarningTag(v) }, { title: ', dataIndex: 'warning_level', key: 'warning_level', width: 100, render: (v: string) => getWarningTag(v) },
{ {
title: '开方时?, dataIndex: 'created_at', key: 'created_at', width: 160, title: '开方时, dataIndex: 'created_at', key: 'created_at', width: 160,
render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-', render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-',
}, },
{ {
...@@ -207,12 +207,12 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -207,12 +207,12 @@ const AdminPrescriptionPage: React.FC = () => {
<div> <div>
<Descriptions bordered size="small" column={2}> <Descriptions bordered size="small" column={2}>
<Descriptions.Item label="处方编号">{currentRecord.prescription_no}</Descriptions.Item> <Descriptions.Item label="处方编号">{currentRecord.prescription_no}</Descriptions.Item>
<Descriptions.Item label="状?>{getWarningTag(currentRecord.warning_level)}</Descriptions.Item> <Descriptions.Item label="状>{getWarningTag(currentRecord.warning_level)}</Descriptions.Item>
<Descriptions.Item label="患?>{currentRecord.patient_name}</Descriptions.Item> <Descriptions.Item label="患>{currentRecord.patient_name}</Descriptions.Item>
<Descriptions.Item label="医生">{currentRecord.doctor_name}</Descriptions.Item> <Descriptions.Item label="医生">{currentRecord.doctor_name}</Descriptions.Item>
<Descriptions.Item label="诊断" span={2}>{currentRecord.diagnosis}</Descriptions.Item> <Descriptions.Item label="诊断" span={2}>{currentRecord.diagnosis}</Descriptions.Item>
<Descriptions.Item label="金额">¥{(currentRecord.total_amount / 100).toFixed(2)}</Descriptions.Item> <Descriptions.Item label="金额">¥{(currentRecord.total_amount / 100).toFixed(2)}</Descriptions.Item>
<Descriptions.Item label="开方时?>{new Date(currentRecord.created_at).toLocaleString('zh-CN')}</Descriptions.Item> <Descriptions.Item label="开方时>{new Date(currentRecord.created_at).toLocaleString('zh-CN')}</Descriptions.Item>
</Descriptions> </Descriptions>
<Card title={<><MedicineBoxOutlined /> 药品明细</>} size="small" style={{ marginTop: 16, borderRadius: 8 }}> <Card title={<><MedicineBoxOutlined /> 药品明细</>} size="small" style={{ marginTop: 16, borderRadius: 8 }}>
{(currentRecord.items || []).map((item: PrescriptionItem, idx: number) => ( {(currentRecord.items || []).map((item: PrescriptionItem, idx: number) => (
...@@ -236,7 +236,7 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -236,7 +236,7 @@ const AdminPrescriptionPage: React.FC = () => {
<Modal title="驳回处方" open={rejectModalVisible} onCancel={() => { setRejectModalVisible(false); setRejectReason(''); }} <Modal title="驳回处方" open={rejectModalVisible} onCancel={() => { setRejectModalVisible(false); setRejectReason(''); }}
onOk={handleReject} okText="确认驳回" okButtonProps={{ danger: true }}> onOk={handleReject} okText="确认驳回" okButtonProps={{ danger: true }}>
<Input.TextArea rows={3} placeholder="请输入驳回原?.." value={rejectReason} onChange={(e) => setRejectReason(e.target.value)} /> <Input.TextArea rows={3} placeholder="请输入驳回原.." value={rejectReason} onChange={(e) => setRejectReason(e.target.value)} />
</Modal> </Modal>
</div> </div>
); );
......
...@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => { ...@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => {
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 12.5%</span>} /></Card> suffix={<span className="text-xs text-green-500"><RiseOutlined /> 12.5%</span>} /></Card>
</Col> </Col>
<Col xs={12} sm={6}> <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> suffix={<span className="text-xs text-green-500"><RiseOutlined /> 8.3%</span>} /></Card>
</Col> </Col>
<Col xs={12} sm={6}> <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> suffix={<span className="text-xs text-green-500"><RiseOutlined /> 15.2%</span>} /></Card>
</Col> </Col>
<Col xs={12} sm={6}> <Col xs={12} sm={6}>
......
...@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo { ...@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo {
} }
const roleMap: Record<string, { text: string; color: string }> = { const roleMap: Record<string, { text: string; color: string }> = {
patient: { text: '?, color: 'blue' }, patient: { text: ', color: 'blue' },
doctor: { text: '医生', color: 'green' }, doctor: { text: '医生', color: 'green' },
admin: { text: '管理?, color: 'purple' }, admin: { text: '管理, color: 'purple' },
}; };
const AdminUsersPage: React.FC = () => { const AdminUsersPage: React.FC = () => {
...@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => { ...@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => {
onOk: async () => { onOk: async () => {
try { try {
await adminApi.resetUserPassword(userId); await adminApi.resetUserPassword(userId);
message.success('密码已重?); message.success('密码已重);
} catch (error) { } catch (error) {
message.error('重置密码失败'); message.error('重置密码失败');
} }
...@@ -115,15 +115,15 @@ const AdminUsersPage: React.FC = () => { ...@@ -115,15 +115,15 @@ const AdminUsersPage: React.FC = () => {
title: '实名认证', title: '实名认证',
dataIndex: 'is_verified', dataIndex: 'is_verified',
key: 'is_verified', key: 'is_verified',
render: (v: boolean) => v ? <Tag color="success">已认?/Tag> : <Tag color="warning">未认?/Tag>, render: (v: boolean) => v ? <Tag color="success">已认证</Tag> : <Tag color="warning">未认证</Tag>,
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => { ...@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center gap-2"> <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" /> onChange={(e) => setKeyword(e.target.value)} style={{ width: 180 }} size="small" />
<Select placeholder="角色" style={{ width: 100 }} size="small" allowClear value={roleFilter} <Select placeholder="角色" style={{ width: 100 }} size="small" allowClear value={roleFilter}
onChange={(v) => setRoleFilter(v)} options={[ 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 <Select placeholder="状 style={{ width: 100 }} size="small" allowClear
options={[{ label: '正常', value: 'active' }, { label: '已禁?, value: 'disabled' }]} /> options={[{ label: '正常', value: 'active' }, { label: '已禁, value: 'disabled' }]} />
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button> <Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
</div> </div>
</Card> </Card>
......
This diff is collapsed.
...@@ -94,10 +94,10 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -94,10 +94,10 @@ const DoctorCertificationPage: React.FC = () => {
<div className="w-14 h-14 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4"> <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" /> <CheckCircleOutlined className="text-2xl text-green-500" />
</div> </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!"> <Paragraph type="secondary" className="text-xs!">
您的资质认证申请已成功提交,管理员将?1-3 个工作日内完成审核? </Paragraph> 您的资质认证申请已成功提交,管理员将?1-3 个工作日内完成审核 </Paragraph>
<Button type="primary" size="small" onClick={() => router.push('/doctor/workbench')}>返回工作?/Button> <Button type="primary" size="small" onClick={() => router.push('/doctor/workbench')}>返回工作</Button>
</div> </div>
</Card> </Card>
</div> </div>
...@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => {
<Card size="small"> <Card size="small">
<Form form={form} layout="vertical" size="small" onFinish={handleSubmit}> <Form form={form} layout="vertical" size="small" onFinish={handleSubmit}>
<Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证? }]}> <Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证 }]}>
<Input placeholder="请输入医师执业证? /> <Input placeholder="请输入医师执业证 />
</Form.Item> </Form.Item>
<Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}> <Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}>
<Select placeholder="请选择职称"> <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.Option value="主治医师">主治医师</Select.Option>
<Select.Option value="住院医师">住院医师</Select.Option> <Select.Option value="住院医师">住院医师</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item name="hospital" label="所属医? rules={[{ required: true, message: '请输入所属医? }]}> <Form.Item name="hospital" label="所属医院 rules={[{ required: true, message: '请输入所属医院 }]}>
<Input placeholder="请输入您所在的医院全称" /> <Input placeholder="请输入您所在的医院全称" />
</Form.Item> </Form.Item>
<Form.Item name="department_name" label="科室" rules={[{ required: true, message: '请选择科室' }]}> <Form.Item name="department_name" label="科室" rules={[{ required: true, message: '请选择科室' }]}>
...@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => {
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item label="执业证照? required> <Form.Item label="执业证照 required>
<Upload <Upload
listType="picture-card" listType="picture-card"
fileList={licenseFileList} fileList={licenseFileList}
...@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => {
maxCount={1} maxCount={1}
> >
{licenseFileList.length === 0 && ( {licenseFileList.length === 0 && (
<div><UploadOutlined /><div className="mt-1 text-xs">上传执业?/div></div> <div><UploadOutlined /><div className="mt-1 text-xs">上传执业</div></div>
)} )}
</Upload> </Upload>
<Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text> <Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text>
</Form.Item> </Form.Item>
<Form.Item label="资格证照? required> <Form.Item label="资格证照 required>
<Upload <Upload
listType="picture-card" listType="picture-card"
fileList={qualificationFileList} fileList={qualificationFileList}
...@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => {
maxCount={1} maxCount={1}
> >
{qualificationFileList.length === 0 && ( {qualificationFileList.length === 0 && (
<div><UploadOutlined /><div className="mt-1 text-xs">上传资格?/div></div> <div><UploadOutlined /><div className="mt-1 text-xs">上传资格</div></div>
)} )}
</Upload> </Upload>
<Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text> <Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text>
......
...@@ -41,8 +41,8 @@ const mockData: ChronicPrescription[] = [ ...@@ -41,8 +41,8 @@ const mockData: ChronicPrescription[] = [
last_prescription_date: '2026-01-25', last_prescription_date: '2026-01-25',
ai_draft: { ai_draft: {
drugs: [ drugs: [
{ name: '二甲双胍缓释?, spec: '0.5g/?, dosage: '每次1?, frequency: '每日2?, days: 30 }, { name: '二甲双胍缓释, spec: '0.5g/?, dosage: '每次1?, frequency: '每日2?, days: 30 },
{ name: '格列美脲?, spec: '2mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 }, { name: '格列美脲, spec: '2mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
], ],
note: '患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%', note: '患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%',
}, },
...@@ -54,14 +54,14 @@ const mockData: ChronicPrescription[] = [ ...@@ -54,14 +54,14 @@ const mockData: ChronicPrescription[] = [
patient_name: '李四', patient_name: '李四',
patient_age: 65, patient_age: 65,
patient_gender: '?, patient_gender: '?,
disease: '高血?, disease: '高血,
last_prescription_date: '2026-01-20', last_prescription_date: '2026-01-20',
ai_draft: { ai_draft: {
drugs: [ drugs: [
{ name: '氨氯地平?, spec: '5mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 }, { name: '氨氯地平, spec: '5mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
{ name: '缬沙坦胶?, spec: '80mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 }, { name: '缬沙坦胶, spec: '80mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
], ],
note: '患者血压控制稳定,建议继续联合用药方案。近期血? 130/85mmHg', note: '患者血压控制稳定,建议继续联合用药方案。近期血 130/85mmHg',
}, },
status: 'pending', status: 'pending',
submitted_at: '2026-02-25 10:15:00', submitted_at: '2026-02-25 10:15:00',
...@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => {
const handleApprove = (id: string) => { const handleApprove = (id: string) => {
setData(data.map((d) => (d.id === id ? { ...d, status: 'approved' as const } : d))); setData(data.map((d) => (d.id === id ? { ...d, status: 'approved' as const } : d)));
message.success('续方已审核通过并签名发?); message.success('续方已审核通过并签名发);
setDetailModalVisible(false); setDetailModalVisible(false);
}; };
...@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => { ...@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => {
Modal.confirm({ Modal.confirm({
title: '拒绝续方申请', title: '拒绝续方申请',
content: ( content: (
<TextArea placeholder="请输入拒绝理?.." rows={3} /> <TextArea placeholder="请输入拒绝理.." rows={3} />
), ),
onOk: () => { onOk: () => {
setData(data.map((d) => (d.id === id ? { ...d, status: 'rejected' as const } : d))); setData(data.map((d) => (d.id === id ? { ...d, status: 'rejected' as const } : d)));
message.info('续方申请已拒?); message.info('续方申请已拒);
setDetailModalVisible(false); setDetailModalVisible(false);
}, },
}); });
...@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => { ...@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
const map: Record<string, { color: string; text: string }> = { const map: Record<string, { color: string; text: string }> = {
pending: { color: 'orange', text: '待审? }, pending: { color: 'orange', text: '待审 },
approved: { color: 'green', text: '已通过' }, approved: { color: 'green', text: '已通过' },
rejected: { color: 'red', text: '已拒? }, rejected: { color: 'red', text: '已拒 },
modified: { color: 'blue', text: '已修? }, modified: { color: 'blue', text: '已修 },
}; };
const s = map[status] || { color: 'default', text: status }; const s = map[status] || { color: 'default', text: status };
return <Tag color={s.color}>{s.text}</Tag>; return <Tag color={s.color}>{s.text}</Tag>;
}; };
const columns = [ const columns = [
{ title: '患者姓?, dataIndex: 'patient_name', key: 'patient_name', width: 100 }, { title: '患者姓, dataIndex: 'patient_name', key: 'patient_name', width: 100 },
{ {
title: '基本信息', title: '基本信息',
key: 'info', key: 'info',
...@@ -119,7 +119,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -119,7 +119,7 @@ const ChronicReviewPage: React.FC = () => {
render: (_: any, r: ChronicPrescription) => `${r.patient_gender} / ${r.patient_age}岁`, render: (_: any, r: ChronicPrescription) => `${r.patient_gender} / ${r.patient_age}岁`,
}, },
{ title: '慢病诊断', dataIndex: 'disease', key: 'disease', width: 120 }, { 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: '药品数量', title: '药品数量',
key: 'drug_count', key: 'drug_count',
...@@ -127,7 +127,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -127,7 +127,7 @@ const ChronicReviewPage: React.FC = () => {
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', dataIndex: 'status',
key: 'status', key: 'status',
width: 100, width: 100,
...@@ -164,7 +164,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -164,7 +164,7 @@ const ChronicReviewPage: React.FC = () => {
<Tabs defaultActiveKey="pending" size="small" items={[ <Tabs defaultActiveKey="pending" size="small" items={[
{ {
key: 'pending', key: 'pending',
label: <Badge count={pendingCount} offset={[10, 0]}>待审?/Badge>, label: <Badge count={pendingCount} offset={[10, 0]}>待审</Badge>,
children: ( children: (
<Card size="small"> <Card size="small">
<Table columns={columns} dataSource={data.filter((d) => d.status === 'pending')} <Table columns={columns} dataSource={data.filter((d) => d.status === 'pending')}
...@@ -195,7 +195,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -195,7 +195,7 @@ const ChronicReviewPage: React.FC = () => {
<CloseCircleOutlined /> 拒绝 <CloseCircleOutlined /> 拒绝
</Button>, </Button>,
<Button key="modify" icon={<EditOutlined />} onClick={() => message.info('修改功能开发中')}> <Button key="modify" icon={<EditOutlined />} onClick={() => message.info('修改功能开发中')}>
修改后签? </Button>, 修改后签 </Button>,
<Button key="approve" type="primary" style={{ background: '#52c41a', borderColor: '#52c41a' }} onClick={() => handleApprove(currentRecord.id)}> <Button key="approve" type="primary" style={{ background: '#52c41a', borderColor: '#52c41a' }} onClick={() => handleApprove(currentRecord.id)}>
<SafetyCertificateOutlined /> 签名发布 <SafetyCertificateOutlined /> 签名发布
</Button>, </Button>,
...@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => { ...@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => {
{currentRecord && ( {currentRecord && (
<div> <div>
<Descriptions bordered size="small" column={2} style={{ marginBottom: 16 }}> <Descriptions bordered size="small" column={2} style={{ marginBottom: 16 }}>
<Descriptions.Item label="患者姓?>{currentRecord.patient_name}</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.patient_gender} / {currentRecord.patient_age}?/Descriptions.Item>
<Descriptions.Item label="慢病诊断">{currentRecord.disease}</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> </Descriptions>
<Card title="AI 续方草?" size="small" style={{ marginBottom: 16, borderColor: '#52c41a' }}> <Card title="AI 续方草" size="small" style={{ marginBottom: 16, borderColor: '#52c41a' }}>
<Table <Table
size="small" size="small"
pagination={false} pagination={false}
...@@ -225,7 +225,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -225,7 +225,7 @@ const ChronicReviewPage: React.FC = () => {
]} ]}
/> />
<div style={{ marginTop: 12, padding: 8, background: '#f6ffed', borderRadius: 6 }}> <div style={{ marginTop: 12, padding: 8, background: '#f6ffed', borderRadius: 6 }}>
<Text type="secondary">AI 备注?/Text> <Text type="secondary">AI 备注</Text>
<Text>{currentRecord.ai_draft.note}</Text> <Text>{currentRecord.ai_draft.note}</Text>
</div> </div>
</Card> </Card>
......
...@@ -40,7 +40,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -40,7 +40,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
const res = await consultApi.aiAssist(activeConsultId, scene); const res = await consultApi.aiAssist(activeConsultId, scene);
setContent(res.data?.content || '暂无分析结果'); setContent(res.data?.content || '暂无分析结果');
} catch (err: any) { } catch (err: any) {
setContent('AI分析失败: ' + (err?.message || '请稍后重?)); setContent('AI分析失败: ' + (err?.message || '请稍后重));
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -63,7 +63,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -63,7 +63,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
{chatMsgs.map((msg, i) => ( {chatMsgs.map((msg, i) => (
<div key={i} style={{ fontSize: 12, marginTop: 6, paddingLeft: 8, borderLeft: msg.role === 'user' ? '2px solid #1890ff' : '2px solid #52c41a' }}> <div key={i} style={{ fontSize: 12, marginTop: 6, paddingLeft: 8, borderLeft: msg.role === 'user' ? '2px solid #1890ff' : '2px solid #52c41a' }}>
<Text strong style={{ fontSize: 12, color: msg.role === 'user' ? '#1890ff' : '#52c41a' }}> <Text strong style={{ fontSize: 12, color: msg.role === 'user' ? '#1890ff' : '#52c41a' }}>
{msg.role === 'user' ? '? : 'AI'}? </Text> {msg.role === 'user' ? ' : 'AI'}? </Text>
<div style={{ marginTop: 2, color: '#333' }}>{msg.content}</div> <div style={{ marginTop: 2, color: '#333' }}>{msg.content}</div>
</div> </div>
))} ))}
...@@ -71,14 +71,15 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -71,14 +71,15 @@ const AIPanel: React.FC<AIPanelProps> = ({
); );
}; };
// 渲染预问诊内容(使用?tab? const renderPreConsultContent = () => { //
const renderPreConsultContent = () => {
if (!preConsultReport) return null; if (!preConsultReport) return null;
const patientInfo = ( const patientInfo = (
<div style={{ padding: 8, background: '#f9f0ff', borderRadius: 6, marginBottom: 8 }}> <div style={{ padding: 8, background: '#f9f0ff', borderRadius: 6, marginBottom: 8 }}>
<Text strong style={{ fontSize: 13, color: '#722ed1' }}> <Text strong style={{ fontSize: 13, color: '#722ed1' }}>
<UserOutlined style={{ marginRight: 4 }} /> <UserOutlined style={{ marginRight: 4 }} />
{preConsultReport.patient_name || '?} {preConsultReport.patient_name || '}
</Text> </Text>
{(preConsultReport.patient_gender || preConsultReport.patient_age) && ( {(preConsultReport.patient_gender || preConsultReport.patient_age) && (
<Text type="secondary" style={{ fontSize: 12, marginLeft: 8 }}> <Text type="secondary" style={{ fontSize: 12, marginLeft: 8 }}>
...@@ -105,7 +106,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -105,7 +106,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
{tags} {tags}
{preConsultReport.chief_complaint && ( {preConsultReport.chief_complaint && (
<div style={{ fontSize: 12, marginBottom: 8 }}> <div style={{ fontSize: 12, marginBottom: 8 }}>
<Text strong>主诉?/Text>{preConsultReport.chief_complaint} <Text strong>主诉</Text>{preConsultReport.chief_complaint}
</div> </div>
)} )}
<Tabs <Tabs
...@@ -157,22 +158,22 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -157,22 +158,22 @@ const AIPanel: React.FC<AIPanelProps> = ({
label: ( label: (
<span> <span>
<FileTextOutlined /> <FileTextOutlined />
{' '}预问? {hasPreConsultData && <Badge dot offset={[4, -2]} />} {' '}预问 {hasPreConsultData && <Badge dot offset={[4, -2]} />}
</span> </span>
), ),
children: preConsultLoading ? ( children: preConsultLoading ? (
<div style={{ textAlign: 'center', padding: 20 }}><Spin tip="加载?.." /></div> <div style={{ textAlign: 'center', padding: 20 }}><Spin tip="加载.." /></div>
) : hasPreConsultData ? ( ) : hasPreConsultData ? (
renderPreConsultContent() renderPreConsultContent()
) : ( ) : (
<Alert message="该患者未进行AI预问? type="info" showIcon style={{ fontSize: 12 }} /> <Alert message="该患者未进行AI预问 type="info" showIcon style={{ fontSize: 12 }} />
), ),
}, },
{ {
key: 'diagnosis', key: 'diagnosis',
label: '鉴别诊断', label: '鉴别诊断',
children: diagnosisLoading ? ( children: diagnosisLoading ? (
<div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析?.." /></div> <div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析.." /></div>
) : diagnosisContent ? ( ) : diagnosisContent ? (
<div> <div>
<div style={{ maxHeight: 400, overflow: 'auto' }}> <div style={{ maxHeight: 400, overflow: 'auto' }}>
...@@ -186,7 +187,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -186,7 +187,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
) : ( ) : (
<div style={{ textAlign: 'center', padding: 16 }}> <div style={{ textAlign: 'center', padding: 16 }}>
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}> <Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}>
基于问诊对话内容,AI将辅助生成鉴别诊? </Text> 基于问诊对话内容,AI将辅助生成鉴别诊 </Text>
<Button type="primary" size="small" icon={<ThunderboltOutlined />} onClick={() => handleAIAssist('consult_diagnosis')}> <Button type="primary" size="small" icon={<ThunderboltOutlined />} onClick={() => handleAIAssist('consult_diagnosis')}>
生成鉴别诊断 生成鉴别诊断
</Button> </Button>
...@@ -197,7 +198,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -197,7 +198,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
key: 'drugs', key: 'drugs',
label: '用药建议', label: '用药建议',
children: medicationLoading ? ( children: medicationLoading ? (
<div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析?.." /></div> <div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析.." /></div>
) : medicationContent ? ( ) : medicationContent ? (
<div> <div>
<div style={{ maxHeight: 400, overflow: 'auto' }}> <div style={{ maxHeight: 400, overflow: 'auto' }}>
...@@ -211,7 +212,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -211,7 +212,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
) : ( ) : (
<div style={{ textAlign: 'center', padding: 16 }}> <div style={{ textAlign: 'center', padding: 16 }}>
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}> <Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}>
基于问诊对话内容,AI将辅助生成用药建? </Text> 基于问诊对话内容,AI将辅助生成用药建 </Text>
<Button type="primary" size="small" icon={<MedicineBoxOutlined />} onClick={() => handleAIAssist('consult_medication')}> <Button type="primary" size="small" icon={<MedicineBoxOutlined />} onClick={() => handleAIAssist('consult_medication')}>
生成用药建议 生成用药建议
</Button> </Button>
......
...@@ -23,7 +23,8 @@ interface ActiveConsult { ...@@ -23,7 +23,8 @@ interface ActiveConsult {
interface ChatPanelProps { interface ChatPanelProps {
activeConsult: ActiveConsult | null; activeConsult: ActiveConsult | null;
consultStatus?: string; // 当前选中问诊的状? messages: ConsultMessage[]; consultStatus?: string; //
messages: ConsultMessage[];
onSend: (content: string) => void; onSend: (content: string) => void;
onEndConsult: () => void; onEndConsult: () => void;
onToggleAI: () => void; onToggleAI: () => void;
...@@ -58,7 +59,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -58,7 +59,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
const handleEndConsult = () => { const handleEndConsult = () => {
Modal.confirm({ Modal.confirm({
title: '确认结束问诊', title: '确认结束问诊',
content: '结束后患者将无法继续发送消息,确认结束本次问诊?, content: '结束后患者将无法继续发送消息,确认结束本次问诊,
okText: '确认结束', okText: '确认结束',
okButtonProps: { danger: true }, okButtonProps: { danger: true },
cancelText: '取消', cancelText: '取消',
...@@ -87,7 +88,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -87,7 +88,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
<Tag color={activeConsult.type === 'video' ? 'blue' : 'green'}> <Tag color={activeConsult.type === 'video' ? 'blue' : 'green'}>
{activeConsult.type === 'video' ? '视频问诊' : '图文问诊'} {activeConsult.type === 'video' ? '视频问诊' : '图文问诊'}
</Tag> </Tag>
{isCompleted && <Tag color="default">已结?/Tag>} {isCompleted && <Tag color="default">已结束</Tag>}
</Space> </Space>
) : '问诊对话' ) : '问诊对话'
} }
...@@ -174,7 +175,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -174,7 +175,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
textAlign: 'center', textAlign: 'center',
background: '#fafafa', background: '#fafafa',
}}> }}>
<Text type="secondary" style={{ fontSize: 13 }}>本次问诊已结?/Text> <Text type="secondary" style={{ fontSize: 13 }}>本次问诊已结</Text>
</div> </div>
) : ( ) : (
<div style={{ padding: 12, borderTop: '1px solid #f0f0f0' }}> <div style={{ padding: 12, borderTop: '1px solid #f0f0f0' }}>
...@@ -199,14 +200,14 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -199,14 +200,14 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
loading={sending} loading={sending}
style={{ height: 'auto', borderRadius: '0 8px 8px 0', background: '#52c41a', borderColor: '#52c41a' }} style={{ height: 'auto', borderRadius: '0 8px 8px 0', background: '#52c41a', borderColor: '#52c41a' }}
> >
? </Button> </Button>
</Space.Compact> </Space.Compact>
</div> </div>
)} )}
</> </>
) : ( ) : (
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Empty description="请从左侧列表中选择患者接? /> <Empty description="请从左侧列表中选择患者接 />
</div> </div>
)} )}
</Card> </Card>
......
...@@ -14,17 +14,19 @@ interface PatientListProps { ...@@ -14,17 +14,19 @@ interface PatientListProps {
} }
const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, onAccept, onReject, selectedConsultId }) => { const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, onAccept, onReject, selectedConsultId }) => {
// 鐘舵€侀厤缃? const statusConfig: Record<string, { color: string; bgColor: string; text: string }> = { //
in_progress: { color: '#52c41a', bgColor: '#f6ffed', text: '杩涜涓? }, const statusConfig: Record<string, { color: string; bgColor: string; text: string }> = {
pending: { color: '#1890ff', bgColor: '#e6f7ff', text: '寰呮帴璇? }, in_progress: { color: '#52c41a', bgColor: '#f6ffed', text: '杩涜涓 },
waiting: { color: '#1890ff', bgColor: '#e6f7ff', text: '寰呮帴璇? }, pending: { color: '#1890ff', bgColor: '#e6f7ff', text: '待接诊 },
completed: { color: '#8c8c8c', bgColor: '#fafafa', text: '宸插畬鎴? }, waiting: { color: '#1890ff', bgColor: '#e6f7ff', text: '待接诊 },
completed: { color: '#8c8c8c', bgColor: '#fafafa', text: '已完成 },
}; };
// 鏍煎紡鍖栫瓑寰呮椂闂? const formatWaitingTime = (seconds: number) => { //
if (seconds < 60) return `${seconds}绉抈; const formatWaitingTime = (seconds: number) => {
if (seconds < 3600) return `${Math.floor(seconds / 60)}鍒嗛挓`; if (seconds < 60) return `${seconds}秒;
return `${Math.floor(seconds / 3600)}灏忔椂`; if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟`;
return `${Math.floor(seconds / 3600)}小时`;
}; };
return ( return (
...@@ -32,7 +34,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -32,7 +34,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
title={ title={
<span className="text-xs"> <span className="text-xs">
<UserOutlined className="text-[#1890ff] mr-1" /> <UserOutlined className="text-[#1890ff] mr-1" />
<Text strong className="text-xs!">鎮h€呭垪琛?/Text> <Text strong className="text-xs!">患者列表</Text>
<Badge count={patients.length} style={{ backgroundColor: '#52c41a' }} className="ml-1" /> <Badge count={patients.length} style={{ backgroundColor: '#52c41a' }} className="ml-1" />
</span> </span>
} }
...@@ -46,7 +48,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -46,7 +48,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
emptyText: ( emptyText: (
<div className="text-center py-8 text-gray-400"> <div className="text-center py-8 text-gray-400">
<UserOutlined className="text-3xl text-gray-300 mb-2 block" /> <UserOutlined className="text-3xl text-gray-300 mb-2 block" />
<div className="text-xs">鏆傛棤鎮h€?/div> <div className="text-xs">暂无患者/div>
</div> </div>
) )
}} }}
...@@ -99,23 +101,23 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -99,23 +101,23 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
<div className="text-[11px] text-gray-500 mb-1"> <div className="text-[11px] text-gray-500 mb-1">
<Text ellipsis className="text-[11px]! text-gray-500!"> <Text ellipsis className="text-[11px]! text-gray-500!">
涓昏瘔锛歿patient.chief_complaint || '鏆傛棤'} 主诉:{patient.chief_complaint || '暂无'}
</Text> </Text>
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-[10px] text-gray-400"> <span className="text-[10px] text-gray-400">
{patient.patient_gender} 路 {patient.patient_age}宀? </span> {patient.patient_gender} · {patient.patient_age}岁 </span>
{/* {patient.status === 'pending' || patient.status === 'waiting' ? ( {/* {patient.status === 'pending' || patient.status === 'waiting' ? (
<Space size={4} style={{ fontSize: 11, color: '#ff4d4f' }}> <Space size={4} style={{ fontSize: 11, color: '#ff4d4f' }}>
<ClockCircleOutlined /> <ClockCircleOutlined />
<span>绛夊緟 {formatWaitingTime(patient.waiting_seconds)}</span> <span>等待 {formatWaitingTime(patient.waiting_seconds)}</span>
</Space> </Space>
) : null} */} ) : null} */}
</div> </div>
{/* 寰呮帴璇婃偅鑰呮樉绀烘帴璇?鎷掔粷鎸夐挳 */} {/* 待接诊患者显示接诊/拒绝按钮 */}
{(patient.status === 'pending' || patient.status === 'waiting') && ( {(patient.status === 'pending' || patient.status === 'waiting') && (
<div className="mt-1.5 flex gap-1.5"> <div className="mt-1.5 flex gap-1.5">
<Button <Button
...@@ -125,17 +127,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -125,17 +127,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
console.log('鎺ヨ瘖鎸夐挳琚偣鍑?, patient); console.log('接诊鎸夐挳袋偣鍑, patient);
console.log('onAccept 鍑芥暟:', onAccept); console.log('onAccept 函数:', onAccept);
if (typeof onAccept === 'function') { if (typeof onAccept === 'function') {
onAccept(patient); onAccept(patient);
} else { } else {
console.error('onAccept 涓嶆槸涓€涓嚱鏁?'); console.error('onAccept 涓嶆槸涓€涓嚱鏁');
} }
}} }}
className="flex-1 text-[11px]!" className="flex-1 text-[11px]!"
> >
鎺ヨ瘖 接诊
</Button> </Button>
<Button <Button
danger danger
...@@ -144,17 +146,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -144,17 +146,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
console.log('鎷掔粷鎸夐挳琚偣鍑?, patient); console.log('拒绝鎸夐挳袋偣鍑, patient);
console.log('onReject 鍑芥暟:', onReject); console.log('onReject 函数:', onReject);
if (typeof onReject === 'function') { if (typeof onReject === 'function') {
onReject(patient); onReject(patient);
} else { } else {
console.error('onReject 涓嶆槸涓€涓嚱鏁?'); console.error('onReject 涓嶆槸涓€涓嚱鏁');
} }
}} }}
className="flex-1 text-[11px]!" className="flex-1 text-[11px]!"
> >
鎷掔粷 拒绝
</Button> </Button>
</div> </div>
)} )}
......
...@@ -18,10 +18,10 @@ interface WaitingQueueProps { ...@@ -18,10 +18,10 @@ interface WaitingQueueProps {
} }
const formatWaitingTime = (seconds: number) => { const formatWaitingTime = (seconds: number) => {
if (seconds < 60) return '鍒氬垰'; if (seconds < 60) return '刚刚';
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}鍒嗛挓`; if (minutes < 60) return `${minutes}分钟`;
return `${Math.floor(minutes / 60)}灏忔椂${minutes % 60}鍒哷; return `${Math.floor(minutes / 60)}小时${minutes % 60};
}; };
const WaitingQueue: React.FC<WaitingQueueProps> = ({ const WaitingQueue: React.FC<WaitingQueueProps> = ({
...@@ -47,9 +47,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -47,9 +47,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Space> <Space>
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} /> <Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} />
<Text strong>{patient.patient_name || '鎮h€?}</Text> <Text strong>{patient.patient_name || '患者}</Text>
<Tag color={patient.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}> <Tag color={patient.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}>
{patient.type === 'video' ? '瑙嗛' : '鍥炬枃'} {patient.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
</Space> </Space>
</div> </div>
...@@ -58,7 +58,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -58,7 +58,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
? patient.chief_complaint.length > 30 ? patient.chief_complaint.length > 30
? patient.chief_complaint.substring(0, 30) + '...' ? patient.chief_complaint.substring(0, 30) + '...'
: patient.chief_complaint : patient.chief_complaint
: '鏈~鍐欎富璇?} : '鏈~鍐欎富璇}
</Text> </Text>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Tag icon={<ClockCircleOutlined />} color="orange" style={{ fontSize: 11 }}> <Tag icon={<ClockCircleOutlined />} color="orange" style={{ fontSize: 11 }}>
...@@ -66,10 +66,10 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -66,10 +66,10 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
</Tag> </Tag>
<Space size={4}> <Space size={4}>
<Button size="small" type="primary" onClick={() => onAccept(patient)}> <Button size="small" type="primary" onClick={() => onAccept(patient)}>
鎺ヨ瘖 接诊
</Button> </Button>
<Button size="small" danger onClick={() => onReject(patient)}> <Button size="small" danger onClick={() => onReject(patient)}>
鎷掕瘖 拒诊
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -92,9 +92,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -92,9 +92,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Space> <Space>
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#1890ff' }} /> <Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#1890ff' }} />
<Text strong>{consult.patient_name || '鎮h€?}</Text> <Text strong>{consult.patient_name || '患者}</Text>
<Tag color={consult.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}> <Tag color={consult.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}>
{consult.type === 'video' ? '瑙嗛' : '鍥炬枃'} {consult.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
</Space> </Space>
</div> </div>
...@@ -103,7 +103,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -103,7 +103,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
? consult.chief_complaint.length > 30 ? consult.chief_complaint.length > 30
? consult.chief_complaint.substring(0, 30) + '...' ? consult.chief_complaint.substring(0, 30) + '...'
: consult.chief_complaint : consult.chief_complaint
: '鏈~鍐欎富璇?} : '鏈~鍐欎富璇}
</Text> </Text>
</Card> </Card>
); );
...@@ -113,12 +113,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -113,12 +113,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
key: 'waiting', key: 'waiting',
label: ( label: (
<Space> <Space>
<span>寰呮帴璇?/span> <span>待接诊/span>
<Badge count={waitingList.length} style={{ backgroundColor: '#fa8c16' }} /> <Badge count={waitingList.length} style={{ backgroundColor: '#fa8c16' }} />
</Space> </Space>
), ),
children: waitingList.length === 0 ? ( children: waitingList.length === 0 ? (
<Empty description="鏆傛棤绛夊緟鎮h€? style={{ marginTop: 40 }} /> <Empty description="暂无等待患者 style={{ marginTop: 40 }} />
) : ( ) : (
<div>{waitingList.map(renderWaitingItem)}</div> <div>{waitingList.map(renderWaitingItem)}</div>
), ),
...@@ -128,12 +128,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -128,12 +128,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
label: ( label: (
<Space> <Space>
<MessageOutlined /> <MessageOutlined />
<span>杩涜涓?/span> <span>杩涜涓</span>
<Badge count={inProgressList.length} style={{ backgroundColor: '#52c41a' }} /> <Badge count={inProgressList.length} style={{ backgroundColor: '#52c41a' }} />
</Space> </Space>
), ),
children: inProgressList.length === 0 ? ( children: inProgressList.length === 0 ? (
<Empty description="鏆傛棤杩涜涓棶璇? style={{ marginTop: 40 }} /> <Empty description="暂无杩涜涓棶璇 style={{ marginTop: 40 }} />
) : ( ) : (
<div>{inProgressList.map(renderInProgressItem)}</div> <div>{inProgressList.map(renderInProgressItem)}</div>
), ),
...@@ -143,12 +143,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -143,12 +143,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
label: ( label: (
<Space> <Space>
<CheckCircleOutlined /> <CheckCircleOutlined />
<span>宸插畬鎴?/span> <span>已完成/span>
<Badge count={completedList.length} style={{ backgroundColor: '#999' }} /> <Badge count={completedList.length} style={{ backgroundColor: '#999' }} />
</Space> </Space>
), ),
children: completedList.length === 0 ? ( children: completedList.length === 0 ? (
<Empty description="鏆傛棤宸插畬鎴愰棶璇? style={{ marginTop: 40 }} /> <Empty description="暂无已完成愰棶璇 style={{ marginTop: 40 }} />
) : ( ) : (
<div>{completedList.map((consult) => ( <div>{completedList.map((consult) => (
<Card <Card
...@@ -167,8 +167,8 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -167,8 +167,8 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Space> <Space>
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#999' }} /> <Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#999' }} />
<Text strong>{consult.patient_name || '鎮h€?}</Text> <Text strong>{consult.patient_name || '患者}</Text>
<Tag color="default" style={{ fontSize: 11 }}>宸插畬鎴?/Tag> <Tag color="default" style={{ fontSize: 11 }}>已完成/Tag>
</Space> </Space>
</div> </div>
<Text type="secondary" style={{ fontSize: 12, display: 'block' }}> <Text type="secondary" style={{ fontSize: 12, display: 'block' }}>
...@@ -176,7 +176,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -176,7 +176,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
? consult.chief_complaint.length > 30 ? consult.chief_complaint.length > 30
? consult.chief_complaint.substring(0, 30) + '...' ? consult.chief_complaint.substring(0, 30) + '...'
: consult.chief_complaint : consult.chief_complaint
: '鏈~鍐欎富璇?} : '鏈~鍐欎富璇}
</Text> </Text>
</Card> </Card>
))}</div> ))}</div>
......
...@@ -30,7 +30,8 @@ const ConsultPage: React.FC = () => { ...@@ -30,7 +30,8 @@ const ConsultPage: React.FC = () => {
const [preConsultLoading, setPreConsultLoading] = useState(false); const [preConsultLoading, setPreConsultLoading] = useState(false);
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
// 获取统一患者列? const fetchPatientList = useCallback(async () => { //
const fetchPatientList = useCallback(async () => {
try { try {
const res = await consultApi.getPatientList(); const res = await consultApi.getPatientList();
setPatientList(res.data || []); setPatientList(res.data || []);
...@@ -49,7 +50,8 @@ const ConsultPage: React.FC = () => { ...@@ -49,7 +50,8 @@ const ConsultPage: React.FC = () => {
} }
}, []); }, []);
// 定时刷新患者列? useEffect(() => { //
useEffect(() => {
fetchPatientList(); fetchPatientList();
const timer = setInterval(() => { const timer = setInterval(() => {
fetchPatientList(); fetchPatientList();
...@@ -90,7 +92,7 @@ const ConsultPage: React.FC = () => { ...@@ -90,7 +92,7 @@ const ConsultPage: React.FC = () => {
// 接诊 // 接诊
const handleAccept = async (patient: PatientListItem) => { const handleAccept = async (patient: PatientListItem) => {
console.log('handleAccept 被调?, patient); console.log('handleAccept 被调, patient);
try { try {
console.log('调用 acceptConsult API', patient.consult_id); console.log('调用 acceptConsult API', patient.consult_id);
await consultApi.acceptConsult(patient.consult_id); await consultApi.acceptConsult(patient.consult_id);
...@@ -98,7 +100,7 @@ const ConsultPage: React.FC = () => { ...@@ -98,7 +100,7 @@ const ConsultPage: React.FC = () => {
setActiveConsult({ setActiveConsult({
consult_id: patient.consult_id, consult_id: patient.consult_id,
patient_id: patient.patient_id, patient_id: patient.patient_id,
patient_name: patient.patient_name || '?, patient_name: patient.patient_name || ',
patient_gender: patient.patient_gender, patient_gender: patient.patient_gender,
patient_age: patient.patient_age, patient_age: patient.patient_age,
chief_complaint: patient.chief_complaint, chief_complaint: patient.chief_complaint,
...@@ -106,7 +108,7 @@ const ConsultPage: React.FC = () => { ...@@ -106,7 +108,7 @@ const ConsultPage: React.FC = () => {
status: 'in_progress', status: 'in_progress',
}); });
setPreConsultReport(null); setPreConsultReport(null);
message.success(`已接诊患?${patient.patient_name || '?}`); message.success(`已接诊患?${patient.patient_name || '患}`);
fetchPreConsultReport(patient.consult_id, patient.patient_id); fetchPreConsultReport(patient.consult_id, patient.patient_id);
fetchPatientList(); fetchPatientList();
fetchMessages(patient.consult_id); fetchMessages(patient.consult_id);
...@@ -121,7 +123,7 @@ const ConsultPage: React.FC = () => { ...@@ -121,7 +123,7 @@ const ConsultPage: React.FC = () => {
setActiveConsult({ setActiveConsult({
consult_id: patient.consult_id, consult_id: patient.consult_id,
patient_id: patient.patient_id, patient_id: patient.patient_id,
patient_name: patient.patient_name || '?, patient_name: patient.patient_name || ',
patient_gender: patient.patient_gender, patient_gender: patient.patient_gender,
patient_age: patient.patient_age, patient_age: patient.patient_age,
chief_complaint: patient.chief_complaint, chief_complaint: patient.chief_complaint,
...@@ -135,16 +137,16 @@ const ConsultPage: React.FC = () => { ...@@ -135,16 +137,16 @@ const ConsultPage: React.FC = () => {
// 拒诊 // 拒诊
const handleReject = (patient: PatientListItem) => { const handleReject = (patient: PatientListItem) => {
console.log('handleReject 被调?, patient); console.log('handleReject 被调, patient);
Modal.confirm({ Modal.confirm({
title: '确认拒诊', title: '确认拒诊',
content: `确定拒绝接诊患?${patient.patient_name || '?}?`, content: `确定拒绝接诊患?${patient.patient_name || '患}?`,
onOk: async () => { onOk: async () => {
try { try {
console.log('调用 rejectConsult API', patient.consult_id); console.log('调用 rejectConsult API', patient.consult_id);
await consultApi.rejectConsult(patient.consult_id); await consultApi.rejectConsult(patient.consult_id);
console.log('rejectConsult API 调用成功'); console.log('rejectConsult API 调用成功');
message.info('已拒?); message.info('已拒);
fetchPatientList(); fetchPatientList();
} catch (err: any) { } catch (err: any) {
console.error('拒诊失败', err); console.error('拒诊失败', err);
...@@ -155,14 +157,15 @@ const ConsultPage: React.FC = () => { ...@@ -155,14 +157,15 @@ const ConsultPage: React.FC = () => {
}; };
// 发送消? const handleSendMessage = async (content: string) => { //
const handleSendMessage = async (content: string) => {
if (!activeConsult) return; if (!activeConsult) return;
setSending(true); setSending(true);
try { try {
await consultApi.sendMessage(activeConsult.consult_id, content); await consultApi.sendMessage(activeConsult.consult_id, content);
fetchMessages(activeConsult.consult_id); fetchMessages(activeConsult.consult_id);
} catch { } catch {
message.error('发送失?); message.error('发送失);
} finally { } finally {
setSending(false); setSending(false);
} }
...@@ -174,7 +177,7 @@ const ConsultPage: React.FC = () => { ...@@ -174,7 +177,7 @@ const ConsultPage: React.FC = () => {
try { try {
await consultApi.endConsult(activeConsult.consult_id); await consultApi.endConsult(activeConsult.consult_id);
message.success('问诊已结?); message.success('问诊已结);
setActiveConsult(null); setActiveConsult(null);
setMessages([]); setMessages([]);
setPreConsultReport(null); setPreConsultReport(null);
......
...@@ -22,27 +22,27 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -22,27 +22,27 @@ const DoctorConsultRoomPage: React.FC = () => {
patient_avatar: '', patient_avatar: '',
type: 'text', type: 'text',
status: 'in_progress', status: 'in_progress',
chief_complaint: '头痛、乏力,持续3天?, chief_complaint: '头痛、乏力,持续3天,
medical_history: '高血压病?年,长期服用降压药?, medical_history: '高血压病?年,长期服用降压药,
}; };
const messages = [ const messages = [
{ {
id: '1', id: '1',
sender_type: 'system', sender_type: 'system',
content: '问诊已开?, content: '问诊已开,
created_at: '2026-02-25T13:00:00Z', created_at: '2026-02-25T13:00:00Z',
}, },
{ {
id: '2', id: '2',
sender_type: 'patient', sender_type: 'patient',
content: '医生您好,我最近三天一直头痛,感觉很乏力,精神也不好?, content: '医生您好,我最近三天一直头痛,感觉很乏力,精神也不好,
created_at: '2026-02-25T13:01:00Z', created_at: '2026-02-25T13:01:00Z',
}, },
{ {
id: '3', id: '3',
sender_type: 'patient', sender_type: 'patient',
content: '我有高血压,一直在吃药控制?, content: '我有高血压,一直在吃药控制,
created_at: '2026-02-25T13:01:30Z', created_at: '2026-02-25T13:01:30Z',
}, },
]; ];
...@@ -53,7 +53,7 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -53,7 +53,7 @@ const DoctorConsultRoomPage: React.FC = () => {
gender: '?, gender: '?,
phone: '138****8888', phone: '138****8888',
medical_history: '高血压病??, medical_history: '高血压病??,
allergy_history: '青霉素过?, allergy_history: '青霉素过,
consultation_count: 3, consultation_count: 3,
}; };
...@@ -80,7 +80,7 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -80,7 +80,7 @@ const DoctorConsultRoomPage: React.FC = () => {
<Tag className="ml-1" color={currentConsult.type === 'video' ? 'blue' : 'green'}> <Tag className="ml-1" color={currentConsult.type === 'video' ? 'blue' : 'green'}>
{currentConsult.type === 'video' ? '视频' : '图文'} {currentConsult.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
<Tag color="processing">问诊?/Tag> <Tag color="processing">问诊</Tag>
</span> </span>
} }
extra={ extra={
...@@ -122,14 +122,14 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -122,14 +122,14 @@ const DoctorConsultRoomPage: React.FC = () => {
<TextArea value={inputValue} onChange={(e) => setInputValue(e.target.value)} <TextArea value={inputValue} onChange={(e) => setInputValue(e.target.value)}
placeholder="输入回复..." autoSize={{ minRows: 1, maxRows: 3 }} size="small" placeholder="输入回复..." autoSize={{ minRows: 1, maxRows: 3 }} size="small"
onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} /> onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} />
<Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend}>?/Button> <Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend}></Button>
</Space.Compact> </Space.Compact>
</div> </div>
</Card> </Card>
</Col> </Col>
<Col span={8} style={{ height: '100%' }}> <Col span={8} style={{ height: '100%' }}>
<Card title={<span className="text-xs font-semibold">患者信?/span>} size="small" <Card title={<span className="text-xs font-semibold">患者信</span>} size="small"
style={{ height: '100%', overflow: 'auto' }}> style={{ height: '100%', overflow: 'auto' }}>
<div className="text-center mb-2"> <div className="text-center mb-2">
<Avatar size={48} icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} /> <Avatar size={48} icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} />
......
...@@ -19,9 +19,9 @@ interface ConsultRecord { ...@@ -19,9 +19,9 @@ interface ConsultRecord {
} }
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
completed: { text: '已完?, color: 'green' }, completed: { text: '已完, color: 'green' },
cancelled: { text: '已取?, color: 'red' }, cancelled: { text: '已取, color: 'red' },
in_progress: { text: '进行?, color: 'blue' }, in_progress: { text: '进行, color: 'blue' },
}; };
const DoctorHistoryPage: React.FC = () => { const DoctorHistoryPage: React.FC = () => {
...@@ -35,7 +35,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -35,7 +35,7 @@ const DoctorHistoryPage: React.FC = () => {
patient_avatar: '', patient_avatar: '',
type: 'text', type: 'text',
status: 'completed', status: 'completed',
chief_complaint: '头痛、乏?, chief_complaint: '头痛、乏,
created_at: '2026-02-24T10:00:00Z', created_at: '2026-02-24T10:00:00Z',
ended_at: '2026-02-24T10:30:00Z', ended_at: '2026-02-24T10:30:00Z',
}, },
...@@ -55,7 +55,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -55,7 +55,7 @@ const DoctorHistoryPage: React.FC = () => {
patient_avatar: '', patient_avatar: '',
type: 'text', type: 'text',
status: 'cancelled', status: 'cancelled',
chief_complaint: '复诊开?, chief_complaint: '复诊开,
created_at: '2026-02-23T09:00:00Z', created_at: '2026-02-23T09:00:00Z',
ended_at: null, ended_at: null,
}, },
...@@ -63,7 +63,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -63,7 +63,7 @@ const DoctorHistoryPage: React.FC = () => {
const columns: ColumnsType<ConsultRecord> = [ const columns: ColumnsType<ConsultRecord> = [
{ {
title: '?, title: ',
dataIndex: 'patient_name', dataIndex: 'patient_name',
key: 'patient_name', key: 'patient_name',
render: (name: string, record) => ( render: (name: string, record) => (
...@@ -93,7 +93,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -93,7 +93,7 @@ const DoctorHistoryPage: React.FC = () => {
ellipsis: true, ellipsis: true,
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => { render: (status: string) => {
...@@ -123,7 +123,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -123,7 +123,7 @@ const DoctorHistoryPage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
placeholder="搜索患者姓? placeholder="搜索患者姓
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
value={keyword} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
...@@ -132,8 +132,8 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -132,8 +132,8 @@ const DoctorHistoryPage: React.FC = () => {
/> />
<Select placeholder="类型" style={{ width: 100 }} size="small" allowClear <Select placeholder="类型" style={{ width: 100 }} size="small" allowClear
options={[{ label: '图文', value: 'text' }, { label: '视频', value: 'video' }]} /> options={[{ label: '图文', value: 'text' }, { label: '视频', value: 'video' }]} />
<Select placeholder="状? style={{ width: 100 }} size="small" allowClear <Select placeholder="状 style={{ width: 100 }} size="small" allowClear
options={[{ label: '已完?, value: 'completed' }, { label: '已取?, value: 'cancelled' }]} /> options={[{ label: '已完, value: 'completed' }, { label: '已取消, value: 'cancelled' }]} />
<RangePicker size="small" /> <RangePicker size="small" />
</div> </div>
</Card> </Card>
......
...@@ -61,7 +61,7 @@ const IncomePage: React.FC = () => { ...@@ -61,7 +61,7 @@ const IncomePage: React.FC = () => {
const incomeColumns = [ const incomeColumns = [
{ title: '时间', dataIndex: 'date', key: 'date', width: 160 }, { title: '时间', dataIndex: 'date', key: 'date', width: 160 },
{ title: '?, dataIndex: 'patient_name', key: 'patient_name', width: 100 }, { title: ', dataIndex: 'patient_name', key: 'patient_name', width: 100 },
{ {
title: '类型', title: '类型',
dataIndex: 'type', dataIndex: 'type',
...@@ -80,13 +80,13 @@ const IncomePage: React.FC = () => { ...@@ -80,13 +80,13 @@ const IncomePage: React.FC = () => {
render: (v: number) => <Text strong style={{ color: '#52c41a' }}>¥{(v / 100).toFixed(2)}</Text>, render: (v: number) => <Text strong style={{ color: '#52c41a' }}>¥{(v / 100).toFixed(2)}</Text>,
}, },
{ {
title: '?, title: ',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 80, width: 80,
render: (v: string) => ( render: (v: string) => (
<Tag color={v === 'settled' ? 'green' : 'orange'}> <Tag color={v === 'settled' ? 'green' : 'orange'}>
{v === 'settled' ? '已结? : '待结?} {v === 'settled' ? '已结算 : '待结}
</Tag> </Tag>
), ),
}, },
...@@ -103,7 +103,7 @@ const IncomePage: React.FC = () => { ...@@ -103,7 +103,7 @@ const IncomePage: React.FC = () => {
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
<Col span={8}> <Col span={8}>
<div className="rounded-lg p-3 text-white" style={{ background: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)' }}> <div className="rounded-lg p-3 text-white" style={{ background: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)' }}>
<div className="text-xs text-white/80 mb-1">可提现余?/div> <div className="text-xs text-white/80 mb-1">可提现余</div>
<div className="text-2xl font-bold">¥{(totalBalance / 100).toFixed(2)}</div> <div className="text-2xl font-bold">¥{(totalBalance / 100).toFixed(2)}</div>
<Button ghost size="small" className="mt-2 text-white! border-white/60!" icon={<WalletOutlined />} <Button ghost size="small" className="mt-2 text-white! border-white/60!" icon={<WalletOutlined />}
onClick={() => setWithdrawModalVisible(true)}>申请提现</Button> onClick={() => setWithdrawModalVisible(true)}>申请提现</Button>
...@@ -114,13 +114,13 @@ const IncomePage: React.FC = () => { ...@@ -114,13 +114,13 @@ const IncomePage: React.FC = () => {
<div className="text-xs text-gray-500 mb-1">本月收入</div> <div className="text-xs text-gray-500 mb-1">本月收入</div>
<div className="text-xl font-bold text-green-500">¥{(monthIncome / 100).toFixed(2)}</div> <div className="text-xl font-bold text-green-500">¥{(monthIncome / 100).toFixed(2)}</div>
<div className="mt-1 text-[11px] text-gray-400"> <div className="mt-1 text-[11px] text-gray-400">
<RiseOutlined className="text-green-500 mr-0.5" />较上?<span className="text-green-500">+12.5%</span> <RiseOutlined className="text-green-500 mr-0.5" />较上<span className="text-green-500">+12.5%</span>
</div> </div>
</div> </div>
</Col> </Col>
<Col span={8}> <Col span={8}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa]"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa]">
<div className="text-xs text-gray-500 mb-1">本月问诊?/div> <div className="text-xs text-gray-500 mb-1">本月问诊</div>
<div className="text-xl font-bold text-[#1890ff]">{monthConsults} <span className="text-xs font-normal text-gray-400">?/span></div> <div className="text-xl font-bold text-[#1890ff]">{monthConsults} <span className="text-xs font-normal text-gray-400">?/span></div>
<div className="mt-1 text-[11px] text-gray-400">日均 {(monthConsults / 25).toFixed(1)} ?/div> <div className="mt-1 text-[11px] text-gray-400">日均 {(monthConsults / 25).toFixed(1)} ?/div>
</div> </div>
...@@ -165,7 +165,7 @@ const IncomePage: React.FC = () => { ...@@ -165,7 +165,7 @@ const IncomePage: React.FC = () => {
</Col> </Col>
<Col span={5}> <Col span={5}>
<Statistic <Statistic
title="总收? title="总收
value={bill.total_income / 100} value={bill.total_income / 100}
precision={2} precision={2}
prefix="¥" prefix="¥"
...@@ -173,7 +173,7 @@ const IncomePage: React.FC = () => { ...@@ -173,7 +173,7 @@ const IncomePage: React.FC = () => {
/> />
</Col> </Col>
<Col span={3}> <Col span={3}>
<Statistic title="问诊? value={bill.consult_count} suffix="? valueStyle={{ fontSize: 16 }} /> <Statistic title="问诊 value={bill.consult_count} suffix="? valueStyle={{ fontSize: 16 }} />
</Col> </Col>
<Col span={12}> <Col span={12}>
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
...@@ -220,7 +220,7 @@ const IncomePage: React.FC = () => { ...@@ -220,7 +220,7 @@ const IncomePage: React.FC = () => {
{ title: '申请日期', dataIndex: 'date', key: 'date' }, { title: '申请日期', dataIndex: 'date', key: 'date' },
{ title: '金额', dataIndex: 'amount', key: 'amount', render: (v: number) => `¥${(v / 100).toFixed(2)}` }, { title: '金额', dataIndex: 'amount', key: 'amount', render: (v: number) => `¥${(v / 100).toFixed(2)}` },
{ title: '提现账户', dataIndex: 'bank', key: 'bank' }, { title: '提现账户', dataIndex: 'bank', key: 'bank' },
{ title: '?, dataIndex: 'status', key: 'status', render: () => <Tag color="green">已到?/Tag> }, { title: '态, dataIndex: 'status', key: 'status', render: () => <Tag color="green">已到</Tag> },
]} ]}
/> />
</Card> </Card>
...@@ -237,20 +237,20 @@ const IncomePage: React.FC = () => { ...@@ -237,20 +237,20 @@ const IncomePage: React.FC = () => {
okText="确认提现" okText="确认提现"
> >
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="可提现金?> <Form.Item label="可提现金>
<Text strong style={{ fontSize: 24, color: '#52c41a' }}>¥{(totalBalance / 100).toFixed(2)}</Text> <Text strong style={{ fontSize: 24, color: '#52c41a' }}>¥{(totalBalance / 100).toFixed(2)}</Text>
</Form.Item> </Form.Item>
<Form.Item label="提现金额" required> <Form.Item label="提现金额" required>
<InputNumber <InputNumber
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder="请输入提现金? placeholder="请输入提现金
min={1} min={1}
max={totalBalance / 100} max={totalBalance / 100}
precision={2} precision={2}
prefix="¥" prefix="¥"
/> />
</Form.Item> </Form.Item>
<Form.Item label="到账银行?> <Form.Item label="到账银行>
<Input value="招商银行 ****5678" disabled /> <Input value="招商银行 ****5678" disabled />
</Form.Item> </Form.Item>
</Form> </Form>
......
...@@ -30,10 +30,10 @@ const consultStatusColor: Record<string, string> = { ...@@ -30,10 +30,10 @@ const consultStatusColor: Record<string, string> = {
}; };
const consultStatusLabel: Record<string, string> = { const consultStatusLabel: Record<string, string> = {
completed: '已完?, completed: '已完,
in_progress: '进行?, in_progress: '进行,
pending: '待接?, pending: '待接,
cancelled: '已取?, cancelled: '已取,
}; };
const prescriptionStatusColor: Record<string, string> = { const prescriptionStatusColor: Record<string, string> = {
...@@ -46,12 +46,12 @@ const prescriptionStatusColor: Record<string, string> = { ...@@ -46,12 +46,12 @@ const prescriptionStatusColor: Record<string, string> = {
}; };
const prescriptionStatusLabel: Record<string, string> = { const prescriptionStatusLabel: Record<string, string> = {
completed: '已完?, completed: '已完,
dispensed: '已取?, dispensed: '已取,
approved: '已审?, approved: '已审,
signed: '已签?, signed: '已签,
pending: '待审?, pending: '待审,
rejected: '已拒?, rejected: '已拒,
}; };
const PatientRecordPage: React.FC = () => { const PatientRecordPage: React.FC = () => {
...@@ -70,7 +70,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -70,7 +70,7 @@ const PatientRecordPage: React.FC = () => {
setPatients(res.data.list || []); setPatients(res.data.list || []);
setTotal(res.data.total || 0); setTotal(res.data.total || 0);
} catch { } catch {
message.error('获取患者列表失?); message.error('获取患者列表失);
} finally { } finally {
setListLoading(false); setListLoading(false);
} }
...@@ -93,7 +93,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -93,7 +93,7 @@ const PatientRecordPage: React.FC = () => {
const res = await doctorPortalApi.getPatientDetail(patientId); const res = await doctorPortalApi.getPatientDetail(patientId);
setSelectedPatient(res.data); setSelectedPatient(res.data);
} catch { } catch {
message.error('获取患者详情失?); message.error('获取患者详情失);
} finally { } finally {
setDetailLoading(false); setDetailLoading(false);
} }
...@@ -107,7 +107,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -107,7 +107,7 @@ const PatientRecordPage: React.FC = () => {
}, },
{ title: '主诉', dataIndex: 'chief_complaint', key: 'chief_complaint', ellipsis: true }, { title: '主诉', dataIndex: 'chief_complaint', key: 'chief_complaint', ellipsis: true },
{ {
title: '?, dataIndex: 'status', key: 'status', width: 80, title: ', dataIndex: 'status', key: 'status', width: 80,
render: (v: string) => <Tag color={consultStatusColor[v] ?? 'default'}>{consultStatusLabel[v] ?? v}</Tag>, render: (v: string) => <Tag color={consultStatusColor[v] ?? 'default'}>{consultStatusLabel[v] ?? v}</Tag>,
}, },
]; ];
...@@ -126,7 +126,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -126,7 +126,7 @@ const PatientRecordPage: React.FC = () => {
), ),
}, },
{ {
title: '?, dataIndex: 'status', key: 'status', width: 80, title: ', dataIndex: 'status', key: 'status', width: 80,
render: (v: string) => <Tag color={prescriptionStatusColor[v] ?? 'default'}>{prescriptionStatusLabel[v] ?? v}</Tag>, render: (v: string) => <Tag color={prescriptionStatusColor[v] ?? 'default'}>{prescriptionStatusLabel[v] ?? v}</Tag>,
}, },
]; ];
...@@ -134,11 +134,11 @@ const PatientRecordPage: React.FC = () => { ...@@ -134,11 +134,11 @@ const PatientRecordPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-bold text-gray-800 m-0"> <h4 className="text-sm font-bold text-gray-800 m-0">
<UserOutlined className="mr-1" />患者档? </h4> <UserOutlined className="mr-1" />患者档 </h4>
<Row gutter={8}> <Row gutter={8}>
<Col span={8}> <Col span={8}>
<Card title={<span className="text-xs font-semibold">患者列?/span>} size="small" <Card title={<span className="text-xs font-semibold">患者列</span>} size="small"
extra={<Text type="secondary" className="text-[11px]!">{total} ?/Text>}> extra={<Text type="secondary" className="text-[11px]!">{total} ?/Text>}>
<Input prefix={<SearchOutlined />} placeholder="搜索姓名/手机" size="small" <Input prefix={<SearchOutlined />} placeholder="搜索姓名/手机" size="small"
value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)} value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}
...@@ -147,7 +147,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -147,7 +147,7 @@ const PatientRecordPage: React.FC = () => {
className="mb-2" /> className="mb-2" />
<Spin spinning={listLoading}> <Spin spinning={listLoading}>
<List size="small" dataSource={patients} <List size="small" dataSource={patients}
locale={{ emptyText: <Empty description="暂无患? /> }} locale={{ emptyText: <Empty description="暂无患 /> }}
pagination={total > 20 ? { current: page, pageSize: 20, total, size: 'small', pagination={total > 20 ? { current: page, pageSize: 20, total, size: 'small',
onChange: (p) => { setPage(p); fetchPatients(searchKeyword, p); } } : false} onChange: (p) => { setPage(p); fetchPatients(searchKeyword, p); } } : false}
renderItem={(patient) => ( renderItem={(patient) => (
...@@ -187,9 +187,9 @@ const PatientRecordPage: React.FC = () => { ...@@ -187,9 +187,9 @@ const PatientRecordPage: React.FC = () => {
<Descriptions.Item label="性别">{selectedPatient.gender === 'male' ? '? : selectedPatient.gender === 'female' ? '? : selectedPatient.gender}</Descriptions.Item> <Descriptions.Item label="性别">{selectedPatient.gender === 'male' ? '? : selectedPatient.gender === 'female' ? '? : selectedPatient.gender}</Descriptions.Item>
<Descriptions.Item label="年龄">{selectedPatient.age}?/Descriptions.Item> <Descriptions.Item label="年龄">{selectedPatient.age}?/Descriptions.Item>
<Descriptions.Item label="手机">{selectedPatient.phone}</Descriptions.Item> <Descriptions.Item label="手机">{selectedPatient.phone}</Descriptions.Item>
<Descriptions.Item label="紧急联?>{selectedPatient.emergency_contact || <Text type="secondary">未填</Text>}</Descriptions.Item> <Descriptions.Item label="紧急联>{selectedPatient.emergency_contact || <Text type="secondary">未填</Text>}</Descriptions.Item>
<Descriptions.Item label="医保">{selectedPatient.insurance_type || <Text type="secondary">未填</Text>}</Descriptions.Item> <Descriptions.Item label="医保">{selectedPatient.insurance_type || <Text type="secondary">未填</Text>}</Descriptions.Item>
<Descriptions.Item label="过敏? span={3}> <Descriptions.Item label="过敏 span={3}>
{selectedPatient.allergy_history ? <Tag color={selectedPatient.allergy_history === '? ? 'green' : 'red'}>{selectedPatient.allergy_history}</Tag> : <Tag color="green">?/Tag>} {selectedPatient.allergy_history ? <Tag color={selectedPatient.allergy_history === '? ? 'green' : 'red'}>{selectedPatient.allergy_history}</Tag> : <Tag color="green">?/Tag>}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="病史" span={3}>{selectedPatient.medical_history || <Text type="secondary">?/Text>}</Descriptions.Item> <Descriptions.Item label="病史" span={3}>{selectedPatient.medical_history || <Text type="secondary">?/Text>}</Descriptions.Item>
...@@ -224,7 +224,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -224,7 +224,7 @@ const PatientRecordPage: React.FC = () => {
</div> </div>
) : ( ) : (
<Card size="small" className="text-center py-10"> <Card size="small" className="text-center py-10">
<Empty description="请从左侧选择患者查看档? /> <Empty description="请从左侧选择患者查看档 />
</Card> </Card>
)} )}
</Spin> </Spin>
......
This diff is collapsed.
...@@ -20,8 +20,8 @@ const DoctorProfilePage: React.FC = () => { ...@@ -20,8 +20,8 @@ const DoctorProfilePage: React.FC = () => {
department_name: '内科', department_name: '内科',
hospital: '北京协和医院', hospital: '北京协和医院',
license_no: 'XXXXX', license_no: 'XXXXX',
introduction: '从事内科临床工作20余年,擅长高血压、糖尿病等慢性病的诊治?, introduction: '从事内科临床工作20余年,擅长高血压、糖尿病等慢性病的诊治,
specialties: ['高血?, '糖尿?, '冠心?, '心律失常'], specialties: ['高血, '糖尿, '冠心, '心律失常'],
price: 5000, price: 5000,
is_online: false, is_online: false,
ai_assist_enabled: true, ai_assist_enabled: true,
...@@ -43,7 +43,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -43,7 +43,7 @@ const DoctorProfilePage: React.FC = () => {
</div> </div>
<div className="text-xs text-gray-500 mb-1">{doctorProfile.hospital} · {doctorProfile.department_name}</div> <div className="text-xs text-gray-500 mb-1">{doctorProfile.hospital} · {doctorProfile.department_name}</div>
<Space size={4}> <Space size={4}>
<Tag icon={<SafetyCertificateOutlined />} color="success">已认?/Tag> <Tag icon={<SafetyCertificateOutlined />} color="success">已认</Tag>
<Tag>证号:{doctorProfile.license_no}</Tag> <Tag>证号:{doctorProfile.license_no}</Tag>
</Space> </Space>
</Col> </Col>
...@@ -72,7 +72,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -72,7 +72,7 @@ const DoctorProfilePage: React.FC = () => {
</Space> </Space>
</Card> </Card>
<Card title={<span className="text-xs font-semibold">个人简?/span>} size="small"> <Card title={<span className="text-xs font-semibold">个人简</span>} size="small">
<Text className="text-xs!">{doctorProfile.introduction}</Text> <Text className="text-xs!">{doctorProfile.introduction}</Text>
</Card> </Card>
...@@ -81,7 +81,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -81,7 +81,7 @@ const DoctorProfilePage: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<Text strong className="text-xs!">AI 辅助接诊</Text> <Text strong className="text-xs!">AI 辅助接诊</Text>
<div className="text-[11px] text-gray-400">开启后 AI 将协助分析患者症?/div> <div className="text-[11px] text-gray-400">开启后 AI 将协助分析患者症</div>
</div> </div>
<Switch size="small" checked={doctorProfile.ai_assist_enabled} /> <Switch size="small" checked={doctorProfile.ai_assist_enabled} />
</div> </div>
...@@ -89,7 +89,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -89,7 +89,7 @@ const DoctorProfilePage: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<Text strong className="text-xs!">自动接诊</Text> <Text strong className="text-xs!">自动接诊</Text>
<div className="text-[11px] text-gray-400">开启后图文问诊将自动接?/div> <div className="text-[11px] text-gray-400">开启后图文问诊将自动接</div>
</div> </div>
<Switch size="small" /> <Switch size="small" />
</div> </div>
......
...@@ -22,7 +22,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -22,7 +22,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c1', consult_id: 'c1',
patient_name: '张三', patient_name: '张三',
patient_avatar: '', patient_avatar: '',
chief_complaint: '头痛、乏力,持续3天。既往有高血压病史?, chief_complaint: '头痛、乏力,持续3天。既往有高血压病史,
type: 'text' as const, type: 'text' as const,
waiting_time: 300, waiting_time: 300,
created_at: '2026-02-25T13:00:00Z', created_at: '2026-02-25T13:00:00Z',
...@@ -32,7 +32,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -32,7 +32,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c2', consult_id: 'c2',
patient_name: '李四', patient_name: '李四',
patient_avatar: '', patient_avatar: '',
chief_complaint: '咳嗽有痰,偶尔发热,已持续一周?, chief_complaint: '咳嗽有痰,偶尔发热,已持续一周,
type: 'video' as const, type: 'video' as const,
waiting_time: 180, waiting_time: 180,
created_at: '2026-02-25T13:05:00Z', created_at: '2026-02-25T13:05:00Z',
...@@ -42,7 +42,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -42,7 +42,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c3', consult_id: 'c3',
patient_name: '王五', patient_name: '王五',
patient_avatar: '', patient_avatar: '',
chief_complaint: '复诊开药,糖尿病用药续方?, chief_complaint: '复诊开药,糖尿病用药续方,
type: 'text' as const, type: 'text' as const,
waiting_time: 60, waiting_time: 60,
created_at: '2026-02-25T13:10:00Z', created_at: '2026-02-25T13:10:00Z',
...@@ -52,7 +52,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -52,7 +52,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c4', consult_id: 'c4',
patient_name: '赵六', patient_name: '赵六',
patient_avatar: '', patient_avatar: '',
chief_complaint: '皮肤瘙痒红肿,疑似过敏?, chief_complaint: '皮肤瘙痒红肿,疑似过敏,
type: 'video' as const, type: 'video' as const,
waiting_time: 30, waiting_time: 30,
created_at: '2026-02-25T13:12:00Z', created_at: '2026-02-25T13:12:00Z',
...@@ -79,7 +79,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -79,7 +79,7 @@ const DoctorQueuePage: React.FC = () => {
<Card size="small"> <Card size="small">
{waitingPatients.length === 0 ? ( {waitingPatients.length === 0 ? (
<Empty description="暂无等待患? /> <Empty description="暂无等待患 />
) : ( ) : (
<List <List
itemLayout="horizontal" itemLayout="horizontal"
......
...@@ -19,7 +19,7 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -19,7 +19,7 @@ const DoctorSchedulePage: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
// TODO: 浠嶢PI鑾峰彇鐪熷疄鏁版嵁 // TODO: 从API获取真实数据
const scheduleData: Record<string, ScheduleSlot[]> = { const scheduleData: Record<string, ScheduleSlot[]> = {
'2026-02-25': [ '2026-02-25': [
{ id: '1', date: '2026-02-25', start_time: '09:00', end_time: '12:00', max_count: 10, remaining: 5 }, { id: '1', date: '2026-02-25', start_time: '09:00', end_time: '12:00', max_count: 10, remaining: 5 },
...@@ -57,8 +57,8 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -57,8 +57,8 @@ const DoctorSchedulePage: React.FC = () => {
}; };
const handleSaveSchedule = async (values: any) => { const handleSaveSchedule = async (values: any) => {
// TODO: 璋冪敤API淇濆瓨鎺掔彮 // TODO: 调用API保存排班
console.log('淇濆瓨鎺掔彮:', { console.log('保存排班:', {
date: selectedDate.format('YYYY-MM-DD'), date: selectedDate.format('YYYY-MM-DD'),
start_time: values.time_range[0].format('HH:mm'), start_time: values.time_range[0].format('HH:mm'),
end_time: values.time_range[1].format('HH:mm'), end_time: values.time_range[1].format('HH:mm'),
...@@ -68,12 +68,12 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -68,12 +68,12 @@ const DoctorSchedulePage: React.FC = () => {
}; };
const handleDeleteSlot = (slotId: string) => { const handleDeleteSlot = (slotId: string) => {
// TODO: 璋冪敤API鍒犻櫎鎺掔彮 // TODO: 调用API删除排班
Modal.confirm({ Modal.confirm({
title: '纭鍒犻櫎', title: '确删除',
content: '纭畾瑕佸垹闄ゆ鎺掔彮鏃舵鍚楋紵', content: '确畾瑕佸垹闄ゆ排班无舵鍚楋紵',
onOk: () => { onOk: () => {
console.log('鍒犻櫎鎺掔彮:', slotId); console.log('删除排班:', slotId);
}, },
}); });
}; };
...@@ -81,8 +81,8 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -81,8 +81,8 @@ const DoctorSchedulePage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">鎺掔彮绠$悊</h4> <h4 className="text-sm font-bold text-gray-800 m-0">排班管理</h4>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAddSchedule}>娣诲姞鎺掔彮</Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAddSchedule}>添加排班</Button>
</div> </div>
<Row gutter={12}> <Row gutter={12}>
...@@ -99,15 +99,15 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -99,15 +99,15 @@ const DoctorSchedulePage: React.FC = () => {
<Col span={8}> <Col span={8}>
<Card <Card
title={<span className="text-xs font-semibold"><CalendarOutlined className="mr-1" />{selectedDate.format('MM鏈圖D鏃?)} 鎺掔彮</span>} title={<span className="text-xs font-semibold"><CalendarOutlined className="mr-1" />{selectedDate.format('MM月DD日)} 排班</span>}
extra={<Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule}>娣诲姞</Button>} extra={<Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule}>添加</Button>}
size="small" size="small"
> >
{todaySchedule.length === 0 ? ( {todaySchedule.length === 0 ? (
<div className="text-center py-6"> <div className="text-center py-6">
<Text type="secondary" className="text-xs!">褰撴棩鏆傛棤鎺掔彮</Text> <Text type="secondary" className="text-xs!">当日暂无排班</Text>
<br /> <br />
<Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule} className="mt-1">娣诲姞鎺掔彮</Button> <Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule} className="mt-1">添加排班</Button>
</div> </div>
) : ( ) : (
<List <List
...@@ -124,8 +124,8 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -124,8 +124,8 @@ const DoctorSchedulePage: React.FC = () => {
<Text strong className="text-xs!">{slot.start_time} - {slot.end_time}</Text> <Text strong className="text-xs!">{slot.start_time} - {slot.end_time}</Text>
</div> </div>
<Space size={4}> <Space size={4}>
<Tag color="blue">鏈€澶?{slot.max_count} 浜?/Tag> <Tag color="blue">最大 {slot.max_count}</Tag>
<Tag color={slot.remaining > 0 ? 'green' : 'red'}>鍓╀綑 {slot.remaining} 鍙?/Tag> <Tag color={slot.remaining > 0 ? 'green' : 'red'}>剩余 {slot.remaining}</Tag>
</Space> </Space>
</div> </div>
</List.Item> </List.Item>
...@@ -137,17 +137,17 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -137,17 +137,17 @@ const DoctorSchedulePage: React.FC = () => {
</Row> </Row>
<Modal <Modal
title={`娣诲姞鎺掔彮 - ${selectedDate.format('YYYY-MM-DD')}`} title={`添加排班 - ${selectedDate.format('YYYY-MM-DD')}`}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
onOk={() => form.submit()} onOk={() => form.submit()}
width={400} width={400}
> >
<Form form={form} onFinish={handleSaveSchedule} layout="vertical" size="small"> <Form form={form} onFinish={handleSaveSchedule} layout="vertical" size="small">
<Form.Item name="time_range" label="鏃堕棿娈? rules={[{ required: true, message: '璇烽€夋嫨鏃堕棿娈? }]}> <Form.Item name="time_range" label="时间段 rules={[{ required: true, message: '请选择时间段 }]}>
<TimePicker.RangePicker format="HH:mm" minuteStep={30} style={{ width: '100%' }} /> <TimePicker.RangePicker format="HH:mm" minuteStep={30} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
<Form.Item name="max_count" label="鏈€澶ф帴璇婃暟" rules={[{ required: true, message: '璇疯緭鍏ユ渶澶ф帴璇婃暟' }]} initialValue={10}> <Form.Item name="max_count" label="最大接诊数" rules={[{ required: true, message: '请输入最大接诊数' }]} initialValue={10}>
<InputNumber min={1} max={50} style={{ width: '100%' }} /> <InputNumber min={1} max={50} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Form> </Form>
......
'use client'; 'use client';
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Card, Row, Col, Statistic, Typography, List, Avatar, Tag, Button, Space, Badge, Alert } from 'antd'; import { Card, Row, Col, Statistic, Typography, List, Avatar, Tag, Button, Space, Badge, Alert } from 'antd';
...@@ -39,7 +39,7 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -39,7 +39,7 @@ const DoctorWorkbenchPage: React.FC = () => {
// 检查医生是否已认证 // 检查医生是否已认证
const isVerified = user?.is_verified; const isVerified = user?.is_verified;
// 获取工作台数? // 获取工作台数
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
try { try {
const [statsRes, patientsRes] = await Promise.all([ const [statsRes, patientsRes] = await Promise.all([
...@@ -47,13 +47,13 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -47,13 +47,13 @@ const DoctorWorkbenchPage: React.FC = () => {
consultApi.getPatientList(), consultApi.getPatientList(),
]); ]);
setStats(statsRes.data); setStats(statsRes.data);
// 只显示待接诊的患? // 只显示待接诊的患
const waiting = (patientsRes.data || []).filter( const waiting = (patientsRes.data || []).filter(
p => p.status === 'pending' || p.status === 'waiting' p => p.status === 'pending' || p.status === 'waiting'
); );
setWaitingPatients(waiting.slice(0, 5)); setWaitingPatients(waiting.slice(0, 5));
} catch (error) { } catch (error) {
console.error('获取工作台数据失?', error); console.error('获取工作台数据失', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -75,13 +75,13 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -75,13 +75,13 @@ const DoctorWorkbenchPage: React.FC = () => {
if (!isVerified) { if (!isVerified) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<h4 className="text-sm font-bold text-gray-800 m-0">医生工作?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">医生工作</h4>
<Alert <Alert
message="请先完成资质认证" message="请先完成资质认证"
description={ description={
<div className="text-xs"> <div className="text-xs">
<p className="mb-1">您的账号尚未完成医生资质认证,无法接诊患者?/p> <p className="mb-1">您的账号尚未完成医生资质认证,无法接诊患者</p>
<p className="mb-2">请提交您的执业证、资格证等相关资料,审核通过后即可开始接诊?/p> <p className="mb-2">请提交您的执业证、资格证等相关资料,审核通过后即可开始接诊</p>
<Button type="primary" size="small" icon={<ExclamationCircleOutlined />} <Button type="primary" size="small" icon={<ExclamationCircleOutlined />}
onClick={() => router.push('/doctor/certification')}> onClick={() => router.push('/doctor/certification')}>
立即认证 立即认证
...@@ -98,17 +98,17 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -98,17 +98,17 @@ const DoctorWorkbenchPage: React.FC = () => {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">医生工作?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">医生工作</h4>
<span className="text-xs text-gray-400">?0秒自动刷?/span> <span className="text-xs text-gray-400">每10秒自动刷新</span>
</div> </div>
{/* 核心指标 */} {/* 核心指标 */}
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{[ {[
{ title: '今日问诊', value: stats.today_consult_count, icon: <MessageOutlined />, color: '#1890ff', bg: '#e6f7ff', suffix: '人次' }, { title: '今日问诊', value: stats.today_consult_count, icon: <MessageOutlined />, color: '#1890ff', bg: '#e6f7ff', suffix: '人次' },
{ title: '等待接诊', value: stats.waiting_count, icon: <ClockCircleOutlined />, color: '#fa8c16', bg: '#fff7e6', suffix: '? }, { title: '等待接诊', value: stats.waiting_count, icon: <ClockCircleOutlined />, color: '#fa8c16', bg: '#fff7e6', suffix: '' },
{ title: '今日完成', value: stats.completed_today, icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', suffix: '? }, { title: '今日完成', value: stats.completed_today, icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', suffix: '' },
{ title: '今日收入', value: stats.income_today, icon: <DollarOutlined />, color: '#722ed1', bg: '#f9f0ff', suffix: '? }, { title: '今日收入', value: stats.income_today, icon: <DollarOutlined />, color: '#722ed1', bg: '#f9f0ff', suffix: '' },
].map((item, i) => ( ].map((item, i) => (
<Col xs={12} sm={6} key={i}> <Col xs={12} sm={6} key={i}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa] hover:shadow-md transition-shadow"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa] hover:shadow-md transition-shadow">
...@@ -127,10 +127,10 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -127,10 +127,10 @@ const DoctorWorkbenchPage: React.FC = () => {
{/* 综合数据 */} {/* 综合数据 */}
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{[ {[
{ icon: <TeamOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '累计患?, value: stats.total_patients }, { icon: <TeamOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '累计患', value: stats.total_patients },
{ icon: <StarOutlined />, color: '#fadb14', bg: '#fffbe6', label: '综合评分', value: stats.rating }, { icon: <StarOutlined />, color: '#fadb14', bg: '#fffbe6', label: '综合评分', value: stats.rating },
{ icon: <RiseOutlined />, color: '#52c41a', bg: '#f6ffed', label: '本月收入', value: `¥${stats.income_month}` }, { icon: <RiseOutlined />, color: '#52c41a', bg: '#f6ffed', label: '本月收入', value: `¥${stats.income_month}` },
{ icon: <MessageOutlined />, color: '#13c2c2', bg: '#e6fffb', label: '进行?, value: stats.in_progress_count }, { icon: <MessageOutlined />, color: '#13c2c2', bg: '#e6fffb', label: '进行', value: stats.in_progress_count },
].map((item, i) => ( ].map((item, i) => (
<Col xs={12} sm={6} key={i}> <Col xs={12} sm={6} key={i}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center">
...@@ -152,7 +152,7 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -152,7 +152,7 @@ const DoctorWorkbenchPage: React.FC = () => {
<List <List
size="small" size="small"
dataSource={waitingPatients} dataSource={waitingPatients}
locale={{ emptyText: '暂无等待接诊的患? }} locale={{ emptyText: '暂无等待接诊的患' }}
renderItem={(patient) => ( renderItem={(patient) => (
<List.Item <List.Item
actions={[ actions={[
......
...@@ -24,8 +24,8 @@ const controlStatusMap: Record<string, { color: string; label: string }> = { ...@@ -24,8 +24,8 @@ const controlStatusMap: Record<string, { color: string; label: string }> = {
}; };
const metricTypeMap: Record<string, { label: string; unit: string; hasValue2: boolean; v1Label: string; v2Label?: string }> = { const metricTypeMap: Record<string, { label: string; unit: string; hasValue2: boolean; v1Label: string; v2Label?: string }> = {
blood_pressure: { label: '?, unit: 'mmHg', hasValue2: true, v1Label: '收缩?, v2Label: '舒张? }, blood_pressure: { label: '压, unit: 'mmHg', hasValue2: true, v1Label: '收缩压, v2Label: '舒张压 },
blood_sugar: { label: '?, unit: 'mmol/L', hasValue2: false, v1Label: '血糖? }, blood_sugar: { label: ', unit: 'mmol/L', hasValue2: false, v1Label: '血糖化 },
weight: { label: '体重', unit: 'kg', hasValue2: false, v1Label: '体重' }, weight: { label: '体重', unit: 'kg', hasValue2: false, v1Label: '体重' },
heart_rate: { label: '心率', unit: 'bpm', hasValue2: false, v1Label: '心率' }, heart_rate: { label: '心率', unit: 'bpm', hasValue2: false, v1Label: '心率' },
temperature: { label: '体温', unit: '?, hasValue2: false, v1Label: '体温' }, temperature: { label: '体温', unit: '?, hasValue2: false, v1Label: '体温' },
...@@ -82,7 +82,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -82,7 +82,7 @@ const ChronicRecordsTab: React.FC = () => {
{ title: '确诊医院', dataIndex: 'hospital', key: 'hospital', ellipsis: true }, { title: '确诊医院', dataIndex: 'hospital', key: 'hospital', ellipsis: true },
{ title: '主治医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 100 }, { title: '主治医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 100 },
{ {
title: '控制状?, dataIndex: 'control_status', key: 'control_status', width: 100, title: '控制状, dataIndex: 'control_status', key: 'control_status', width: 100,
render: (v: string) => { render: (v: string) => {
const s = controlStatusMap[v] || { color: 'default', label: v }; const s = controlStatusMap[v] || { color: 'default', label: v };
return <Tag color={s.color}>{s.label}</Tag>; return <Tag color={s.color}>{s.label}</Tag>;
...@@ -97,7 +97,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -97,7 +97,7 @@ const ChronicRecordsTab: React.FC = () => {
render: (_: any, r: ChronicRecord) => ( render: (_: any, r: ChronicRecord) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} /> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} />
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />} /> <Button type="link" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
</Space> </Space>
...@@ -129,7 +129,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -129,7 +129,7 @@ const ChronicRecordsTab: React.FC = () => {
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="control_status" label="控制状? initialValue="stable"> <Form.Item name="control_status" label="控制状 initialValue="stable">
<Select options={Object.entries(controlStatusMap).map(([v, s]) => ({ value: v, label: s.label }))} /> <Select options={Object.entries(controlStatusMap).map(([v, s]) => ({ value: v, label: s.label }))} />
</Form.Item> </Form.Item>
</Col> </Col>
...@@ -147,7 +147,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -147,7 +147,7 @@ const ChronicRecordsTab: React.FC = () => {
</Col> </Col>
</Row> </Row>
<Form.Item name="current_meds" label="当前用药"> <Form.Item name="current_meds" label="当前用药">
<TextArea rows={2} placeholder="当前服用的药? /> <TextArea rows={2} placeholder="当前服用的药 />
</Form.Item> </Form.Item>
<Form.Item name="notes" label="备注"> <Form.Item name="notes" label="备注">
<TextArea rows={2} /> <TextArea rows={2} />
...@@ -175,7 +175,7 @@ const RenewalsTab: React.FC = () => { ...@@ -175,7 +175,7 @@ const RenewalsTab: React.FC = () => {
medicines: values.medicines?.split('\n').filter(Boolean) || [], medicines: values.medicines?.split('\n').filter(Boolean) || [],
}), }),
onSuccess: () => { onSuccess: () => {
message.success('续方申请已提?); message.success('续方申请已提);
qc.invalidateQueries({ queryKey: ['renewals'] }); qc.invalidateQueries({ queryKey: ['renewals'] });
setModalOpen(false); setModalOpen(false);
form.resetFields(); form.resetFields();
...@@ -196,9 +196,9 @@ const RenewalsTab: React.FC = () => { ...@@ -196,9 +196,9 @@ const RenewalsTab: React.FC = () => {
}; };
const statusMap: Record<string, { color: string; label: string }> = { const statusMap: Record<string, { color: string; label: string }> = {
pending: { color: 'orange', label: '待审? }, pending: { color: 'orange', label: '待审 },
approved: { color: 'green', label: '已通过' }, approved: { color: 'green', label: '已通过' },
rejected: { color: 'red', label: '已拒? }, rejected: { color: 'red', label: '已拒 },
}; };
const columns = [ const columns = [
...@@ -212,7 +212,7 @@ const RenewalsTab: React.FC = () => { ...@@ -212,7 +212,7 @@ const RenewalsTab: React.FC = () => {
}, },
{ title: '申请原因', dataIndex: 'reason', key: 'reason', ellipsis: true }, { title: '申请原因', dataIndex: 'reason', key: 'reason', ellipsis: true },
{ {
title: '?, dataIndex: 'status', key: 'status', width: 90, title: ', dataIndex: 'status', key: 'status', width: 90,
render: (v: string) => { const s = statusMap[v] || { color: 'default', label: v }; return <Tag color={s.color}>{s.label}</Tag>; }, render: (v: string) => { const s = statusMap[v] || { color: 'default', label: v }; return <Tag color={s.color}>{s.label}</Tag>; },
}, },
{ {
...@@ -254,13 +254,13 @@ const RenewalsTab: React.FC = () => { ...@@ -254,13 +254,13 @@ const RenewalsTab: React.FC = () => {
options={(records?.data || []).map(r => ({ value: r.id, label: r.disease_name }))} /> options={(records?.data || []).map(r => ({ value: r.id, label: r.disease_name }))} />
</Form.Item> </Form.Item>
<Form.Item name="disease_name" label="疾病名称" rules={[{ required: true }]}> <Form.Item name="disease_name" label="疾病名称" rules={[{ required: true }]}>
<Input placeholder="如:高血? /> <Input placeholder="如:高血 />
</Form.Item> </Form.Item>
<Form.Item name="medicines" label="申请药品(每行一种)" rules={[{ required: true }]}> <Form.Item name="medicines" label="申请药品(每行一种)" rules={[{ required: true }]}>
<TextArea rows={3} placeholder="苯磺酸氨氯地平片&#10;厄贝沙坦? /> <TextArea rows={3} placeholder="苯磺酸氨氯地平片&#10;厄贝沙坦 />
</Form.Item> </Form.Item>
<Form.Item name="reason" label="续方原因" rules={[{ required: true }]}> <Form.Item name="reason" label="续方原因" rules={[{ required: true }]}>
<TextArea rows={2} placeholder="请说明续方原? /> <TextArea rows={2} placeholder="请说明续方原 />
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
...@@ -323,7 +323,7 @@ const RemindersTab: React.FC = () => { ...@@ -323,7 +323,7 @@ const RemindersTab: React.FC = () => {
render: (v: string) => { try { return (JSON.parse(v) as string[]).map((t, i) => <Tag key={i}>{t}</Tag>); } catch { return v; } }, render: (v: string) => { try { return (JSON.parse(v) as string[]).map((t, i) => <Tag key={i}>{t}</Tag>); } catch { return v; } },
}, },
{ {
title: '?, dataIndex: 'is_active', key: 'is_active', width: 80, title: ', dataIndex: 'is_active', key: 'is_active', width: 80,
render: (v: boolean, r: MedicationReminder) => ( render: (v: boolean, r: MedicationReminder) => (
<Switch checked={v} size="small" onChange={() => toggleMutation.mutate(r.id)} /> <Switch checked={v} size="small" onChange={() => toggleMutation.mutate(r.id)} />
), ),
...@@ -333,7 +333,7 @@ const RemindersTab: React.FC = () => { ...@@ -333,7 +333,7 @@ const RemindersTab: React.FC = () => {
render: (_: any, r: MedicationReminder) => ( render: (_: any, r: MedicationReminder) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} /> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} />
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />} /> <Button type="link" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
</Space> </Space>
...@@ -364,13 +364,13 @@ const RemindersTab: React.FC = () => { ...@@ -364,13 +364,13 @@ const RemindersTab: React.FC = () => {
</Col> </Col>
</Row> </Row>
<Form.Item name="frequency" label="服药频次" rules={[{ required: true }]}> <Form.Item name="frequency" label="服药频次" rules={[{ required: true }]}>
<Select options={['每日一?,'每日两次','每日三次','每周一?,'按需服用'].map(v => ({ value: v, label: v }))} /> <Select options={['每日一次,'每日两次','每日三次','每周一次,'按需服用'].map(v => ({ value: v, label: v }))} />
</Form.Item> </Form.Item>
<Form.Item name="remind_times" label="提醒时间" rules={[{ required: true }]}> <Form.Item name="remind_times" label="提醒时间" rules={[{ required: true }]}>
<TimePicker.RangePicker format="HH:mm" style={{ width: '100%' }} /> <TimePicker.RangePicker format="HH:mm" style={{ width: '100%' }} />
</Form.Item> </Form.Item>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}><Form.Item name="start_date" label="开始日?><DatePicker style={{ width: '100%' }} /></Form.Item></Col> <Col span={12}><Form.Item name="start_date" label="开始日><DatePicker style={{ width: '100%' }} /></Form.Item></Col>
<Col span={12}><Form.Item name="end_date" label="结束日期"><DatePicker style={{ width: '100%' }} /></Form.Item></Col> <Col span={12}><Form.Item name="end_date" label="结束日期"><DatePicker style={{ width: '100%' }} /></Form.Item></Col>
</Row> </Row>
<Form.Item name="notes" label="备注"><Input /></Form.Item> <Form.Item name="notes" label="备注"><Input /></Form.Item>
...@@ -420,7 +420,7 @@ const MetricsTab: React.FC = () => { ...@@ -420,7 +420,7 @@ const MetricsTab: React.FC = () => {
const columns = [ const columns = [
{ {
title: '?, key: 'value', title: ', key: 'value',
render: (_: any, r: HealthMetric) => ( render: (_: any, r: HealthMetric) => (
<Text strong style={{ color: '#1890ff' }}> <Text strong style={{ color: '#1890ff' }}>
{meta.hasValue2 ? `${r.value1}/${r.value2}` : r.value1} {r.unit} {meta.hasValue2 ? `${r.value1}/${r.value2}` : r.value1} {r.unit}
...@@ -435,14 +435,15 @@ const MetricsTab: React.FC = () => { ...@@ -435,14 +435,15 @@ const MetricsTab: React.FC = () => {
{ {
title: '操作', key: 'action', width: 80, title: '操作', key: 'action', width: 80,
render: (_: any, r: HealthMetric) => ( render: (_: any, r: HealthMetric) => (
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />} /> <Button type="link" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
), ),
}, },
]; ];
// 最新值统? const latest = list[0]; //
const latest = list[0];
return ( return (
<> <>
...@@ -458,7 +459,7 @@ const MetricsTab: React.FC = () => { ...@@ -458,7 +459,7 @@ const MetricsTab: React.FC = () => {
<Row gutter={16} style={{ marginBottom: 16 }}> <Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={8}> <Col span={8}>
<Card size="small" style={{ borderRadius: 8, background: '#f0f5ff' }}> <Card size="small" style={{ borderRadius: 8, background: '#f0f5ff' }}>
<Statistic title="最新? value={meta.hasValue2 ? `${latest.value1}/${latest.value2}` : latest.value1} <Statistic title="最新 value={meta.hasValue2 ? `${latest.value1}/${latest.value2}` : latest.value1}
suffix={meta.unit} valueStyle={{ color: '#1890ff' }} /> suffix={meta.unit} valueStyle={{ color: '#1890ff' }} />
</Card> </Card>
</Col> </Col>
...@@ -469,7 +470,7 @@ const MetricsTab: React.FC = () => { ...@@ -469,7 +470,7 @@ const MetricsTab: React.FC = () => {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Card size="small" style={{ borderRadius: 8, background: '#fff7e6' }}> <Card size="small" style={{ borderRadius: 8, background: '#fff7e6' }}>
<Statistic title="最近记? value={dayjs(latest.recorded_at).format('MM-DD')} <Statistic title="最近记 value={dayjs(latest.recorded_at).format('MM-DD')}
valueStyle={{ color: '#fa8c16' }} /> valueStyle={{ color: '#fa8c16' }} />
</Card> </Card>
</Col> </Col>
......
...@@ -25,7 +25,7 @@ const { TextArea } = Input; ...@@ -25,7 +25,7 @@ const { TextArea } = Input;
const ConsultCreatePage: React.FC = () => { const ConsultCreatePage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const [searchParams] = useSearchParams(); const searchParams = useSearchParams();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [doctor, setDoctor] = useState<Doctor | null>(null); const [doctor, setDoctor] = useState<Doctor | null>(null);
...@@ -51,7 +51,8 @@ const ConsultCreatePage: React.FC = () => { ...@@ -51,7 +51,8 @@ const ConsultCreatePage: React.FC = () => {
if (preConsultId) { if (preConsultId) {
const res = await preConsultApi.getDetail(preConsultId); const res = await preConsultApi.getDetail(preConsultId);
setPreConsult(res.data); setPreConsult(res.data);
// 用预问诊的主诉填? form.setFieldsValue({ //
form.setFieldsValue({
chief_complaint: res.data.chief_complaint || chiefComplaintParam, chief_complaint: res.data.chief_complaint || chiefComplaintParam,
}); });
} else if (chiefComplaintParam) { } else if (chiefComplaintParam) {
...@@ -79,16 +80,17 @@ const ConsultCreatePage: React.FC = () => { ...@@ -79,16 +80,17 @@ const ConsultCreatePage: React.FC = () => {
pre_consult_id: preConsultId || undefined, pre_consult_id: preConsultId || undefined,
}); });
message.success('问诊创建成功?); message.success('问诊创建成功);
// 跳转到问诊对话页? if (consultType === 'video') { //
if (consultType === 'video') {
router.push(`/patient/consult/video/${res.data.id}`); router.push(`/patient/consult/video/${res.data.id}`);
} else { } else {
router.push(`/patient/consult/text/${res.data.id}`); router.push(`/patient/consult/text/${res.data.id}`);
} }
} catch (error: any) { } catch (error: any) {
if (error?.errorFields) return; if (error?.errorFields) return;
message.error('创建问诊失败: ' + (error?.response?.data?.message || error?.message || '请稍后重?)); message.error('创建问诊失败: ' + (error?.response?.data?.message || error?.message || '请稍后重));
} finally { } finally {
setSubmitting(false); setSubmitting(false);
} }
...@@ -97,7 +99,7 @@ const ConsultCreatePage: React.FC = () => { ...@@ -97,7 +99,7 @@ const ConsultCreatePage: React.FC = () => {
if (loading) { if (loading) {
return ( return (
<div style={{ textAlign: 'center', padding: 80 }}> <div style={{ textAlign: 'center', padding: 80 }}>
<Spin size="large" tip="加载?.." /> <Spin size="large" tip="加载.." />
</div> </div>
); );
} }
...@@ -109,7 +111,7 @@ const ConsultCreatePage: React.FC = () => { ...@@ -109,7 +111,7 @@ const ConsultCreatePage: React.FC = () => {
{consultType === 'video' ? <VideoCameraOutlined className="mr-1" /> : <MessageOutlined className="mr-1" />} {consultType === 'video' ? <VideoCameraOutlined className="mr-1" /> : <MessageOutlined className="mr-1" />}
创建{consultType === 'video' ? '视频' : '图文'}问诊 创建{consultType === 'video' ? '视频' : '图文'}问诊
</h4> </h4>
<Button size="small" icon={<ArrowLeftOutlined />} onClick={() => router.push(-1)}>返回</Button> <Button size="small" icon={<ArrowLeftOutlined />} onClick={() => router.back()}>返回</Button>
</div> </div>
{doctor && ( {doctor && (
...@@ -124,7 +126,7 @@ const ConsultCreatePage: React.FC = () => { ...@@ -124,7 +126,7 @@ const ConsultCreatePage: React.FC = () => {
</div> </div>
<Text type="secondary" className="text-xs!">{doctor.hospital}</Text> <Text type="secondary" className="text-xs!">{doctor.hospital}</Text>
<div className="mt-0.5"> <div className="mt-0.5">
<Tag color="orange">¥{(doctor.price / 100).toFixed(0)}/?/Tag> <Tag color="orange">¥{(doctor.price / 100).toFixed(0)}/</Tag>
<Text type="secondary" className="text-xs!">评分 {doctor.rating} · {doctor.consult_count}?/Text> <Text type="secondary" className="text-xs!">评分 {doctor.rating} · {doctor.consult_count}?/Text>
</div> </div>
</div> </div>
...@@ -152,8 +154,8 @@ const ConsultCreatePage: React.FC = () => { ...@@ -152,8 +154,8 @@ const ConsultCreatePage: React.FC = () => {
<Card size="small"> <Card size="small">
<Form form={form} layout="vertical" size="small"> <Form form={form} layout="vertical" size="small">
<Form.Item label="主诉(症状描述)" name="chief_complaint" rules={[{ required: true, message: '请描述您的症? }]}> <Form.Item label="主诉(症状描述)" name="chief_complaint" rules={[{ required: true, message: '请描述您的症 }]}>
<TextArea rows={3} placeholder="请详细描述您的症?.." maxLength={500} showCount /> <TextArea rows={3} placeholder="请详细描述您的症.." maxLength={500} showCount />
</Form.Item> </Form.Item>
<Form.Item label="既往病史" name="medical_history"> <Form.Item label="既往病史" name="medical_history">
<TextArea rows={2} placeholder="如有慢性病、手术史等请填写" /> <TextArea rows={2} placeholder="如有慢性病、手术史等请填写" />
...@@ -161,9 +163,9 @@ const ConsultCreatePage: React.FC = () => { ...@@ -161,9 +163,9 @@ const ConsultCreatePage: React.FC = () => {
<Divider className="my-2!" /> <Divider className="my-2!" />
<Form.Item className="text-center mb-0!"> <Form.Item className="text-center mb-0!">
<Space size={8}> <Space size={8}>
<Button size="small" onClick={() => router.push(-1)}>取消</Button> <Button size="small" onClick={() => router.back()}>取消</Button>
<Button type="primary" size="small" onClick={handleSubmit} loading={submitting}> <Button type="primary" size="small" onClick={handleSubmit} loading={submitting}>
{submitting ? '提交?..' : `发起${consultType === 'video' ? '视频' : '图文'}问诊`} {submitting ? '提交..' : `发起${consultType === 'video' ? '视频' : '图文'}问诊`}
</Button> </Button>
</Space> </Space>
</Form.Item> </Form.Item>
......
'use client'; 'use client';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { import {
...@@ -15,11 +15,11 @@ import dayjs from 'dayjs'; ...@@ -15,11 +15,11 @@ import dayjs from 'dayjs';
const { Text, Title } = Typography; const { Text, Title } = Typography;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '寰呮帴璇?, color: 'orange' }, pending: { text: '待接诊', color: 'orange' },
waiting: { text: '绛夊緟涓?, color: 'blue' }, waiting: { text: '等待中', color: 'blue' },
in_progress: { text: '闂瘖涓?, color: 'green' }, in_progress: { text: '问诊中', color: 'green' },
completed: { text: '宸插畬鎴?, color: 'default' }, completed: { text: '已完成', color: 'default' },
cancelled: { text: '宸插彇娑?, color: 'red' }, cancelled: { text: '已取消', color: 'red' },
}; };
const statusIcon: Record<string, React.ReactNode> = { const statusIcon: Record<string, React.ReactNode> = {
...@@ -42,7 +42,7 @@ const PatientConsultListPage: React.FC = () => { ...@@ -42,7 +42,7 @@ const PatientConsultListPage: React.FC = () => {
const res = await consultApi.getConsultList(status); const res = await consultApi.getConsultList(status);
setConsultList(res.data || []); setConsultList(res.data || []);
} catch { } catch {
message.error('鑾峰彇闂瘖鍒楄〃澶辫触'); message.error('获取问诊列表失败');
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -64,10 +64,10 @@ const PatientConsultListPage: React.FC = () => { ...@@ -64,10 +64,10 @@ const PatientConsultListPage: React.FC = () => {
const handleCancel = async (consult: Consultation) => { const handleCancel = async (consult: Consultation) => {
try { try {
await consultApi.cancelConsult(consult.id); await consultApi.cancelConsult(consult.id);
message.success('闂瘖宸插彇娑?); message.success('问诊已取消');
fetchList(activeTab === 'all' ? undefined : activeTab as ConsultStatus); fetchList(activeTab === 'all' ? undefined : activeTab as ConsultStatus);
} catch { } catch {
message.error('鍙栨秷澶辫触'); message.error('取消失败');
} }
}; };
...@@ -76,26 +76,26 @@ const PatientConsultListPage: React.FC = () => { ...@@ -76,26 +76,26 @@ const PatientConsultListPage: React.FC = () => {
if (consult.status === 'in_progress') { if (consult.status === 'in_progress') {
actions.push( actions.push(
<Button type="primary" size="small" onClick={() => handleEnterConsult(consult)}> <Button type="primary" size="small" onClick={() => handleEnterConsult(consult)}>
杩涘叆闂瘖 进入问诊
</Button> </Button>
); );
} }
if (consult.status === 'pending' || consult.status === 'waiting') { if (consult.status === 'pending' || consult.status === 'waiting') {
actions.push( actions.push(
<Button size="small" onClick={() => handleEnterConsult(consult)}> <Button size="small" onClick={() => handleEnterConsult(consult)}>
鏌ョ湅璇︽儏 查看详情
</Button> </Button>
); );
actions.push( actions.push(
<Button size="small" danger onClick={() => handleCancel(consult)}> <Button size="small" danger onClick={() => handleCancel(consult)}>
鍙栨秷 取消
</Button> </Button>
); );
} }
if (consult.status === 'completed' && consult.prescription_id) { if (consult.status === 'completed' && consult.prescription_id) {
actions.push( actions.push(
<Button size="small" onClick={() => router.push(`/patient/prescription/${consult.prescription_id}`)}> <Button size="small" onClick={() => router.push(`/patient/prescription/${consult.prescription_id}`)}>
鏌ョ湅澶勬柟 查看处方
</Button> </Button>
); );
} }
...@@ -107,9 +107,9 @@ const PatientConsultListPage: React.FC = () => { ...@@ -107,9 +107,9 @@ const PatientConsultListPage: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0"> <h4 className="text-sm font-bold text-gray-800 m-0">
<MedicineBoxOutlined className="mr-1 text-[#1890ff]" /> <MedicineBoxOutlined className="mr-1 text-[#1890ff]" />
鎴戠殑闂瘖 我的问诊
</h4> </h4>
<Button size="small" type="primary" onClick={() => router.push('/patient/doctors')}>鍙戣捣闂瘖</Button> <Button size="small" type="primary" onClick={() => router.push('/patient/doctors')}>发起问诊</Button>
</div> </div>
<Card size="small"> <Card size="small">
...@@ -118,11 +118,11 @@ const PatientConsultListPage: React.FC = () => { ...@@ -118,11 +118,11 @@ const PatientConsultListPage: React.FC = () => {
onChange={setActiveTab} onChange={setActiveTab}
size="small" size="small"
items={[ items={[
{ key: 'all', label: '鍏ㄩ儴' }, { key: 'all', label: '全部' },
{ key: 'in_progress', label: '杩涜涓? }, { key: 'in_progress', label: '进行中' },
{ key: 'pending', label: '寰呮帴璇? }, { key: 'pending', label: '待接诊' },
{ key: 'completed', label: '宸插畬鎴? }, { key: 'completed', label: '已完成' },
{ key: 'cancelled', label: '宸插彇娑? }, { key: 'cancelled', label: '已取消' },
]} ]}
/> />
...@@ -130,7 +130,7 @@ const PatientConsultListPage: React.FC = () => { ...@@ -130,7 +130,7 @@ const PatientConsultListPage: React.FC = () => {
<List <List
size="small" size="small"
dataSource={consultList} dataSource={consultList}
locale={{ emptyText: <Empty description="鏆傛棤闂瘖璁板綍" /> }} locale={{ emptyText: <Empty description="暂无问诊记录" /> }}
renderItem={(consult: Consultation) => { renderItem={(consult: Consultation) => {
const status = statusMap[consult.status] || { text: consult.status, color: 'default' }; const status = statusMap[consult.status] || { text: consult.status, color: 'default' };
return ( return (
...@@ -151,18 +151,18 @@ const PatientConsultListPage: React.FC = () => { ...@@ -151,18 +151,18 @@ const PatientConsultListPage: React.FC = () => {
icon={consult.type === 'video' ? <VideoCameraOutlined /> : <MessageOutlined />} icon={consult.type === 'video' ? <VideoCameraOutlined /> : <MessageOutlined />}
color={consult.type === 'video' ? 'blue' : 'cyan'} color={consult.type === 'video' ? 'blue' : 'cyan'}
> >
{consult.type === 'video' ? '瑙嗛' : '鍥炬枃'} {consult.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
<Tag icon={statusIcon[consult.status]} color={status.color}>{status.text}</Tag> <Tag icon={statusIcon[consult.status]} color={status.color}>{status.text}</Tag>
</span> </span>
} }
description={ description={
<div className="mt-0.5"> <div className="mt-0.5">
<div className="text-xs text-gray-600 mb-0.5">涓昏瘔锛歿consult.chief_complaint || '鏃?}</div> <div className="text-xs text-gray-600 mb-0.5">主诉:{consult.chief_complaint || ''}</div>
<span className="text-[11px] text-gray-400"> <span className="text-[11px] text-gray-400">
{dayjs(consult.created_at).format('YYYY-MM-DD HH:mm')} {dayjs(consult.created_at).format('YYYY-MM-DD HH:mm')}
{consult.started_at && ` 路 鎺ヨ瘖 ${dayjs(consult.started_at).format('HH:mm')}`} {consult.started_at && ` · 接诊 ${dayjs(consult.started_at).format('HH:mm')}`}
{consult.ended_at && ` 路 缁撴潫 ${dayjs(consult.ended_at).format('HH:mm')}`} {consult.ended_at && ` · 结束 ${dayjs(consult.ended_at).format('HH:mm')}`}
</span> </span>
</div> </div>
} }
......
...@@ -60,7 +60,7 @@ const DoctorDetailPage: React.FC = () => { ...@@ -60,7 +60,7 @@ const DoctorDetailPage: React.FC = () => {
<div className="text-right"> <div className="text-right">
<div className="mb-2"> <div className="mb-2">
<Text strong className="text-xl! text-red-500!">¥{(doctor.price / 100).toFixed(0)}</Text> <Text strong className="text-xl! text-red-500!">¥{(doctor.price / 100).toFixed(0)}</Text>
<Text type="secondary" className="text-xs!">/?/Text> <Text type="secondary" className="text-xs!">/</Text>
</div> </div>
<Space> <Space>
<Button type="primary" size="small" icon={<VideoCameraOutlined />} <Button type="primary" size="small" icon={<VideoCameraOutlined />}
...@@ -81,18 +81,18 @@ const DoctorDetailPage: React.FC = () => { ...@@ -81,18 +81,18 @@ const DoctorDetailPage: React.FC = () => {
</Space> </Space>
</Card> </Card>
<Card title={<span className="text-xs font-semibold">医生简?/span>} size="small"> <Card title={<span className="text-xs font-semibold">医生简</span>} size="small">
<Paragraph className="text-xs! mb-0!">{doctor.introduction || '暂无简?}</Paragraph> <Paragraph className="text-xs! mb-0!">{doctor.introduction || '暂无简}</Paragraph>
</Card> </Card>
<Card title={<span className="text-xs font-semibold">排班信息</span>} <Card title={<span className="text-xs font-semibold">排班信息</span>}
extra={<Button type="link" size="small" icon={<CalendarOutlined />}>更多</Button>} size="small"> extra={<Button type="link" size="small" icon={<CalendarOutlined />}>更多</Button>} size="small">
<Descriptions column={2} size="small"> <Descriptions column={2} size="small">
<Descriptions.Item label="出诊状?> <Descriptions.Item label="出诊状>
{doctor.is_online ? <Tag color="green">在线</Tag> : <Tag color="default">未排?/Tag>} {doctor.is_online ? <Tag color="green">在线</Tag> : <Tag color="default">未排</Tag>}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="资质认证"> <Descriptions.Item label="资质认证">
<Space size={4}><SafetyCertificateOutlined className="text-green-500" /><Text className="text-xs!">已认?/Text></Space> <Space size={4}><SafetyCertificateOutlined className="text-green-500" /><Text className="text-xs!">已认</Text></Space>
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</Card> </Card>
......
...@@ -12,7 +12,7 @@ const { Text } = Typography; ...@@ -12,7 +12,7 @@ const { Text } = Typography;
const PatientDoctorsPage: React.FC = () => { const PatientDoctorsPage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const [searchParams] = useSearchParams(); const searchParams = useSearchParams();
const [keyword, setKeyword] = useState(searchParams.get('keyword') || ''); const [keyword, setKeyword] = useState(searchParams.get('keyword') || '');
const [departmentId, setDepartmentId] = useState(searchParams.get('department') || ''); const [departmentId, setDepartmentId] = useState(searchParams.get('department') || '');
const [sortBy, setSortBy] = useState<'rating' | 'consult_count' | 'price'>('rating'); const [sortBy, setSortBy] = useState<'rating' | 'consult_count' | 'price'>('rating');
...@@ -68,9 +68,9 @@ const PatientDoctorsPage: React.FC = () => { ...@@ -68,9 +68,9 @@ const PatientDoctorsPage: React.FC = () => {
style={{ width: 110 }} style={{ width: 110 }}
size="small" size="small"
options={[ options={[
{ label: '评分最?, value: 'rating' }, { label: '评分最, value: 'rating' },
{ label: '问诊最?, value: 'consult_count' }, { label: '问诊最, value: 'consult_count' },
{ label: '价格最?, value: 'price' }, { label: '价格最, value: 'price' },
]} ]}
/> />
</div> </div>
...@@ -105,7 +105,7 @@ const PatientDoctorsPage: React.FC = () => { ...@@ -105,7 +105,7 @@ const PatientDoctorsPage: React.FC = () => {
<div className="text-right"> <div className="text-right">
<div className="mb-2"> <div className="mb-2">
<span className="text-base font-bold text-red-500">¥{(doctor.price / 100).toFixed(0)}</span> <span className="text-base font-bold text-red-500">¥{(doctor.price / 100).toFixed(0)}</span>
<span className="text-[11px] text-gray-400">/?/span> <span className="text-[11px] text-gray-400">/</span>
</div> </div>
<Space size={4}> <Space size={4}>
<Button size="small" icon={<MessageOutlined />} onClick={() => handleConsult(doctor, 'text')}> <Button size="small" icon={<MessageOutlined />} onClick={() => handleConsult(doctor, 'text')}>
......
...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation'; ...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation';
const { Text } = Typography; const { Text } = Typography;
const features = [ const features = [
{ icon: <WechatOutlined style={{ fontSize: 28, color: '#07c160' }} />, title: '寰俊/鏀粯瀹濇敮浠?, desc: '渚挎嵎鐨勫湪绾挎敮浠樻柟寮? }, { icon: <WechatOutlined style={{ fontSize: 28, color: '#07c160' }} />, title: '微信/支付宝支付', desc: '便捷的在线支付方式' },
{ icon: <PayCircleOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '鍖讳繚鎵h垂', desc: '鏀寔鍖讳繚鍦ㄧ嚎缁撶畻' }, { icon: <PayCircleOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '医保扣费', desc: '支持医保在线结算' },
{ icon: <FileTextOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '璐﹀崟鏄庣粏', desc: '璇︾粏鐨勮垂鐢ㄦ竻鍗? }, { icon: <FileTextOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '账单明细', desc: '详细的费用清单' },
{ icon: <HistoryOutlined style={{ fontSize: 28, color: '#722ed1' }} />, title: '鏀粯璁板綍', desc: '鍘嗗彶鏀粯璁板綍鏌ヨ' }, { icon: <HistoryOutlined style={{ fontSize: 28, color: '#722ed1' }} />, title: '支付记录', desc: '历史支付记录查询' },
]; ];
const PatientPaymentPage: React.FC = () => { const PatientPaymentPage: React.FC = () => {
...@@ -19,13 +19,13 @@ const PatientPaymentPage: React.FC = () => { ...@@ -19,13 +19,13 @@ const PatientPaymentPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">鍦ㄧ嚎鏀粯</h4> <h4 className="text-sm font-bold text-gray-800 m-0">在线支付</h4>
<Button type="primary" size="small" onClick={() => router.push('/patient/consult')}>鏌ョ湅闂瘖</Button> <Button type="primary" size="small" onClick={() => router.push('/patient/consult')}>查看问诊</Button>
</div> </div>
<Card size="small"> <Card size="small">
<div className="text-center py-4"> <div className="text-center py-4">
<PayCircleOutlined className="text-4xl text-yellow-400 mb-2" /> <PayCircleOutlined className="text-4xl text-yellow-400 mb-2" />
<div className="text-xs text-gray-400">鍔熻兘寮€鍙戜腑锛屾暚璇锋湡寰?/div> <div className="text-xs text-gray-400">支付方式说明</div>
</div> </div>
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{features.map((f) => ( {features.map((f) => (
...@@ -48,4 +48,4 @@ const PatientPaymentPage: React.FC = () => { ...@@ -48,4 +48,4 @@ const PatientPaymentPage: React.FC = () => {
}; };
export default PatientPaymentPage; export default PatientPaymentPage;
...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation'; ...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation';
const { Text } = Typography; const { Text } = Typography;
const features = [ const features = [
{ icon: <EnvironmentOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '閰嶉€佽繘搴﹁窡韪?, desc: '瀹炴椂鏌ョ湅鑽搧閰嶉€佺姸鎬? }, { icon: <EnvironmentOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '配送进度跟踪', desc: '实时查看药品配送状态' },
{ icon: <ShoppingCartOutlined style={{ fontSize: 28, color: '#eb2f96' }} />, title: '鐗╂祦鍦板浘', desc: '鍙鍖栭厤閫佽矾绾? }, { icon: <ShoppingCartOutlined style={{ fontSize: 28, color: '#eb2f96' }} />, title: '物流地图', desc: '可视化配送路线' },
{ icon: <CheckCircleOutlined style={{ fontSize: 28, color: '#52c41a' }} />, title: '绛炬敹纭', desc: '鍦ㄧ嚎纭鏀惰揣' }, { icon: <CheckCircleOutlined style={{ fontSize: 28, color: '#52c41a' }} />, title: '签收确认', desc: '在线确认收货' },
{ icon: <HistoryOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '璁㈠崟鍘嗗彶', desc: '鏌ョ湅鍘嗗彶鑽搧璁㈠崟' }, { icon: <HistoryOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '订单历史', desc: '查看历史药品订单' },
]; ];
const PatientPharmacyOrderPage: React.FC = () => { const PatientPharmacyOrderPage: React.FC = () => {
...@@ -19,13 +19,13 @@ const PatientPharmacyOrderPage: React.FC = () => { ...@@ -19,13 +19,13 @@ const PatientPharmacyOrderPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">鑽搧閰嶉€?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">药品配送</h4>
<Button type="primary" size="small" onClick={() => router.push('/patient/prescription')}>鏌ョ湅澶勬柟</Button> <Button type="primary" size="small" onClick={() => router.push('/patient/prescription')}>查看处方</Button>
</div> </div>
<Card size="small"> <Card size="small">
<div className="text-center py-4"> <div className="text-center py-4">
<ShoppingCartOutlined className="text-4xl text-pink-500 mb-2" /> <ShoppingCartOutlined className="text-4xl text-pink-500 mb-2" />
<div className="text-xs text-gray-400">鍔熻兘寮€鍙戜腑锛屾暚璇锋湡寰?/div> <div className="text-xs text-gray-400">功能开发中,敬请期待</div>
</div> </div>
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{features.map((f) => ( {features.map((f) => (
...@@ -48,4 +48,4 @@ const PatientPharmacyOrderPage: React.FC = () => { ...@@ -48,4 +48,4 @@ const PatientPharmacyOrderPage: React.FC = () => {
}; };
export default PatientPharmacyOrderPage; export default PatientPharmacyOrderPage;
...@@ -40,7 +40,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -40,7 +40,8 @@ const PatientPreConsultPage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { user } = useUserStore(); const { user } = useUserStore();
// 对话状? const [messages, setMessages] = useState<DisplayMessage[]>([]); //
const [messages, setMessages] = useState<DisplayMessage[]>([]);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [sessionId, setSessionId] = useState<string | null>(null); const [sessionId, setSessionId] = useState<string | null>(null);
...@@ -48,21 +49,24 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -48,21 +49,24 @@ const PatientPreConsultPage: React.FC = () => {
const [turnCount, setTurnCount] = useState(0); const [turnCount, setTurnCount] = useState(0);
const [initialLoading, setInitialLoading] = useState(true); const [initialLoading, setInitialLoading] = useState(true);
// 分析报告状? const [reportContent, setReportContent] = useState(''); //
const [reportContent, setReportContent] = useState('');
const [generating, setGenerating] = useState(false); const [generating, setGenerating] = useState(false);
const [reportDone, setReportDone] = useState(false); const [reportDone, setReportDone] = useState(false);
const [doneInfo, setDoneInfo] = useState<ChatDoneInfo | null>(null); const [doneInfo, setDoneInfo] = useState<ChatDoneInfo | null>(null);
// 创建问诊弹窗状? const [consultModalOpen, setConsultModalOpen] = useState(false); //
const [consultModalOpen, setConsultModalOpen] = useState(false);
const [selectedDoctor, setSelectedDoctor] = useState<DoctorBrief | null>(null); const [selectedDoctor, setSelectedDoctor] = useState<DoctorBrief | null>(null);
// 历史对话抽屉状? const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false); //
const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false);
const [historyList, setHistoryList] = useState<PreConsultResponse[]>([]); const [historyList, setHistoryList] = useState<PreConsultResponse[]>([]);
const [historyLoading, setHistoryLoading] = useState(false); const [historyLoading, setHistoryLoading] = useState(false);
// 患者信息(从用户信息自动获取) // 患者信息(从用户信息自动获取)
const patientInfo = { const patientInfo = {
name: user?.real_name || '?, name: user?.real_name || ',
gender: user?.gender || '未知', gender: user?.gender || '未知',
age: user?.age || 0, age: user?.age || 0,
}; };
...@@ -72,7 +76,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -72,7 +76,8 @@ const PatientPreConsultPage: React.FC = () => {
const abortRef = useRef<AbortController | null>(null); const abortRef = useRef<AbortController | null>(null);
const inputRef = useRef<any>(null); const inputRef = useRef<any>(null);
// 自动滚动到底? const scrollToBottom = useCallback(() => { //
const scrollToBottom = useCallback(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, []); }, []);
...@@ -114,7 +119,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -114,7 +119,8 @@ const PatientPreConsultPage: React.FC = () => {
setMessages(chatMessagesToDisplay(chatMsgs)); setMessages(chatMessagesToDisplay(chatMsgs));
setTurnCount(Math.floor(chatMsgs.length / 2)); setTurnCount(Math.floor(chatMsgs.length / 2));
} else { } else {
// 没有对话历史,清空消? setMessages([]); //
setMessages([]);
setTurnCount(0); setTurnCount(0);
} }
...@@ -145,13 +151,15 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -145,13 +151,15 @@ const PatientPreConsultPage: React.FC = () => {
useEffect(() => { useEffect(() => {
const initSession = async () => { const initSession = async () => {
try { try {
// 先尝试获取进行中的会? const latestRes = await preConsultApi.getLatest(); //
const latestRes = await preConsultApi.getLatest();
if (latestRes.data) { if (latestRes.data) {
restoreSession(latestRes.data); restoreSession(latestRes.data);
return; return;
} }
// 没有进行中的,获取历史列表,恢复最新的已完成会? const listRes = await preConsultApi.getList(); //
const listRes = await preConsultApi.getList();
if (listRes.data && listRes.data.length > 0) { if (listRes.data && listRes.data.length > 0) {
const latestCompleted = listRes.data.find(s => s.status === 'completed'); const latestCompleted = listRes.data.find(s => s.status === 'completed');
if (latestCompleted) { if (latestCompleted) {
...@@ -178,7 +186,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -178,7 +186,8 @@ const PatientPreConsultPage: React.FC = () => {
initSession().finally(() => setInitialLoading(false)); initSession().finally(() => setInitialLoading(false));
}, []); }, []);
// 发送用户消? const handleSend = () => { //
const handleSend = () => {
const text = inputValue.trim(); const text = inputValue.trim();
if (!text || sending || generating) return; if (!text || sending || generating) return;
...@@ -253,7 +262,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -253,7 +262,8 @@ const PatientPreConsultPage: React.FC = () => {
}); });
}; };
// 结束对话,生成分析报? const handleFinishChat = () => { //
const handleFinishChat = () => {
if (!sessionId || generating) return; if (!sessionId || generating) return;
setGenerating(true); setGenerating(true);
...@@ -292,7 +302,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -292,7 +302,8 @@ const PatientPreConsultPage: React.FC = () => {
setConsultModalOpen(true); setConsultModalOpen(true);
}; };
// 键盘快捷? const handleKeyDown = (e: React.KeyboardEvent) => { //
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleSend(); handleSend();
...@@ -387,7 +398,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -387,7 +398,7 @@ const PatientPreConsultPage: React.FC = () => {
title={ title={
<Space> <Space>
<FileTextOutlined style={{ color: '#722ed1' }} /> <FileTextOutlined style={{ color: '#722ed1' }} />
<span>AI 预问诊分析报?/span> <span>AI 预问诊分析报</span>
{generating && <LoadingOutlined style={{ color: '#722ed1' }} />} {generating && <LoadingOutlined style={{ color: '#722ed1' }} />}
{reportDone && <Tag color="success">生成完成</Tag>} {reportDone && <Tag color="success">生成完成</Tag>}
</Space> </Space>
...@@ -452,7 +463,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -452,7 +463,7 @@ const PatientPreConsultPage: React.FC = () => {
<Space split={<Divider type="vertical" />}> <Space split={<Divider type="vertical" />}>
<span>评分 {doctor.rating}</span> <span>评分 {doctor.rating}</span>
<span>问诊 {doctor.consult_count} ?/span> <span>问诊 {doctor.consult_count} ?/span>
<span style={{ color: '#fa8c16' }}>¥{(doctor.price / 100).toFixed(0)}/?/span> <span style={{ color: '#fa8c16' }}>¥{(doctor.price / 100).toFixed(0)}/次</span>
</Space> </Space>
} }
/> />
...@@ -480,9 +491,9 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -480,9 +491,9 @@ const PatientPreConsultPage: React.FC = () => {
<Space> <Space>
<RobotOutlined style={{ fontSize: 24 }} /> <RobotOutlined style={{ fontSize: 24 }} />
<div> <div>
<div style={{ fontSize: 16, fontWeight: 600 }}>AI 智能预问?/div> <div style={{ fontSize: 16, fontWeight: 600 }}>AI 智能预问诊</div>
<div style={{ fontSize: 12, opacity: 0.85 }}> <div style={{ fontSize: 12, opacity: 0.85 }}>
{sessionStatus === 'completed' ? '问诊已完? : {sessionStatus === 'completed' ? '问诊已完 :
sessionStatus === 'in_progress' ? `对话?· ?{turnCount}轮` : sessionStatus === 'in_progress' ? `对话?· ?{turnCount}轮` :
'请先填写基本信息'} '请先填写基本信息'}
</div> </div>
...@@ -560,7 +571,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -560,7 +571,8 @@ const PatientPreConsultPage: React.FC = () => {
<Button <Button
size="large" size="large"
onClick={() => { onClick={() => {
// 重置所有状? setSessionId(null); //
setSessionId(null);
setSessionStatus('new'); setSessionStatus('new');
setTurnCount(0); setTurnCount(0);
setReportContent(''); setReportContent('');
...@@ -578,7 +590,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -578,7 +590,7 @@ const PatientPreConsultPage: React.FC = () => {
}} }}
style={{ borderColor: '#722ed1', color: '#722ed1' }} style={{ borderColor: '#722ed1', color: '#722ed1' }}
> >
重新预问? </Button> 重新预问 </Button>
</Space> </Space>
</div> </div>
)} )}
...@@ -598,7 +610,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -598,7 +610,7 @@ const PatientPreConsultPage: React.FC = () => {
> >
{turnCount >= 2 && !generating && !reportDone && ( {turnCount >= 2 && !generating && !reportDone && (
<Alert <Alert
message="对话信息已足够,您可以点击右上角「结束对?· 生成报告」获取分析结果,或继续补充信息? message="对话信息已足够,您可以点击右上角「结束对?· 生成报告」获取分析结果,或继续补充信息
type="info" type="info"
showIcon showIcon
style={{ marginBottom: 12, borderRadius: 8 }} style={{ marginBottom: 12, borderRadius: 8 }}
...@@ -611,7 +623,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -611,7 +623,7 @@ const PatientPreConsultPage: React.FC = () => {
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={sending ? 'AI 正在回复?..' : '请描述您的症状或回答AI的问?.. (Enter发送,Shift+Enter换行)'} placeholder={sending ? 'AI 正在回复..' : '请描述您的症状或回答AI的问.. (Enter发送,Shift+Enter换行)'}
autoSize={{ minRows: 1, maxRows: 4 }} autoSize={{ minRows: 1, maxRows: 4 }}
disabled={sending || generating} disabled={sending || generating}
style={{ style={{
...@@ -666,7 +678,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -666,7 +678,7 @@ const PatientPreConsultPage: React.FC = () => {
> >
{historyLoading ? ( {historyLoading ? (
<div style={{ textAlign: 'center', padding: 60 }}> <div style={{ textAlign: 'center', padding: 60 }}>
<Spin tip="加载?.." /> <Spin tip="加载.." />
</div> </div>
) : historyList.length === 0 ? ( ) : historyList.length === 0 ? (
<Empty description="暂无历史对话" /> <Empty description="暂无历史对话" />
...@@ -688,7 +700,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -688,7 +700,7 @@ const PatientPreConsultPage: React.FC = () => {
title={ title={
<Space> <Space>
<Text strong ellipsis style={{ maxWidth: 200 }}> <Text strong ellipsis style={{ maxWidth: 200 }}>
{item.chief_complaint || '预问诊对?} {item.chief_complaint || '预问诊对}
</Text> </Text>
{item.id === sessionId && <Tag color="blue">当前</Tag>} {item.id === sessionId && <Tag color="blue">当前</Tag>}
</Space> </Space>
...@@ -698,15 +710,15 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -698,15 +710,15 @@ const PatientPreConsultPage: React.FC = () => {
<div style={{ fontSize: 12, color: '#666', marginBottom: 4 }}> <div style={{ fontSize: 12, color: '#666', marginBottom: 4 }}>
{item.status === 'completed' ? ( {item.status === 'completed' ? (
<> <>
<Tag color="green" style={{ marginRight: 4 }}>已完?/Tag> <Tag color="green" style={{ marginRight: 4 }}>已完</Tag>
{item.ai_department && <Tag>{item.ai_department}</Tag>} {item.ai_department && <Tag>{item.ai_department}</Tag>}
</> </>
) : ( ) : (
<Tag color="orange">进行?/Tag> <Tag color="orange">进行</Tag>
)} )}
</div> </div>
<div style={{ fontSize: 12, color: '#999' }}> <div style={{ fontSize: 12, color: '#999' }}>
{item.chat_messages ? `${Math.floor(item.chat_messages.length / 2)}轮对话` : '无对?} · {new Date(item.created_at).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} {item.chat_messages ? `${Math.floor(item.chat_messages.length / 2)}轮对话` : '无对} · {new Date(item.created_at).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
</div> </div>
</div> </div>
} }
......
...@@ -51,12 +51,12 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -51,12 +51,12 @@ const PatientPrescriptionPage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
const map: Record<string, { color: string; label: string }> = { const map: Record<string, { color: string; label: string }> = {
pending: { color: 'orange', label: '待签? }, pending: { color: 'orange', label: '待签 },
signed: { color: 'blue', label: '已签? }, signed: { color: 'blue', label: '已签 },
approved: { color: 'green', label: '已审? }, approved: { color: 'green', label: '已审 },
rejected: { color: 'red', label: '已驳? }, rejected: { color: 'red', label: '已驳 },
dispensed: { color: 'cyan', label: '已发? }, dispensed: { color: 'cyan', label: '已发 },
completed: { color: 'default', label: '已完? }, completed: { color: 'default', label: '已完 },
}; };
const s = map[status] || { color: 'default', label: status }; const s = map[status] || { color: 'default', label: status };
return <Tag color={s.color}>{s.label}</Tag>; return <Tag color={s.color}>{s.label}</Tag>;
...@@ -79,9 +79,9 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -79,9 +79,9 @@ const PatientPrescriptionPage: React.FC = () => {
title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100, title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100,
render: (v: number) => <Text strong style={{ color: '#f5222d' }}>¥{(v / 100).toFixed(2)}</Text>, render: (v: number) => <Text strong style={{ color: '#f5222d' }}>¥{(v / 100).toFixed(2)}</Text>,
}, },
{ title: '?, dataIndex: 'status', key: 'status', width: 90, render: (v: string) => getStatusTag(v) }, { title: ', dataIndex: 'status', key: 'status', width: 90, render: (v: string) => getStatusTag(v) },
{ {
title: '开方时?, dataIndex: 'created_at', key: 'created_at', width: 170, title: '开方时, dataIndex: 'created_at', key: 'created_at', width: 170,
render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-', render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-',
}, },
{ {
...@@ -98,9 +98,9 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -98,9 +98,9 @@ const PatientPrescriptionPage: React.FC = () => {
<Card size="small" className="text-center py-6"> <Card size="small" className="text-center py-6">
<FileTextOutlined className="text-4xl text-[#1890ff] mb-2" /> <FileTextOutlined className="text-4xl text-[#1890ff] mb-2" />
<div className="text-sm font-bold mb-1">电子处方</div> <div className="text-sm font-bold mb-1">电子处方</div>
<Text type="secondary" className="text-xs!">暂无处方记录,完成问诊后医生会为您开具处?/Text> <Text type="secondary" className="text-xs!">暂无处方记录,完成问诊后医生会为您开具处</Text>
<div className="mt-4"> <div className="mt-4">
<Button type="primary" size="small" onClick={() => router.push('/patient/doctors')}>去问?/Button> <Button type="primary" size="small" onClick={() => router.push('/patient/doctors')}>去问</Button>
</div> </div>
</Card> </Card>
</div> </div>
...@@ -123,26 +123,26 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -123,26 +123,26 @@ const PatientPrescriptionPage: React.FC = () => {
footer={ footer={
currentRx && ['approved', 'signed'].includes(currentRx.status) ? [ currentRx && ['approved', 'signed'].includes(currentRx.status) ? [
<Button key="buy" type="primary" icon={<ShoppingCartOutlined />} <Button key="buy" type="primary" icon={<ShoppingCartOutlined />}
onClick={() => { message.info('购药功能即将上线'); }}>一键购?/Button>, onClick={() => { message.info('购药功能即将上线'); }}>一键购</Button>,
] : null ] : null
}> }>
{currentRx && ( {currentRx && (
<div> <div>
<div style={{ textAlign: 'center', marginBottom: 16 }}> <div style={{ textAlign: 'center', marginBottom: 16 }}>
<SafetyCertificateOutlined style={{ fontSize: 32, color: '#52c41a' }} /> <SafetyCertificateOutlined style={{ fontSize: 32, color: '#52c41a' }} />
<Title level={5} style={{ margin: '8px 0 0' }}>电子处方?/Title> <Title level={5} style={{ margin: '8px 0 0' }}>电子处方</Title>
<Text type="secondary">{currentRx.prescription_no}</Text> <Text type="secondary">{currentRx.prescription_no}</Text>
</div> </div>
<Descriptions bordered size="small" column={2}> <Descriptions bordered size="small" column={2}>
<Descriptions.Item label="患者姓?>{currentRx.patient_name}</Descriptions.Item> <Descriptions.Item label="患者姓>{currentRx.patient_name}</Descriptions.Item>
<Descriptions.Item label="性别/年龄"> <Descriptions.Item label="性别/年龄">
{currentRx.patient_gender || '-'} / {currentRx.patient_age ? `${currentRx.patient_age}岁` : '-'} {currentRx.patient_gender || '-'} / {currentRx.patient_age ? `${currentRx.patient_age}岁` : '-'}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="开方医?>{currentRx.doctor_name}</Descriptions.Item> <Descriptions.Item label="开方医>{currentRx.doctor_name}</Descriptions.Item>
<Descriptions.Item label="状?>{getStatusTag(currentRx.status)}</Descriptions.Item> <Descriptions.Item label="状>{getStatusTag(currentRx.status)}</Descriptions.Item>
<Descriptions.Item label="临床诊断" span={2}>{currentRx.diagnosis}</Descriptions.Item> <Descriptions.Item label="临床诊断" span={2}>{currentRx.diagnosis}</Descriptions.Item>
{currentRx.allergy_history && ( {currentRx.allergy_history && (
<Descriptions.Item label="过敏? span={2}> <Descriptions.Item label="过敏 span={2}>
<Text type="danger">{currentRx.allergy_history}</Text> <Text type="danger">{currentRx.allergy_history}</Text>
</Descriptions.Item> </Descriptions.Item>
)} )}
......
...@@ -27,15 +27,15 @@ const PatientProfilePage: React.FC = () => { ...@@ -27,15 +27,15 @@ const PatientProfilePage: React.FC = () => {
<Avatar size={56} src={user?.avatar} icon={<UserOutlined />} /> <Avatar size={56} src={user?.avatar} icon={<UserOutlined />} />
</Col> </Col>
<Col flex="auto"> <Col flex="auto">
<h4 className="text-sm font-bold m-0 mb-0.5">{user?.real_name || '未设置姓?}</h4> <h4 className="text-sm font-bold m-0 mb-0.5">{user?.real_name || '未设置姓}</h4>
<span className="text-xs text-gray-400"><PhoneOutlined className="mr-1" />{user?.phone || '未绑定手?}</span> <span className="text-xs text-gray-400"><PhoneOutlined className="mr-1" />{user?.phone || '未绑定手}</span>
<div className="mt-1"> <div className="mt-1">
{user?.is_verified ? ( {user?.is_verified ? (
<Tag icon={<SafetyCertificateOutlined />} color="success">已实?/Tag> <Tag icon={<SafetyCertificateOutlined />} color="success">已实</Tag>
) : ( ) : (
<Tag color="warning">未认?/Tag> <Tag color="warning">未认</Tag>
)} )}
<Tag color="blue">?/Tag> <Tag color="blue"></Tag>
</div> </div>
</Col> </Col>
<Col> <Col>
...@@ -48,7 +48,7 @@ const PatientProfilePage: React.FC = () => { ...@@ -48,7 +48,7 @@ const PatientProfilePage: React.FC = () => {
<Card title={<span className="text-xs font-semibold">基本信息</span>} size="small"> <Card title={<span className="text-xs font-semibold">基本信息</span>} size="small">
<Descriptions column={2} size="small"> <Descriptions column={2} size="small">
<Descriptions.Item label="用户ID">{user?.id || '-'}</Descriptions.Item> <Descriptions.Item label="用户ID">{user?.id || '-'}</Descriptions.Item>
<Descriptions.Item label="手机?>{user?.phone || '-'}</Descriptions.Item> <Descriptions.Item label="手机>{user?.phone || '-'}</Descriptions.Item>
<Descriptions.Item label="真实姓名">{user?.real_name || '-'}</Descriptions.Item> <Descriptions.Item label="真实姓名">{user?.real_name || '-'}</Descriptions.Item>
<Descriptions.Item label="注册时间">{user?.created_at || '-'}</Descriptions.Item> <Descriptions.Item label="注册时间">{user?.created_at || '-'}</Descriptions.Item>
</Descriptions> </Descriptions>
...@@ -61,15 +61,15 @@ const PatientProfilePage: React.FC = () => { ...@@ -61,15 +61,15 @@ const PatientProfilePage: React.FC = () => {
<div className="w-10 h-10 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-2"> <div className="w-10 h-10 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-2">
<SafetyCertificateOutlined className="text-xl text-green-500" /> <SafetyCertificateOutlined className="text-xl text-green-500" />
</div> </div>
<Text strong className="text-xs!">已完成实名认?/Text> <Text strong className="text-xs!">已完成实名认</Text>
</div> </div>
) : ( ) : (
<div className="text-center py-4"> <div className="text-center py-4">
<div className="w-10 h-10 rounded-full bg-yellow-50 flex items-center justify-center mx-auto mb-2"> <div className="w-10 h-10 rounded-full bg-yellow-50 flex items-center justify-center mx-auto mb-2">
<IdcardOutlined className="text-xl text-yellow-500" /> <IdcardOutlined className="text-xl text-yellow-500" />
</div> </div>
<div className="text-xs text-gray-500 mb-2">完成实名认证后可享受完整的线上问诊服?/div> <div className="text-xs text-gray-500 mb-2">完成实名认证后可享受完整的线上问诊服</div>
<Button type="primary" size="small">去认?/Button> <Button type="primary" size="small">去认</Button>
</div> </div>
)} )}
</Card> </Card>
...@@ -81,8 +81,8 @@ const PatientProfilePage: React.FC = () => { ...@@ -81,8 +81,8 @@ const PatientProfilePage: React.FC = () => {
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/prescription')}>电子处方</Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/prescription')}>电子处方</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/health-records')}>健康档案</Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/health-records')}>健康档案</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/chronic')}>慢病管理</Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/chronic')}>慢病管理</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/pharmacy/order')}>药品配?/Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/pharmacy/order')}>药品配</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/pre-consult')}>AI预问?/Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/pre-consult')}>AI预问</Button></Col>
</Row> </Row>
</Card> </Card>
</div> </div>
......
...@@ -17,10 +17,10 @@ const { TextArea } = Input; ...@@ -17,10 +17,10 @@ const { TextArea } = Input;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '等待接诊', color: 'orange' }, pending: { text: '等待接诊', color: 'orange' },
waiting: { text: '等待?, color: 'orange' }, waiting: { text: '等待, color: 'orange' },
in_progress: { text: '问诊?, color: 'green' }, in_progress: { text: '问诊, color: 'green' },
completed: { text: '已完?, color: 'default' }, completed: { text: '已完, color: 'default' },
cancelled: { text: '已取?, color: 'red' }, cancelled: { text: '已取, color: 'red' },
}; };
const PatientTextConsultPage: React.FC = () => { const PatientTextConsultPage: React.FC = () => {
...@@ -52,7 +52,7 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -52,7 +52,7 @@ const PatientTextConsultPage: React.FC = () => {
setInputValue(''); setInputValue('');
}, },
onError: () => { onError: () => {
message.error('发送失?); message.error('发送失);
}, },
}); });
...@@ -164,7 +164,7 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -164,7 +164,7 @@ const PatientTextConsultPage: React.FC = () => {
</div> </div>
<Divider className="my-1!" /> <Divider className="my-1!" />
<div className="text-[11px] text-gray-400 mb-1">主诉</div> <div className="text-[11px] text-gray-400 mb-1">主诉</div>
<div className="text-xs bg-gray-50 rounded p-2">{consult?.chief_complaint || '未填?}</div> <div className="text-xs bg-gray-50 rounded p-2">{consult?.chief_complaint || '未填}</div>
</Card> </Card>
</Col> </Col>
...@@ -179,7 +179,7 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -179,7 +179,7 @@ const PatientTextConsultPage: React.FC = () => {
<div style={{ flex: 1, overflow: 'auto', padding: 12 }}> <div style={{ flex: 1, overflow: 'auto', padding: 12 }}>
{(!messagesData?.data || messagesData.data.length === 0) ? ( {(!messagesData?.data || messagesData.data.length === 0) ? (
<div className="text-center py-10 text-xs text-gray-400"> <div className="text-center py-10 text-xs text-gray-400">
{consult?.status === 'pending' || consult?.status === 'waiting' ? '等待医生接诊?..' : '暂无消息'} {consult?.status === 'pending' || consult?.status === 'waiting' ? '等待医生接诊..' : '暂无消息'}
</div> </div>
) : ( ) : (
<List dataSource={messagesData.data} renderItem={renderMessage} split={false} /> <List dataSource={messagesData.data} renderItem={renderMessage} split={false} />
...@@ -193,11 +193,11 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -193,11 +193,11 @@ const PatientTextConsultPage: React.FC = () => {
placeholder="输入消息..." autoSize={{ minRows: 1, maxRows: 3 }} size="small" placeholder="输入消息..." autoSize={{ minRows: 1, maxRows: 3 }} size="small"
onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} /> onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} />
<Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend} <Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend}
loading={sendMutation.isPending}>?/Button> loading={sendMutation.isPending}></Button>
</Space.Compact> </Space.Compact>
) : ( ) : (
<div className="text-center text-xs text-gray-400 py-1"> <div className="text-center text-xs text-gray-400 py-1">
{consult?.status === 'completed' ? '问诊已结? : '等待医生接诊后可发送消?} {consult?.status === 'completed' ? '问诊已结算 : '等待医生接诊后可发送消息}
</div> </div>
)} )}
</div> </div>
......
...@@ -33,7 +33,7 @@ const PatientVideoConsultPage: React.FC = () => { ...@@ -33,7 +33,7 @@ const PatientVideoConsultPage: React.FC = () => {
} = useVideoCall({ } = useVideoCall({
consultId: id || '', consultId: id || '',
onCallEnded: () => { onCallEnded: () => {
message.info('通话已结?); message.info('通话已结);
router.push('/patient/consult'); router.push('/patient/consult');
}, },
}); });
...@@ -46,7 +46,7 @@ const PatientVideoConsultPage: React.FC = () => { ...@@ -46,7 +46,7 @@ const PatientVideoConsultPage: React.FC = () => {
useEffect(() => { useEffect(() => {
joinRoom().catch((error) => { joinRoom().catch((error) => {
message.error('加入房间失败? + error.message); message.error('加入房间失败 + error.message);
}); });
return () => { return () => {
......
B'use client'; B'use client';
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