Commit 7880e29e authored by yuguo's avatar yuguo

fix

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