Commit 7880e29e authored by yuguo's avatar yuguo

fix

parent 46cbe422
...@@ -7,7 +7,12 @@ ...@@ -7,7 +7,12 @@
"Bash(taskkill:*)", "Bash(taskkill:*)",
"Bash(powershell:*)", "Bash(powershell:*)",
"Bash(curl:*)", "Bash(curl:*)",
"Bash(netstat:*)" "Bash(netstat:*)",
"Bash(cat:*)",
"Bash(node:*)",
"Bash(ls:*)",
"Bash(npx tailwindcss:*)",
"Bash(npx next:*)"
] ]
} }
} }
const fs = require('fs');
const path = require('path');
function walk(dir) {
const files = [];
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, f.name);
if (f.isDirectory()) files.push(...walk(p));
else if (f.name.endsWith('.tsx') || f.name.endsWith('.ts')) files.push(p);
}
return files;
}
const pagesDir = path.join(__dirname, 'src', 'pages');
const files = walk(pagesDir);
const corrupt = [];
for (const f of files) {
const text = fs.readFileSync(f, 'utf8');
// Detect: garbled chars (Hebrew/Arabic range from GBK mis-decode),
// or Chinese followed by ? then closing tag, or replacement chars
const hasGarbled = /[\u0200-\u06FF]{2,}/.test(text) ||
/[\u4e00-\u9fff]\?[\/<'",}\]);\s]/.test(text) ||
text.includes('\ufffd');
if (hasGarbled) {
corrupt.push(path.relative(__dirname, f).replace(/\\/g, '/'));
}
}
corrupt.sort();
console.log(JSON.stringify(corrupt, null, 2));
console.log('Total:', corrupt.length);
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;
}
// Common Chinese word completions for medical app context
// Pattern: the last char of a Chinese word was lost, replaced with ?
// We match: (Chinese chars)(?) and try to guess the missing char
const completions = {
// JSX closing tags: 中文?/tag> => 中文X</tag>
// String endings: '中文?' => '中文X'
// Generic pattern-based fixes
'评分最': '高', '问诊最': '多', '价格最': '低',
'简': '介', '未排': '班', '已认': '证', '出诊状': '态',
'暂无简': '介', '暂无数': '据', '暂无更': '多',
'医生简': '介', '医生工作': '台', '工作': '台',
'互联网医': '院', '优质医': '疗', '找医': '生',
'搜索医生、科室、疾': '病', '服务患': '者',
'皮肤': '科', '口腔': '科', '心血管内': '科',
'接诊患': '者', '开始接': '诊', '无法接诊患': '者',
'资质认': '证', '立即认': '证', '实名认': '证',
'完成资质认': '证', '医生资质认': '证',
// Common suffixes
'问诊': '量', // when followed by ? and number context
'每': '次',
'擅': '长',
// Admin pages
'用户管': '理', '科室管': '理', '问诊管': '理', '处方监': '管',
'合规报': '表', '统计报': '表', '数据统': '计', '运营大': '盘',
'系统设': '置', '药品': '库', '收入统': '计',
'待审': '核', '已审': '核', '审核通': '过', '审核拒': '绝',
'操作成': '功', '操作失': '败', '保存成': '功', '删除成': '功',
'创建成': '功', '更新成': '功', '提交成': '功',
'加载': '中', '提交': '中', '处理': '中', '审核': '中',
'确认删': '除', '确认操': '作', '确认提': '交',
'查看详': '情', '返回列': '表', '暂无内': '容',
// Doctor pages
'排班管': '理', '在线接': '诊', '在线坐': '诊',
'患者档': '案', '收入明': '细', '提现记': '录',
'账户余': '额', '本月收': '入', '总收': '入',
'好评': '率', '接诊': '量', '诊断结': '果',
'治疗方': '案', '用药建': '议', '注意事': '项',
'续方审': '核', '慢病续': '方', '慢病管': '理',
'智能辅': '助', 'AI辅': '助', 'AI预问': '诊',
'辅助诊': '断', '预问': '诊', '症状分': '析',
'问诊记': '录', '处方记': '录', '就诊记': '录',
'检查报': '告', '用药记': '录', '病历详': '情',
'基本信': '息', '患者信': '息', '医生信': '息', '个人信': '息',
'问诊详': '情', '处方详': '情', '订单详': '情',
'消息通': '知', '个人中': '心', '退出登': '录', '修改密': '码',
'在线医': '生', '推荐医': '生', '热门科': '室',
'全部科': '室', '注册医': '生', '平均响': '应',
'联系方': '式', '家庭地': '址', '身份证': '号',
'手机': '号', '真实姓': '名',
'电子处': '方', '健康档': '案',
'暂无问': '诊', '暂无处': '方', '暂无患': '者', '暂无消': '息',
'暂无记': '录', '暂无评': '价', '暂无排': '班',
'加载失': '败', '网络错': '误', '请稍后重': '试',
'登录失': '败', '注册失': '败', '发送失': '败',
'总问诊': '量', '活跃医': '生', '活跃患': '者',
'今日问': '诊', '今日收': '入', '本周问': '诊', '本月问': '诊',
'累计问': '诊', '处方总': '量', '待审处': '方',
'审核记': '录', '监管统': '计', '药品总': '数',
'库存预': '警', '入库记': '录', '出库记': '录',
'模型配': '置', '合规检': '查', '违规记': '录',
'处方合': '规', '电子签': '名', '隐私保': '护', '数据安': '全',
'主治医': '师', '副主任医': '师', '主任医': '师',
'执业证': '书', '职称证': '书', '学历证': '书',
'医师资': '格', '工作证': '明', '机构信': '息',
'药品配': '送', '支付订': '单', '药品管': '理',
'待支': '付', '已支': '付', '已取': '消', '已完': '成',
'进行': '中', '待接': '诊', '已接': '诊',
'患者列': '表', '消息列': '表', '药品列': '表',
'处方列': '表', '订单列': '表', '医生列': '表',
'开始问': '诊', '结束问': '诊', '发送消': '息',
'上传文': '件', '选择文': '件', '请输': '入', '请选': '择',
'复诊预': '约', '在线咨': '询', '视频问': '诊', '图文问': '诊',
'预约挂': '号', '门诊排': '班', '接诊设': '置',
'自动接': '诊', '手动接': '诊', '最大接诊': '数',
'挂号': '费', '问诊': '费', '处方': '费',
'回复时': '间', '平均回复时': '间', '满意': '度',
'未认': '证', '认证审': '核',
'视频通': '话', '图文对': '话', '文字问': '诊',
'请先完成资质认': '证',
'尚未完成医生资质认': '证',
'相关资料': ',',
'开始接诊': '。',
'请描述您的症': '状', '请详细描述您的症': '状',
'症状描': '述', '主': '诉',
'就诊': '人', '性': '别', '年': '龄',
'男': '性', '女': '性',
'更多排': '班', '更': '多',
'正在进行': '中', '等待接': '诊', '等待中': '...',
'候诊': '中', '已结': '束', '已过': '期', '已失': '效',
'今日接': '诊', '累计接': '诊', '累计患': '者',
'累计收': '入', '月收': '入', '周收': '入', '日收': '入',
'我的患': '者', '我的问': '诊', '我的处': '方', '我的收': '入',
'我的排': '班', '我的评': '价', '我的认': '证',
'认证状': '态', '认证信': '息',
'等待审': '核', '审核': '中',
'上传执业证': '书', '上传资格证': '书',
'执业范': '围', '执业类': '别', '执业地': '点',
'所在医': '院', '所在科': '室',
'从业年': '限', '擅长方': '向',
'最新公': '告', '近期问': '诊', '待处': '理',
'随访计': '划',
'请稍': '候', '请等': '待',
'未完': '成', '未开': '始', '未提': '交',
'编辑资': '料', '修改信': '息',
'管理': '员', '管理后': '台',
'医生审': '核', '医生管': '理',
'患者管': '理', '部门管': '理',
'诊室管': '理', '问诊统': '计',
'处方审': '核', '处方管': '理',
'药房管': '理', '合规管': '理',
'AI配': '置', '知识库管': '理',
'配送管': '理', '物流管': '理',
'在线': '中', '离线': '中',
'搜索患': '者', '搜索医': '生', '搜索用': '户',
'添加用': '户', '添加医': '生', '添加科': '室',
'禁用账': '号', '启用账': '号', '重置密': '码',
'角色管': '理', '权限管': '理', '日志管': '理',
'门诊量统': '计', '收入趋': '势',
'问诊量趋': '势', '用户增长趋': '势',
'科室问诊分': '布', '医生问诊排': '行',
};
function fixContent(text) {
let result = text;
let changed = false;
// Pattern 1: Chinese?/tag> - missing char before closing JSX tag
result = result.replace(/([\u4e00-\u9fff])\?\/([\w]+>)/g, (match, lastChar, closingTag) => {
// Try to find the context before to guess the missing char
const idx = result.indexOf(match);
const before = result.substring(Math.max(0, idx - 30), idx + 1);
for (const [prefix, suffix] of Object.entries(completions)) {
if (before.endsWith(prefix.slice(-1)) && prefix.length > 1) {
const fullPrefix = before.slice(-(prefix.length));
if (fullPrefix === prefix) {
changed = true;
return lastChar + suffix + '</' + closingTag;
}
}
}
// Default: just close the tag properly
changed = true;
return lastChar + '</' + closingTag;
});
// Pattern 2: '中文?' or "中文?" - missing char in string literal
result = result.replace(/([\u4e00-\u9fff])\?(['",})\]])/g, (match, lastChar, endChar) => {
const idx = result.indexOf(match);
const before = result.substring(Math.max(0, idx - 30), idx + 1);
for (const [prefix, suffix] of Object.entries(completions)) {
if (before.endsWith(prefix.slice(-1)) && prefix.length > 1) {
const fullPrefix = before.slice(-(prefix.length));
if (fullPrefix === prefix) {
changed = true;
return lastChar + suffix + endChar;
}
}
}
// If no match found, just remove the ?
changed = true;
return lastChar + endChar;
});
// Pattern 3: 中文?} - in JSX expressions
result = result.replace(/([\u4e00-\u9fff])\?\}/g, (match, lastChar) => {
const idx = result.indexOf(match);
const before = result.substring(Math.max(0, idx - 30), idx + 1);
for (const [prefix, suffix] of Object.entries(completions)) {
if (before.endsWith(prefix.slice(-1)) && prefix.length > 1) {
const fullPrefix = before.slice(-(prefix.length));
if (fullPrefix === prefix) {
changed = true;
return lastChar + suffix + '}';
}
}
}
changed = true;
return lastChar + '}';
});
// Pattern 4: Garbled chars (Hebrew/Arabic range from double-encoding)
// These are sequences of chars in U+0200-U+06FF range that represent garbled Chinese
result = result.replace(/[\u0200-\u06FF]{2,}/g, (match) => {
changed = true;
return ''; // Remove garbled sequences entirely
});
// Pattern 5: Single garbled char surrounded by ASCII/CJK
result = result.replace(/([\u4e00-\u9fff\w])[\u0200-\u06FF]([\u4e00-\u9fff\w])/g, (match, before, after) => {
changed = true;
return before + after;
});
// Clean up any remaining replacement characters
if (result.includes('\ufffd')) {
result = result.replace(/\ufffd/g, '');
changed = true;
}
// Fix empty JSX tags that result from cleanup: <>< should not happen
// Fix double-space from removed chars
result = result.replace(/ +/g, (m) => {
if (m.length > 2) { changed = true; return ' '; }
return m;
});
return { text: result, changed };
}
const pagesDir = path.join(__dirname, 'src', 'pages');
const files = walk(pagesDir);
let fixedCount = 0;
const stillBroken = [];
for (const f of files) {
const original = fs.readFileSync(f, 'utf8');
const { text, changed } = fixContent(original);
if (changed) {
fs.writeFileSync(f, text, 'utf8');
fixedCount++;
// Check if still has issues
const remaining = (text.match(/[\u4e00-\u9fff]\?[\/'"<,}\]);\s]/g) || []).length +
(text.match(/[\u0200-\u06FF]{2,}/g) || []).length;
if (remaining > 0) {
stillBroken.push({ file: path.relative(__dirname, f), remaining });
}
console.log('Fixed: ' + path.relative(__dirname, f));
}
}
console.log('\nTotal fixed: ' + fixedCount);
if (stillBroken.length > 0) {
console.log('\nStill have issues:');
stillBroken.forEach(b => console.log(' ' + b.file + ' (' + b.remaining + ' issues)'));
}
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;
}
// Complete GBK garbled -> correct Chinese mapping
// These are UTF-8 strings that were read as GBK then written back as UTF-8
const m = {
// ===== Common phrases =====
'鑾峰彇': '获取', '澶辫触': '失败', '鍒楄〃': '列表',
'鎿嶄綔': '操作', '鎴愬姛': '成功', '鍒犻櫎': '删除',
'缂栬緫': '编辑', '娣诲姞': '添加', '淇濆瓨': '保存',
'纭': '确', '璇疯緭鍏': '请输入', '璇烽€夋嫨': '请选择',
'鎼滅储': '搜索', '鎻愪氦': '提交', '鍙栨秷': '取消',
// ===== Admin Departments =====
'绉戝鍒楄〃': '科室列表', '绉戝鍚嶇О': '科室名称',
'绉戝绠$悊': '科室管理', '绉戝鏇存柊': '科室更新',
'绉戝鍒涘缓': '科室创建', '娣诲姞绉戝': '添加科室',
'缂栬緫绉戝': '编辑科室', '鎺掑簭': '排序',
'鍥炬爣': '图标', '鎺掑簭鍙': '排序号',
'璇疯緭鍏ョ瀹ゅ悕绉': '请输入科室名称',
'璇疯緭鍏moji鍥炬爣': '请输入Emoji图标',
'纭鍒犻櫎': '确认删除',
'纭畾瑕佸垹闄ょ瀹': '确定要删除科室',
'鍚楋紵璇ユ搷浣滀笉鍙仮澶嶃€': '吗?该操作不可恢复。',
'宸插垹闄': '已删除',
'鍒犻櫎澶辫触': '删除失败',
'鑾峰彇绉戝鍒楄〃澶辫触': '获取科室列表失败',
'馃彞': '🏥',
// ===== Admin Pharmacy =====
'鑽搧': '药品', '鑽搧鍚嶇О': '药品名称',
'鑽搧搴撶鐞': '药品库管理', '鑽搧鎬绘暟': '药品总数',
'鑽搧宸插垹闄': '药品已删除', '鑽搧宸叉洿鏂': '药品已更新',
'鑽搧宸叉坊鍔': '药品已添加', '娣诲姞鑽搧': '添加药品',
'缂栬緫鑽搧': '编辑药品', '鎼滅储鑽搧': '搜索药品',
'搴撳瓨': '库存', '搴撳瓨鍏呰冻': '库存充足',
'搴撳瓨棰勮': '库存预警', '搴撳瓨鏁伴噺': '库存数量',
'搴撳瓨棰勮绾': '库存预警线', '缂鸿揣': '缺货',
'缂鸿揣鑽搧': '缺货药品',
'閫氱敤鍚': '通用名', '瑙勬牸': '规格',
'绫诲埆': '类别', '鍘傚晢': '厂商',
'浠锋牸': '价格', '鍗曚綅': '单位', '鍗曚环': '单价',
'鐘舵€': '状态', '鐘舵€?': '状态',
'鎶楃敓绱': '抗生素', '瑙g儹闀囩棝': '解热镇痛',
'闄嶇硸鑽': '降糖药', '闄嶅帇鑽': '降压药',
'娑堝寲绯荤粺': '消化系统', '蹇冭绠': '心血管',
'鍛煎惛绯荤粺': '呼吸系统', '缁寸敓绱': '维生素',
'涓垚鑽': '中成药', '鍏朵粬': '其他',
'纭畾鍒犻櫎璇ヨ嵂鍝侊紵': '确定删除该药品?',
'璇疯緭鍏ヨ嵂鍝佸悕绉': '请输入药品名称',
'璇疯緭鍏ラ€氱敤鍚': '请输入通用名',
'璇疯緭鍏ヨ鏍': '请输入规格',
'閫夋嫨绫诲埆': '选择类别',
'鐢熶骇鍘傚晢': '生产厂商', '璇疯緭鍏ュ巶鍟': '请输入厂商',
'閫夋嫨鍗曚綅': '选择单位',
'鐩': '盒', '鐡': '瓶', '琚': '袋', '鏀': '支',
'浠锋牸锛堝厓锛': '价格(元)',
'璇疯緭鍏ヤ环鏍': '请输入价格',
'浣庝簬姝ゆ暟閲忛璀': '低于此数量预警',
'鍏呰冻': '充足', '棰勮': '预警',
'鑽搧淇℃伅': '药品信息',
'褰撳墠鏈': '当前有',
'绉嶈嵂鍝佸簱瀛樹笉瓒筹紝': '种药品库存不足,',
'绉嶈嵂鍝佺己璐э紝璇峰強无惰ˉ璐': '种药品缺货,请及时补货',
'鍏': '共', '绉': '种',
// ===== Doctor AIAssist =====
'杈呭姪璇婃柇': '辅助诊断', 'AI 杈呭姪璇婃柇': 'AI 辅助诊断',
'AI 杈呭姪璇婃柇浠呬緵鍙傝€冿紝鏈€缁堣瘖鏂浠ュ尰鐢熶复搴婂垽鏂负鍑': 'AI 辅助诊断仅供参考,最终诊断以医生临床判断为准',
'鐥囩姸鎻忚堪': '症状描述',
'璇疯緭鍏ユ偅鑰呯殑涓昏瘔鍜岀棁鐘舵弿杩..': '请输入患者的主诉和症状描述..',
'AI 鏅鸿兘鍒嗘瀽': 'AI 智能分析',
'AI 姝e湪鍒嗘瀽涓..': 'AI 正在分析中..',
'闉村埆璇婃柇': '鉴别诊断', '鐩镐技鐥呬緥': '相似病例',
'鐢ㄨ嵂寤鸿': '用药建议', '鍖归厤搴': '匹配度',
'鎽樿锛': '摘要:', '璇婃柇锛': '诊断:', '娌荤枟锛': '治疗:',
'娣诲姞澶勬柟': '添加处方',
'璇疯緭鍏ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽': '请输入症状描述后进行分析',
// AI mock data
'鎬ユ€т笂鍛煎惛閬撴劅鏌': '急性上呼吸道感染',
'鐢辩梾姣掑紩璧风殑涓婂懠鍚搁亾鐐庣棁锛屽父瑙佺棁鐘朵负榧诲銆佹祦娑曘€佸捊鐥涖€佸挸鍡界瓑': '由病毒引起的上呼吸道炎症,常见症状为鼻塞、流涕、咽痛、咳嗽等',
'鎬ユ€ф敮姘旂鐐': '急性支气管炎',
'鏀皵绠¢粡鑶滅殑鎬ユ€х値鐥囷紝甯哥户鍙戜簬涓婂懠鍚搁亾鎰熸煋': '支气管黏膜的急性炎症,常继发于上呼吸道感染',
'杩囨晱鎬ч蓟鐐': '过敏性鼻炎',
'榧婚粡鑶滈潪鎰熸煋鎬х値鎬х柧鐥咃紝浠ラ樀鍙戞€у柗鍤忋€佹竻姘存牱榧绘稌绛変负鐗瑰緛': '鼻黏膜非感染性炎性疾病,以阵发性喷嚏、清水样鼻涕等为特征',
'鎮h€咃紝鐢凤紝35宀侊紝鍜冲椊銆佹祦娑?澶╋紝浣撴俯37.8掳C': '患者,男,35岁,咳嗽、流涕5天,体温37.8°C',
'瀵圭棁娌荤枟锛屽彛鏈嶅竷娲涜姮閫€鐑紝姘ㄦ捍绱㈠寲鐥': '对症治疗,口服布洛芬退热,氨溴索化痰',
'鎮h€咃紝濂筹紝28宀侊紝鍜界棝銆侀蓟濉?澶╋紝浼磋交寰挸鍡': '患者,女,28岁,咽痛、鼻塞3天,伴轻微咳嗽',
'鍙f湇娓呭紑鐏甸绮掞紝鍚湇瑗跨摐闇滃惈鐗': '口服清开灵颗粒,含服西瓜霜含片',
'鎮h€咃紝鐢凤紝42宀侊紝鍜冲椊1鍛紝鐥版恫榛勮壊': '患者,男,42岁,咳嗽1周,痰液黄色',
'鍙f湇闃胯帿瑗挎灄+姘ㄦ捍绱紝闆惧寲鍚稿叆甯冨湴濂堝痉': '口服阿莫西林+氨溴索,雾化吸入布地奈德',
'甯冩礇鑺紦閲婅兌鍥': '布洛芬缓释胶囊',
'0.3g/绮': '0.3g/粒', '鍙f湇': '口服',
'姣忔1绮掞紝姣忔棩2娆': '每次1粒,每日2次',
'鑳冩簝鐤℃偅鑰呮厧鐢': '胃溃疡患者慎用',
'姘ㄦ捍绱㈠彛鏈嶆恫': '氨溴索口服液',
'100ml/鐡': '100ml/瓶',
'姣忔10ml锛屾瘡无娆': '每次10ml,每日3次',
'瀛曟棭鏈熺鐢': '孕早期禁用',
'瀵逛箼閰版皒鍩洪厷鐗': '对乙酰氨基酚片',
'0.5g/鐗': '0.5g/片',
'姣忔1鐗囷紝鍙戠儹无舵湇鐢紝闂撮殧4-6灏忔椂': '每次1片,发热时服用,间隔4-6小时',
'鑲濆姛鑳戒笉鍏ㄨ€呮厧鐢': '肝功能不全者慎用',
// TODO comments
'璋冪敤鍚庣 AI 璇婃柇鎺ュ彛': '调用后端 AI 诊断接口',
'妯℃嫙鏁版嵁': '模拟数据',
// ===== Doctor PatientList =====
'鎮h€呭垪琛': '患者列表', '鎮h€?': '患者', '鎮h€': '患者',
'绉抈': '秒', '鍒嗛挓': '分钟', '灏忔椂': '小时',
'鎷掔粷': '拒绝',
'鎸夐挳琚偣鍑': '按钮被点击',
'鍑芥暟:': '函数:',
'涓嶆槸涓€涓嚱鏁': '不是一个函数',
'鎺ヨ瘖鎸夐挳琚偣鍑': '接诊按钮被点击',
'鎷掔粷鎸夐挳琚偣鍑': '拒绝按钮被点击',
'绛夊緟 ': '等待 ',
'待接诊婃偅鑰呮樉绀烘帴璇?鎷掔粷鎸夐挳': '待接诊患者显示接诊/拒绝按钮',
// ===== Doctor WaitingQueue =====
'鍒氬垰': '刚刚', '鍒哷': '分',
'鏈~鍐欎富璇': '未填写主诉',
'鎷掕瘖': '拒诊', '杩涜涓棶璇': '进行中问诊',
'绛夊緟鎮h€?': '等待患者', '宸插畬鎴愰棶璇': '已完成问诊',
'杩涜涓': '进行中',
// ===== Doctor Schedule =====
'鎺掔彮绠$悊': '排班管理', '鎺掔彮': '排班',
'娣诲姞鎺掔彮': '添加排班', '鍒犻櫎鎺掔彮': '删除排班',
'涓椂娈': '个时段',
'纭畾瑕佸垹闄ゆ鎺掔彮无舵鍚楋紵': '确定要删除此排班时段吗?',
'淇濆瓨鎺掔彮': '保存排班',
'浠嶢PI鑾峰彇鐪熷疄鏁版嵁': '从API获取真实数据',
'璋冪敤API淇濆瓨鎺掔彮': '调用API保存排班',
'璋冪敤API鍒犻櫎鎺掔彮': '调用API删除排班',
'鏈圖D无': '月DD日',
'褰撴棩': '当日',
'无堕棿娈': '时间段',
'璇烽€夋嫨无堕棿娈': '请选择时间段',
'鏈€澶ф帴璇婃暟': '最大接诊数',
'璇疯緭鍏ユ渶澶ф帴璇婃暟': '请输入最大接诊数',
'鏈€澶?': '最大 ',
'鍓╀綑': '剩余',
'浜': '人', '鍙': '号',
// ===== Doctor Prescription =====
'寮€鍏峰鏂': '开具处方', '澶勬柟': '处方',
'澶勬柟宸茬鍚嶅苟鎻愪氦': '处方已签名并提交',
'鎻愪氦澶勬柟澶辫触': '提交处方失败',
'璇ヨ嵂鍝佸凡娣诲姞': '该药品已添加',
'璇疯嚦灏戞坊鍔犱竴绉嶈嵂鍝': '请至少添加一种药品',
'鐢ㄦ硶': '用法', '鍗曟鍓傞噺': '单次剂量',
'棰戞': '频次', '澶╂暟': '天数', '鏁伴噺': '数量',
'澶栫敤': '外用',
'闈欒剦娉ㄥ皠': '静脉注射', '鑲岃倝娉ㄥ皠': '肌肉注射',
'闆惧寲鍚稿叆': '雾化吸入',
'姣忔棩1娆': '每日1次', '姣忔棩2娆': '每日2次',
'姣忔棩3娆': '每日3次', '蹇呰无': '必要时',
'鐫″墠': '睡前',
'濮撳悕': '姓名', '鎬у埆': '性别',
'鐢': '男', '濂': '女', '骞撮緞': '年龄', '宀': '岁',
'璇婃柇': '诊断', '杩囨晱': '过敏', '杩囨晱鍙': '过敏史',
'鍖诲槺澶囨敞...': '医嘱备注...',
'鎵撳嵃鍔熻兘寮€鍙戜腑': '打印功能开发中',
'鎵撳嵃': '打印', '绛惧悕鎻愪氦': '签名提交',
'CA 绛惧悕纭': 'CA 签名确认',
'纭绛惧悕': '确认签名',
'纭浣跨敤 CA 鏁板瓧璇佷功绛剧讲姝ゅ鏂癸紵': '确认使用 CA 数字证书签署此处方?',
'绛剧讲鍚庡鏂瑰皢鍏锋湁娉曞緥鏁堝姏': '签署后处方将具有法律效力',
'鑽搧锛': '药品:', '閲戦锛': '金额:',
'鍚堣锛': '合计:',
'璇锋悳绱㈠苟娣诲姞鑽搧': '请搜索并添加药品',
'鎼滅储鑽搧...': '搜索药品...',
'搴撳瓨:': '库存:',
'濡 1绮': '如 1粒', '濡 0.5g脳24绮?鐩': '如 0.5g×24粒/盒',
// ===== Generic =====
'鍔熻兘寮€鍙戜腑锛屾暚璇锋湡寰': '功能开发中,敬请期待',
'璇疯緭鍏': '请输入',
};
function fixFile(filePath) {
let text = fs.readFileSync(filePath, 'utf8');
const original = text;
// Sort keys by length descending (longest first)
const sortedKeys = Object.keys(m).sort((a, b) => b.length - a.length);
for (const key of sortedKeys) {
if (text.includes(key)) {
text = text.split(key).join(m[key]);
}
}
// Fix remaining single-char GBK + ? patterns
const singleFixes = {
'患': '者', '状': '态', '医': '院', '机': '构', '姓': '名',
'证': '书', '认': '证', '审': '核', '简': '介', '拒': '绝',
'禁': '用', '码': '码', '号': '码', '理': '员', '诊': '断',
'血': '压', '糖': '化', '药': '物', '签': '名', '完': '成',
'取': '消', '行': '中', '支': '付', '接': '诊', '收': '入',
'结': '算', '发': '送', '败': '了', '处': '方', '统': '计',
'记': '录', '调': '整', '待': '处', '最': '高', '症': '状',
'开': '始', '除': '外', '一': '次', '数': '量', '新': '增',
'更': '新', '报': '告', '敏': '感', '金': '额', '息': '。',
'跃': '量', '常': '规', '保': '险', '食': '物', '病': '史',
'问': '诊', '天': '数', '药': '物', '好': '评', '制': '定',
'过': '敏', '乏': '力', '周': '期', '史': '记', '方': '案',
'照': '片', '核': '实', '缩': '压', '张': '压', '坦': '类',
'原': '件', '释': '放', '平': '均', '胶': '囊', '脲': '类',
};
text = text.replace(/([\u4e00-\u9fff])\?([,}'"\s\r\n\/)<>;:\]])/g, (match, ch, after) => {
if (singleFixes[ch]) return ch + singleFixes[ch] + after;
return ch + after;
});
// Fix Chinese?/tag> patterns
text = text.replace(/([\u4e00-\u9fff]+)\?\/([\w]+>)/g, (match, chinese, tag) => {
const fixes = {
'工作': '台', '简': '介', '排': '班', '认': '证', '患': '者',
'配送': '中', '跟踪': '中', '态': '度', '诊': '断',
};
for (const [k, v] of Object.entries(fixes).sort((a, b) => b[0].length - a[0].length)) {
if (chinese.endsWith(k)) return chinese + v + '</' + tag;
}
return chinese + '</' + tag;
});
// Fix remaining isolated garbled sequences
text = text.replace(/[\u0200-\u06FF]{3,}/g, '');
// Fix router.push(-1) -> router.back()
text = text.replace(/router\.push\(-1\)/g, 'router.back()');
// Fix useSearchParams
text = text.replace(/const \[searchParams\] = useSearchParams\(\)/g, 'const searchParams = useSearchParams()');
if (text !== original) {
fs.writeFileSync(filePath, text, 'utf8');
return true;
}
return false;
}
const pagesDir = path.join(__dirname, 'src', 'pages');
const files = walk(pagesDir);
let fixedCount = 0;
for (const f of files) {
if (fixFile(f)) {
fixedCount++;
console.log('Fixed: ' + path.relative(__dirname, f));
}
}
console.log('\nTotal fixed: ' + fixedCount);
// Verify
let broken = 0;
for (const f of files) {
const text = fs.readFileSync(f, 'utf8');
const p1 = text.match(/[\u4e00-\u9fff]\?[,}'"\s\/)<>;]/g) || [];
const p2 = text.match(/[\u0200-\u06FF]{3,}/g) || [];
// Check for common GBK garbled chars
const gbkChars = ['鑾峰', '宸插', '鍒犻', '缂栬', '娣诲', '鎮h', '鎺掑', '绉戝', '鑽搧', '鏆傛', '璇疯', '鎼滅'];
let p3 = 0;
for (const g of gbkChars) {
if (text.includes(g)) p3++;
}
if (p1.length || p2.length || p3) {
broken++;
console.log('STILL BROKEN: ' + path.relative(__dirname, f) +
' (?' + p1.length + ' garbled:' + p2.length + ' gbk:' + p3 + ')');
if (p1.length) p1.slice(0, 3).forEach(x => console.log(' ?: ' + JSON.stringify(x)));
}
}
console.log('\nStill broken: ' + broken);
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>
);
}
...@@ -68,7 +68,7 @@ const { TextArea } = Input; ...@@ -68,7 +68,7 @@ const { TextArea } = Input;
local: { local: {
baseURL: 'http://localhost:11434', baseURL: 'http://localhost:11434',
models: [ models: [
{ label: '自定义模?, value: 'custom' }, { label: '自定义模, value: 'custom' },
], ],
}, },
}; };
...@@ -84,9 +84,9 @@ interface SafeWord { ...@@ -84,9 +84,9 @@ interface SafeWord {
const mockSafeWords: SafeWord[] = [ const mockSafeWords: SafeWord[] = [
{ id: '1', word: '自杀', category: '心理危机', action: 'block' }, { id: '1', word: '自杀', category: '心理危机', action: 'block' },
{ id: '2', word: '自残', category: '心理危机', action: 'block' }, { id: '2', word: '自残', category: '心理危机', action: 'block' },
{ id: '3', word: '处方药购?, category: '违规用药', action: 'warn' }, { id: '3', word: '处方药购, category: '违规用药', action: 'warn' },
{ id: '4', word: '毒品', category: '违禁?, action: 'block' }, { id: '4', word: '毒品', category: '违禁用, action: 'block' },
{ id: '5', word: '未经验证的偏?, category: '不实信息', action: 'replace', replacement: '请咨询专业医生获取科学的治疗建议' }, { id: '5', word: '未经验证的偏, category: '不实信息', action: 'replace', replacement: '请咨询专业医生获取科学的治疗建议' },
]; ];
const AdminAIConfigPage: React.FC = () => { const AdminAIConfigPage: React.FC = () => {
...@@ -140,7 +140,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -140,7 +140,8 @@ const AdminAIConfigPage: React.FC = () => {
setUsageStats(statsRes.data); setUsageStats(statsRes.data);
} catch (error) { } catch (error) {
console.error('加载AI配置失败:', error); console.error('加载AI配置失败:', error);
// 使用默认? modelForm.setFieldsValue({ //
modelForm.setFieldsValue({
provider: 'deepseek', provider: 'deepseek',
model: 'deepseek-chat', model: 'deepseek-chat',
base_url: 'https://api.deepseek.com', base_url: 'https://api.deepseek.com',
...@@ -182,7 +183,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -182,7 +183,8 @@ const AdminAIConfigPage: React.FC = () => {
fetchAILogs(); fetchAILogs();
}, [fetchAILogs]); }, [fetchAILogs]);
// 切换提供商时自动更新 Base URL 和模? const handleProviderChange = (provider: string) => { //
const handleProviderChange = (provider: string) => {
setCurrentProvider(provider); setCurrentProvider(provider);
const defaults = providerDefaults[provider]; const defaults = providerDefaults[provider];
if (defaults) { if (defaults) {
...@@ -201,7 +203,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -201,7 +203,8 @@ const AdminAIConfigPage: React.FC = () => {
await adminApi.saveAIConfig({ await adminApi.saveAIConfig({
provider: values.provider, provider: values.provider,
model: values.model, model: values.model,
api_key: values.api_key || '', // 空则后端保留原? base_url: values.base_url, api_key: values.api_key || '', //
base_url: values.base_url,
max_tokens: values.max_tokens, max_tokens: values.max_tokens,
temperature: values.temperature, temperature: values.temperature,
enable_ai: values.enable_ai, enable_ai: values.enable_ai,
...@@ -211,7 +214,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -211,7 +214,7 @@ const AdminAIConfigPage: React.FC = () => {
modelForm.setFieldValue('api_key', ''); modelForm.setFieldValue('api_key', '');
} catch (error: any) { } catch (error: any) {
if (error?.errorFields) return; if (error?.errorFields) return;
message.error('保存失败: ' + (error?.message || '请稍后重?)); message.error('保存失败: ' + (error?.message || '请稍后重试));
} finally { } finally {
setSaving(false); setSaving(false);
} }
...@@ -284,7 +287,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -284,7 +287,7 @@ const AdminAIConfigPage: React.FC = () => {
content: values.content, content: values.content,
status: values.status || 'active', status: values.status || 'active',
}); });
message.success('Prompt 模板已创?); message.success('Prompt 模板已创);
} else if (editingTemplate) { } else if (editingTemplate) {
await adminApi.updatePromptTemplate(editingTemplate.id, { await adminApi.updatePromptTemplate(editingTemplate.id, {
template_key: values.template_key, template_key: values.template_key,
...@@ -293,7 +296,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -293,7 +296,7 @@ const AdminAIConfigPage: React.FC = () => {
content: values.content, content: values.content,
status: values.status, status: values.status,
}); });
message.success('Prompt 模板已更?); message.success('Prompt 模板已更);
} }
setTemplateModalVisible(false); setTemplateModalVisible(false);
fetchTemplates(); fetchTemplates();
...@@ -323,8 +326,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -323,8 +326,8 @@ const AdminAIConfigPage: React.FC = () => {
title: '应用场景', dataIndex: 'scene', key: 'scene', width: 130, title: '应用场景', dataIndex: 'scene', key: 'scene', width: 130,
render: (v: string) => { render: (v: string) => {
const sceneLabels: Record<string, string> = { const sceneLabels: Record<string, string> = {
pre_consult_chat: '预问诊对?, pre_consult_chat: '预问诊对,
pre_consult_analysis: '预问诊报?, pre_consult_analysis: '预问诊报告,
consult_diagnosis: '鉴别诊断', consult_diagnosis: '鉴别诊断',
consult_medication: '用药建议', consult_medication: '用药建议',
prescription_review: '处方审核', prescription_review: '处方审核',
...@@ -337,7 +340,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -337,7 +340,7 @@ const AdminAIConfigPage: React.FC = () => {
render: (v: string) => <Text type="secondary">{v && v.length > 50 ? v.substring(0, 50) + '...' : v}</Text>, render: (v: string) => <Text type="secondary">{v && v.length > 50 ? v.substring(0, 50) + '...' : v}</Text>,
}, },
{ {
title: '状?, dataIndex: 'status', key: 'status', width: 80, title: '状态, dataIndex: 'status', key: 'status', width: 80,
render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v === 'active' ? '启用' : '停用'}</Tag>, render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v === 'active' ? '启用' : '停用'}</Tag>,
}, },
{ title: '更新时间', dataIndex: 'updated_at', key: 'updated_at', width: 170, render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-' }, { title: '更新时间', dataIndex: 'updated_at', key: 'updated_at', width: 170, render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-' },
...@@ -351,8 +354,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -351,8 +354,8 @@ const AdminAIConfigPage: React.FC = () => {
await adminApi.activatePromptTemplate(record.id); await adminApi.activatePromptTemplate(record.id);
message.success(`已激活模板?{record.name}」,同场景其他模板已自动停用`); message.success(`已激活模板?{record.name}」,同场景其他模板已自动停用`);
fetchTemplates(); fetchTemplates();
} catch { message.error('激活失?); } } catch { message.error('激活失); }
}}>激?/Button> }}>激</Button>
)} )}
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEditTemplate(record)}>编辑</Button> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEditTemplate(record)}>编辑</Button>
<Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDeleteTemplate(record)}>删除</Button> <Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDeleteTemplate(record)}>删除</Button>
...@@ -362,7 +365,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -362,7 +365,7 @@ const AdminAIConfigPage: React.FC = () => {
]; ];
const safeWordColumns = [ const safeWordColumns = [
{ title: '关键?, dataIndex: 'word', key: 'word', render: (v: string) => <Text strong>{v}</Text> }, { title: '关键, dataIndex: 'word', key: 'word', render: (v: string) => <Text strong>{v}</Text> },
{ title: '分类', dataIndex: 'category', key: 'category', width: 120, render: (v: string) => <Tag>{v}</Tag> }, { title: '分类', dataIndex: 'category', key: 'category', width: 120, render: (v: string) => <Tag>{v}</Tag> },
{ {
title: '触发动作', dataIndex: 'action', key: 'action', width: 120, title: '触发动作', dataIndex: 'action', key: 'action', width: 120,
...@@ -417,7 +420,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -417,7 +420,7 @@ const AdminAIConfigPage: React.FC = () => {
<Col span={16}> <Col span={16}>
<Card title="AI 模型配置" style={{ borderRadius: 12 }}> <Card title="AI 模型配置" style={{ borderRadius: 12 }}>
<Alert <Alert
message="此配置将全局生效,影响所?AI 功能(AI预问诊、辅助诊断、处方审核等? message="此配置将全局生效,影响所?AI 功能(AI预问诊、辅助诊断、处方审核等
type="info" type="info"
showIcon showIcon
style={{ marginBottom: 20 }} style={{ marginBottom: 20 }}
...@@ -425,7 +428,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -425,7 +428,7 @@ const AdminAIConfigPage: React.FC = () => {
<Form form={modelForm} layout="vertical"> <Form form={modelForm} layout="vertical">
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="模型提供? name="provider" rules={[{ required: true }]}> <Form.Item label="模型提供 name="provider" rules={[{ required: true }]}>
<Select <Select
options={providerOptions} options={providerOptions}
onChange={handleProviderChange} onChange={handleProviderChange}
...@@ -467,7 +470,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -467,7 +470,7 @@ const AdminAIConfigPage: React.FC = () => {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Item label="启用 AI 辅助" name="enable_ai" valuePropName="checked"> <Form.Item label="启用 AI 辅助" name="enable_ai" valuePropName="checked">
<Switch checkedChildren="开? unCheckedChildren="关闭" /> <Switch checkedChildren="开始 unCheckedChildren="关闭" />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
...@@ -479,7 +482,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -479,7 +482,7 @@ const AdminAIConfigPage: React.FC = () => {
size="large" size="large"
style={{ minWidth: 140 }} style={{ minWidth: 140 }}
> >
{saving ? '保存?..' : '保存配置'} {saving ? '保存..' : '保存配置'}
</Button> </Button>
</Form> </Form>
</Card> </Card>
...@@ -493,7 +496,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -493,7 +496,7 @@ const AdminAIConfigPage: React.FC = () => {
</div> </div>
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<div> <div>
<Text type="secondary">本月 Token 消?/Text> <Text type="secondary">本月 Token 消</Text>
<Title level={3} style={{ margin: 0 }}>{formatTokens(usageStats?.month_tokens ?? 0)}</Title> <Title level={3} style={{ margin: 0 }}>{formatTokens(usageStats?.month_tokens ?? 0)}</Title>
</div> </div>
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
...@@ -506,7 +509,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -506,7 +509,7 @@ const AdminAIConfigPage: React.FC = () => {
<Card title="配置说明" style={{ borderRadius: 12, marginTop: 16 }} size="small"> <Card title="配置说明" style={{ borderRadius: 12, marginTop: 16 }} size="small">
<div style={{ fontSize: 13 }}> <div style={{ fontSize: 13 }}>
<p><Text strong>DeepSeek</Text>:国产大模型,性价比高,推荐用于医疗场?/p> <p><Text strong>DeepSeek</Text>:国产大模型,性价比高,推荐用于医疗场</p>
<p><Text strong>OpenAI</Text>:GPT系列,英文能力强</p> <p><Text strong>OpenAI</Text>:GPT系列,英文能力强</p>
<p><Text strong>通义千问</Text>:阿里云大模型,中文能力优秀</p> <p><Text strong>通义千问</Text>:阿里云大模型,中文能力优秀</p>
<p><Text strong>Temperature</Text>:越低输出越稳定,医疗场景建?0.1-0.3</p> <p><Text strong>Temperature</Text>:越低输出越稳定,医疗场景建?0.1-0.3</p>
...@@ -534,7 +537,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -534,7 +537,7 @@ const AdminAIConfigPage: React.FC = () => {
}, },
{ {
key: 'safety', key: 'safety',
label: <span><SafetyOutlined /> 安全词过?/span>, label: <span><SafetyOutlined /> 安全词过</span>,
children: ( children: (
<div> <div>
<Alert <Alert
...@@ -546,11 +549,11 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -546,11 +549,11 @@ const AdminAIConfigPage: React.FC = () => {
<Card style={{ borderRadius: 12 }}> <Card style={{ borderRadius: 12 }}>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}> <div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
<Space> <Space>
<Input placeholder="搜索关键? prefix={<ExclamationCircleOutlined />} style={{ width: 200 }} allowClear /> <Input placeholder="搜索关键 prefix={<ExclamationCircleOutlined />} style={{ width: 200 }} allowClear />
<Select placeholder="分类" style={{ width: 120 }} allowClear options={[ <Select placeholder="分类" style={{ width: 120 }} allowClear options={[
{ label: '心理危机', value: '心理危机' }, { label: '心理危机', value: '心理危机' },
{ label: '违规用药', value: '违规用药' }, { label: '违规用药', value: '违规用药' },
{ label: '违禁?, value: '违禁? }, { label: '违禁用, value: '违禁用 },
{ label: '不实信息', value: '不实信息' }, { label: '不实信息', value: '不实信息' },
]} /> ]} />
</Space> </Space>
...@@ -558,7 +561,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -558,7 +561,7 @@ const AdminAIConfigPage: React.FC = () => {
safeWordForm.resetFields(); safeWordForm.resetFields();
setSafeWordModalVisible(true); setSafeWordModalVisible(true);
}}> }}>
添加安全? </Button> 添加安全 </Button>
</div> </div>
<Table columns={safeWordColumns} dataSource={mockSafeWords} rowKey="id" pagination={false} /> <Table columns={safeWordColumns} dataSource={mockSafeWords} rowKey="id" pagination={false} />
</Card> </Card>
...@@ -573,20 +576,20 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -573,20 +576,20 @@ const AdminAIConfigPage: React.FC = () => {
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space wrap> <Space wrap>
<Select <Select
placeholder="场景筛? placeholder="场景筛
style={{ width: 150 }} style={{ width: 150 }}
allowClear allowClear
value={logSceneFilter} value={logSceneFilter}
onChange={setLogSceneFilter} onChange={setLogSceneFilter}
options={[ options={[
{ label: '预问诊对?, value: 'pre_consult_chat' }, { label: '预问诊对, value: 'pre_consult_chat' },
{ label: '预问诊报?, value: 'pre_consult_analysis' }, { label: '预问诊报告, value: 'pre_consult_analysis' },
{ label: '鉴别诊断', value: 'consult_diagnosis' }, { label: '鉴别诊断', value: 'consult_diagnosis' },
{ label: '用药建议', value: 'consult_medication' }, { label: '用药建议', value: 'consult_medication' },
]} ]}
/> />
<Select <Select
placeholder="状态筛? placeholder="状态筛
style={{ width: 120 }} style={{ width: 120 }}
allowClear allowClear
value={logSuccessFilter} value={logSuccessFilter}
...@@ -610,8 +613,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -610,8 +613,8 @@ const AdminAIConfigPage: React.FC = () => {
title: '场景', dataIndex: 'scene', key: 'scene', width: 130, title: '场景', dataIndex: 'scene', key: 'scene', width: 130,
render: (v: string) => { render: (v: string) => {
const sceneLabels: Record<string, string> = { const sceneLabels: Record<string, string> = {
pre_consult_chat: '预问诊对?, pre_consult_chat: '预问诊对,
pre_consult_analysis: '预问诊报?, pre_consult_analysis: '预问诊报告,
consult_diagnosis: '鉴别诊断', consult_diagnosis: '鉴别诊断',
consult_medication: '用药建议', consult_medication: '用药建议',
}; };
...@@ -620,9 +623,10 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -620,9 +623,10 @@ const AdminAIConfigPage: React.FC = () => {
}, },
{ title: '模型', dataIndex: 'model', key: 'model', width: 140 }, { title: '模型', dataIndex: 'model', key: 'model', width: 140 },
{ {
title: '提示?, dataIndex: 'prompt', key: 'prompt', title: '提示, dataIndex: 'prompt', key: 'prompt',
render: (v: string) => { render: (v: string) => {
// 提取第一行或关键行作为摘? const lines = v?.split('\n') || []; //
const lines = v?.split('\n') || [];
const firstLine = lines.find(line => line.trim() && !line.startsWith('#')) || lines[0] || ''; const firstLine = lines.find(line => line.trim() && !line.startsWith('#')) || lines[0] || '';
return ( return (
<Tooltip title={v}> <Tooltip title={v}>
...@@ -652,7 +656,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -652,7 +656,7 @@ const AdminAIConfigPage: React.FC = () => {
render: (v: number) => <Text type="secondary">{v ? (v / 1000).toFixed(2) + 's' : '-'}</Text>, render: (v: number) => <Text type="secondary">{v ? (v / 1000).toFixed(2) + 's' : '-'}</Text>,
}, },
{ {
title: '状?, dataIndex: 'success', key: 'success', width: 80, title: '状态, dataIndex: 'success', key: 'success', width: 80,
render: (v: boolean, record: AILogItem) => ( render: (v: boolean, record: AILogItem) => (
<Space direction="vertical" size={0}> <Space direction="vertical" size={0}>
<Tag color={v ? 'success' : 'error'}>{v ? '成功' : '失败'}</Tag> <Tag color={v ? 'success' : 'error'}>{v ? '成功' : '失败'}</Tag>
...@@ -718,8 +722,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -718,8 +722,8 @@ const AdminAIConfigPage: React.FC = () => {
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="模板名称" name="name" rules={[{ required: true, message: '请输入模板名? }]}> <Form.Item label="模板名称" name="name" rules={[{ required: true, message: '请输入模板名 }]}>
<Input placeholder="请输入模板名? /> <Input placeholder="请输入模板名 />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
...@@ -727,8 +731,8 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -727,8 +731,8 @@ const AdminAIConfigPage: React.FC = () => {
<Col span={12}> <Col span={12}>
<Form.Item label="应用场景" name="scene" rules={[{ required: true, message: '请选择场景' }]}> <Form.Item label="应用场景" name="scene" rules={[{ required: true, message: '请选择场景' }]}>
<Select placeholder="选择场景" options={[ <Select placeholder="选择场景" options={[
{ label: '预问诊对?, value: 'pre_consult_chat' }, { label: '预问诊对, value: 'pre_consult_chat' },
{ label: '预问诊报?, value: 'pre_consult_analysis' }, { label: '预问诊报告, value: 'pre_consult_analysis' },
{ label: '鉴别诊断', value: 'consult_diagnosis' }, { label: '鉴别诊断', value: 'consult_diagnosis' },
{ label: '用药建议', value: 'consult_medication' }, { label: '用药建议', value: 'consult_medication' },
{ label: '处方审核', value: 'prescription_review' }, { label: '处方审核', value: 'prescription_review' },
...@@ -736,7 +740,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -736,7 +740,7 @@ const AdminAIConfigPage: React.FC = () => {
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="状? name="status" rules={[{ required: true }]}> <Form.Item label="状态 name="status" rules={[{ required: true }]}>
<Select options={[ <Select options={[
{ label: '启用', value: 'active' }, { label: '启用', value: 'active' },
{ label: '停用', value: 'disabled' }, { label: '停用', value: 'disabled' },
...@@ -748,7 +752,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -748,7 +752,7 @@ const AdminAIConfigPage: React.FC = () => {
<TextArea rows={8} placeholder="请输?Prompt 模板内容...支持多行" /> <TextArea rows={8} placeholder="请输?Prompt 模板内容...支持多行" />
</Form.Item> </Form.Item>
<Alert <Alert
message='提示:同一场景可创建多个模板,但只有一个“启用”状态的模板会生效。点击“激活”按钮可切换当前场景使用的模板? message='提示:同一场景可创建多个模板,但只有一个“启用”状态的模板会生效。点击“激活”按钮可切换当前场景使用的模板
type="info" type="info"
showIcon showIcon
style={{ marginBottom: 0 }} style={{ marginBottom: 0 }}
...@@ -758,33 +762,33 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -758,33 +762,33 @@ const AdminAIConfigPage: React.FC = () => {
{/* 安全词弹?*/} {/* 安全词弹?*/}
<Modal <Modal
title="添加安全? title="添加安全
open={safeWordModalVisible} open={safeWordModalVisible}
onOk={handleSaveSafeWord} onOk={handleSaveSafeWord}
onCancel={() => setSafeWordModalVisible(false)} onCancel={() => setSafeWordModalVisible(false)}
okText="保存" okText="保存"
> >
<Form form={safeWordForm} layout="vertical"> <Form form={safeWordForm} layout="vertical">
<Form.Item label="关键? name="word" rules={[{ required: true }]}> <Form.Item label="关键 name="word" rules={[{ required: true }]}>
<Input placeholder="请输入需过滤的关键词" /> <Input placeholder="请输入需过滤的关键词" />
</Form.Item> </Form.Item>
<Form.Item label="分类" name="category" rules={[{ required: true }]}> <Form.Item label="分类" name="category" rules={[{ required: true }]}>
<Select placeholder="选择分类" options={[ <Select placeholder="选择分类" options={[
{ label: '心理危机', value: '心理危机' }, { label: '心理危机', value: '心理危机' },
{ label: '违规用药', value: '违规用药' }, { label: '违规用药', value: '违规用药' },
{ label: '违禁?, value: '违禁? }, { label: '违禁用, value: '违禁用 },
{ label: '不实信息', value: '不实信息' }, { label: '不实信息', value: '不实信息' },
]} /> ]} />
</Form.Item> </Form.Item>
<Form.Item label="触发动作" name="action" rules={[{ required: true }]}> <Form.Item label="触发动作" name="action" rules={[{ required: true }]}>
<Select placeholder="选择动作" options={[ <Select placeholder="选择动作" options={[
{ label: '拦截(阻止输出)', value: 'block' }, { label: '拦截(阻止输出)', value: 'block' },
{ label: '预警(允许但标记?, value: 'warn' }, { label: '预警(允许但标记录, value: 'warn' },
{ label: '替换(输出替换文本)', value: 'replace' }, { label: '替换(输出替换文本)', value: 'replace' },
]} /> ]} />
</Form.Item> </Form.Item>
<Form.Item label="替换内容" name="replacement"> <Form.Item label="替换内容" name="replacement">
<Input placeholder="仅替换动作时需要填? /> <Input placeholder="仅替换动作时需要填 />
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
...@@ -804,11 +808,11 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -804,11 +808,11 @@ const AdminAIConfigPage: React.FC = () => {
<Space direction="vertical" size={16} style={{ width: '100%' }}> <Space direction="vertical" size={16} style={{ width: '100%' }}>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Text type="secondary">场景?/Text> <Text type="secondary">场景</Text>
<Text strong>{logDetailItem.scene}</Text> <Text strong>{logDetailItem.scene}</Text>
</Col> </Col>
<Col span={8}> <Col span={8}>
<Text type="secondary">模型?/Text> <Text type="secondary">模型</Text>
<Text strong>{logDetailItem.model || '-'}</Text> <Text strong>{logDetailItem.model || '-'}</Text>
</Col> </Col>
<Col span={8}> <Col span={8}>
...@@ -835,11 +839,11 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -835,11 +839,11 @@ const AdminAIConfigPage: React.FC = () => {
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Text type="secondary">响应时间?/Text> <Text type="secondary">响应时间</Text>
<Text>{logDetailItem.response_time_ms ? (logDetailItem.response_time_ms / 1000).toFixed(2) + 's' : '-'}</Text> <Text>{logDetailItem.response_time_ms ? (logDetailItem.response_time_ms / 1000).toFixed(2) + 's' : '-'}</Text>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Text type="secondary">调用时间?/Text> <Text type="secondary">调用时间</Text>
<Text>{dayjs(logDetailItem.created_at).format('YYYY-MM-DD HH:mm:ss')}</Text> <Text>{dayjs(logDetailItem.created_at).format('YYYY-MM-DD HH:mm:ss')}</Text>
</Col> </Col>
</Row> </Row>
...@@ -853,7 +857,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -853,7 +857,7 @@ const AdminAIConfigPage: React.FC = () => {
</Card> </Card>
</div> </div>
<div> <div>
<Text type="secondary">请求摘要?/Text> <Text type="secondary">请求摘要</Text>
<Card size="small" style={{ marginTop: 8, maxHeight: 150, overflow: 'auto' }}> <Card size="small" style={{ marginTop: 8, maxHeight: 150, overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}> <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{logDetailItem.request_content || '?} {logDetailItem.request_content || '?}
...@@ -861,7 +865,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -861,7 +865,7 @@ const AdminAIConfigPage: React.FC = () => {
</Card> </Card>
</div> </div>
<div> <div>
<Text type="secondary">响应内容?/Text> <Text type="secondary">响应内容</Text>
<Card size="small" style={{ marginTop: 8, maxHeight: 300, overflow: 'auto' }}> <Card size="small" style={{ marginTop: 8, maxHeight: 300, overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}> <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{logDetailItem.response_content || '?} {logDetailItem.response_content || '?}
...@@ -870,7 +874,7 @@ const AdminAIConfigPage: React.FC = () => { ...@@ -870,7 +874,7 @@ const AdminAIConfigPage: React.FC = () => {
</div> </div>
{logDetailItem.error_message && ( {logDetailItem.error_message && (
<div> <div>
<Text type="secondary">错误信息?/Text> <Text type="secondary">错误信息</Text>
<Alert message={logDetailItem.error_message} type="error" style={{ marginTop: 8 }} /> <Alert message={logDetailItem.error_message} type="error" style={{ marginTop: 8 }} />
</div> </div>
)} )}
......
...@@ -29,7 +29,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -29,7 +29,7 @@ const AdminAdminsPage: React.FC = () => {
setAdmins(res.data.list || []); setAdmins(res.data.list || []);
setTotal(res.data.total || 0); setTotal(res.data.total || 0);
} catch (error) { } catch (error) {
message.error('获取管理员列表失?); message.error('获取管理员列表失);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -63,7 +63,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -63,7 +63,7 @@ const AdminAdminsPage: React.FC = () => {
const handleResetPassword = (record: UserInfo) => { const handleResetPassword = (record: UserInfo) => {
Modal.confirm({ Modal.confirm({
title: '确认重置密码?, title: '确认重置密码码,
content: `将重置管理员 ${record.real_name || record.phone} 的密码为默认密码`, content: `将重置管理员 ${record.real_name || record.phone} 的密码为默认密码`,
onOk: async () => { onOk: async () => {
try { try {
...@@ -97,7 +97,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -97,7 +97,7 @@ const AdminAdminsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('管理员信息更新成?); message.success('管理员信息更新成功);
setEditModalVisible(false); setEditModalVisible(false);
editForm.resetFields(); editForm.resetFields();
fetchAdmins(); fetchAdmins();
...@@ -110,7 +110,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -110,7 +110,7 @@ const AdminAdminsPage: React.FC = () => {
const handleDeleteAdmin = (record: UserInfo) => { const handleDeleteAdmin = (record: UserInfo) => {
Modal.confirm({ Modal.confirm({
title: '确认删除该管理员?, title: '确认删除该管理员,
content: `管理员:${record.real_name || record.phone},删除后不可恢复`, content: `管理员:${record.real_name || record.phone},删除后不可恢复`,
okType: 'danger', okType: 'danger',
onOk: async () => { onOk: async () => {
...@@ -135,7 +135,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -135,7 +135,7 @@ const AdminAdminsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('管理员添加成?); message.success('管理员添加成);
setAddModalVisible(false); setAddModalVisible(false);
addForm.resetFields(); addForm.resetFields();
fetchAdmins(); fetchAdmins();
...@@ -148,13 +148,13 @@ const AdminAdminsPage: React.FC = () => { ...@@ -148,13 +148,13 @@ const AdminAdminsPage: React.FC = () => {
const columns: ColumnsType<UserInfo> = [ const columns: ColumnsType<UserInfo> = [
{ {
title: '管理?, title: '管理员,
key: 'admin', key: 'admin',
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
<Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#722ed1' }} /> <Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#722ed1' }} />
<div> <div>
<Text strong>{record.real_name || '未填?}</Text> <Text strong>{record.real_name || '未填写}</Text>
<br /> <br />
<Text type="secondary" style={{ fontSize: 12 }}>@{record.phone}</Text> <Text type="secondary" style={{ fontSize: 12 }}>@{record.phone}</Text>
</div> </div>
...@@ -162,7 +162,7 @@ const AdminAdminsPage: React.FC = () => { ...@@ -162,7 +162,7 @@ const AdminAdminsPage: React.FC = () => {
), ),
}, },
{ {
title: '用户?, title: '用户,
dataIndex: 'phone', dataIndex: 'phone',
key: 'phone', key: 'phone',
}, },
...@@ -181,12 +181,12 @@ const AdminAdminsPage: React.FC = () => { ...@@ -181,12 +181,12 @@ const AdminAdminsPage: React.FC = () => {
render: (age: number) => age || '-', render: (age: number) => age || '-',
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -227,19 +227,19 @@ const AdminAdminsPage: React.FC = () => { ...@@ -227,19 +227,19 @@ const AdminAdminsPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">管理员管?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">管理员管</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input placeholder="搜索姓名/用户? prefix={<SearchOutlined />} value={keyword} <Input placeholder="搜索姓名/用户 prefix={<SearchOutlined />} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onPressEnter={handleSearch} style={{ width: 180 }} size="small" /> onChange={(e) => setKeyword(e.target.value)} onPressEnter={handleSearch} style={{ width: 180 }} size="small" />
<Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button> <Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
<Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setPage(1); fetchAdmins(); }}>重置</Button> <Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setPage(1); fetchAdmins(); }}>重置</Button>
</div> </div>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={() => setAddModalVisible(true)}>添加管理?/Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={() => setAddModalVisible(true)}>添加管理员</Button>
</div> </div>
</Card> </Card>
...@@ -261,13 +261,13 @@ const AdminAdminsPage: React.FC = () => { ...@@ -261,13 +261,13 @@ const AdminAdminsPage: React.FC = () => {
/> />
</Card> </Card>
<Modal title="添加管理? open={addModalVisible} onCancel={() => setAddModalVisible(false)} footer={null} width={450}> <Modal title="添加管理员 open={addModalVisible} onCancel={() => setAddModalVisible(false)} footer={null} width={450}>
<Form form={addForm} layout="vertical" size="small" onFinish={handleAddAdmin}> <Form form={addForm} layout="vertical" size="small" onFinish={handleAddAdmin}>
<Form.Item name="username" label="用户? rules={[{ required: true, message: '请输入用户名' }]}> <Form.Item name="username" label="用户 rules={[{ required: true, message: '请输入用户名' }]}>
<Input placeholder="用户名(用于登录? /> <Input placeholder="用户名(用于登录 />
</Form.Item> </Form.Item>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓名 }]}>
<Input placeholder="管理员姓? /> <Input placeholder="管理员姓名 />
</Form.Item> </Form.Item>
<Form.Item name="gender" label="性别"> <Form.Item name="gender" label="性别">
<Input placeholder="性别" /> <Input placeholder="性别" />
...@@ -290,18 +290,18 @@ const AdminAdminsPage: React.FC = () => { ...@@ -290,18 +290,18 @@ const AdminAdminsPage: React.FC = () => {
</Modal> </Modal>
<Modal <Modal
title="编辑管理? title="编辑管理员
open={editModalVisible} open={editModalVisible}
onCancel={() => setEditModalVisible(false)} onCancel={() => setEditModalVisible(false)}
footer={null} footer={null}
width={500} width={500}
> >
<Form form={editForm} layout="vertical" onFinish={handleEditSubmit}> <Form form={editForm} layout="vertical" onFinish={handleEditSubmit}>
<Form.Item name="username" label="用户? rules={[{ required: true, message: '请输入用户名' }]}> <Form.Item name="username" label="用户 rules={[{ required: true, message: '请输入用户名' }]}>
<Input placeholder="用户? /> <Input placeholder="用户 />
</Form.Item> </Form.Item>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓名 }]}>
<Input placeholder="管理员姓? /> <Input placeholder="管理员姓名 />
</Form.Item> </Form.Item>
<Form.Item name="gender" label="性别"> <Form.Item name="gender" label="性别">
<Input placeholder="性别" /> <Input placeholder="性别" />
......
...@@ -40,17 +40,17 @@ interface ReportItem { ...@@ -40,17 +40,17 @@ interface ReportItem {
} }
const mockLogs: AuditLog[] = [ const mockLogs: AuditLog[] = [
{ id: '1', timestamp: '2026-02-25 18:30:15', user: '管理?, role: 'admin', action: '审核通过', module: '医生管理', ip: '192.168.1.100', detail: '审核通过医生 周医?的资质认证申?, level: 'info' }, { id: '1', timestamp: '2026-02-25 18:30:15', user: '管理员, role: 'admin', action: '审核通过', module: '医生管理', ip: '192.168.1.100', detail: '审核通过医生 周医?的资质认证申, level: 'info' },
{ id: '2', timestamp: '2026-02-25 17:45:22', user: '陈医?, role: 'doctor', action: '开具处?, module: '处方管理', ip: '192.168.1.105', detail: '为患者张三开具处?RX20260225001', level: 'info' }, { id: '2', timestamp: '2026-02-25 17:45:22', user: '陈医院, role: 'doctor', action: '开具处方, module: '处方管理', ip: '192.168.1.105', detail: '为患者张三开具处?RX20260225001', level: 'info' },
{ id: '3', timestamp: '2026-02-25 16:20:08', user: '管理?, role: 'admin', action: '修改配置', module: 'AI配置', ip: '192.168.1.100', detail: '修改 AI 模型 temperature 参数?0.5 ?0.3', level: 'warning' }, { id: '3', timestamp: '2026-02-25 16:20:08', user: '管理员, role: 'admin', action: '修改配置', module: 'AI配置', ip: '192.168.1.100', detail: '修改 AI 模型 temperature 参数?0.5 ?0.3', level: 'warning' },
{ id: '4', timestamp: '2026-02-25 15:10:33', user: '未知用户', role: 'unknown', action: '登录失败', module: '系统安全', ip: '10.0.0.55', detail: '连续3次密码错误,账户已临时锁?, level: 'danger' }, { id: '4', timestamp: '2026-02-25 15:10:33', user: '未知用户', role: 'unknown', action: '登录失败', module: '系统安全', ip: '10.0.0.55', detail: '连续3次密码错误,账户已临时锁, level: 'danger' },
{ id: '5', timestamp: '2026-02-25 14:05:11', user: '管理?, role: 'admin', action: '数据导出', module: '数据管理', ip: '192.168.1.100', detail: '导出本月问诊数据报表', level: 'info' }, { id: '5', timestamp: '2026-02-25 14:05:11', user: '管理员, role: 'admin', action: '数据导出', module: '数据管理', ip: '192.168.1.100', detail: '导出本月问诊数据报表', level: 'info' },
{ id: '6', timestamp: '2026-02-25 11:30:45', user: '刘医?, role: 'doctor', action: '异常处方', module: '处方监管', ip: '192.168.1.108', detail: '处方 RX20260225004 被系统标记为异常:抗生素使用不符合指?, level: 'danger' }, { id: '6', timestamp: '2026-02-25 11:30:45', user: '刘医院, role: 'doctor', action: '异常处方', module: '处方监管', ip: '192.168.1.108', detail: '处方 RX20260225004 被系统标记为异常:抗生素使用不符合指, level: 'danger' },
]; ];
const mockReports: ReportItem[] = [ const mockReports: ReportItem[] = [
{ id: '1', name: '2026?月运营月?, type: '月度报告', period: '2026-02', status: 'pending' }, { id: '1', name: '2026?月运营月, type: '月度报告', period: '2026-02', status: 'pending' },
{ id: '2', name: '2026?月运营月?, type: '月度报告', period: '2026-01', status: 'submitted', generated_at: '2026-02-05', submitted_at: '2026-02-10' }, { id: '2', name: '2026?月运营月, type: '月度报告', period: '2026-01', status: 'submitted', generated_at: '2026-02-05', submitted_at: '2026-02-10' },
{ id: '3', name: '2025Q4季度合规报告', type: '季度报告', period: '2025-Q4', status: 'submitted', generated_at: '2026-01-10', submitted_at: '2026-01-15' }, { id: '3', name: '2025Q4季度合规报告', type: '季度报告', period: '2025-Q4', status: 'submitted', generated_at: '2026-01-10', submitted_at: '2026-01-15' },
{ id: '4', name: '处方用药合规专项报告', type: '专项报告', period: '2026-02', status: 'generated', generated_at: '2026-02-20' }, { id: '4', name: '处方用药合规专项报告', type: '专项报告', period: '2026-02', status: 'generated', generated_at: '2026-02-20' },
{ id: '5', name: '等保三级自查报告', type: '安全报告', period: '2026-02', status: 'pending' }, { id: '5', name: '等保三级自查报告', type: '安全报告', period: '2026-02', status: 'pending' },
...@@ -75,9 +75,9 @@ const AdminCompliancePage: React.FC = () => { ...@@ -75,9 +75,9 @@ const AdminCompliancePage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
switch (status) { switch (status) {
case 'generated': return <Tag color="blue">已生?/Tag>; case 'generated': return <Tag color="blue">已生</Tag>;
case 'pending': return <Tag color="orange">待生?/Tag>; case 'pending': return <Tag color="orange">待生</Tag>;
case 'submitted': return <Tag color="green">已上?/Tag>; case 'submitted': return <Tag color="green">已上</Tag>;
default: return <Tag>{status}</Tag>; default: return <Tag>{status}</Tag>;
} }
}; };
...@@ -88,7 +88,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -88,7 +88,7 @@ const AdminCompliancePage: React.FC = () => {
{ {
title: '角色', dataIndex: 'role', key: 'role', width: 80, title: '角色', dataIndex: 'role', key: 'role', width: 80,
render: (v: string) => { render: (v: string) => {
const map: Record<string, string> = { admin: '管理?, doctor: '医生', patient: '患?, unknown: '未知' }; const map: Record<string, string> = { admin: '管理员, doctor: '医生', patient: '患者, unknown: '未知' };
return <Tag>{map[v] || v}</Tag>; return <Tag>{map[v] || v}</Tag>;
}, },
}, },
...@@ -103,7 +103,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -103,7 +103,7 @@ const AdminCompliancePage: React.FC = () => {
{ title: '报告名称', dataIndex: 'name', key: 'name', render: (v: string) => <Text strong>{v}</Text> }, { title: '报告名称', dataIndex: 'name', key: 'name', render: (v: string) => <Text strong>{v}</Text> },
{ title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => <Tag color="purple">{v}</Tag> }, { title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => <Tag color="purple">{v}</Tag> },
{ title: '周期', dataIndex: 'period', key: 'period', width: 100 }, { title: '周期', dataIndex: 'period', key: 'period', width: 100 },
{ title: '状?, dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) }, { title: '状态, dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) },
{ title: '生成时间', dataIndex: 'generated_at', key: 'generated_at', width: 120, render: (v: string) => v || '-' }, { title: '生成时间', dataIndex: 'generated_at', key: 'generated_at', width: 120, render: (v: string) => v || '-' },
{ title: '上报时间', dataIndex: 'submitted_at', key: 'submitted_at', width: 120, render: (v: string) => v || '-' }, { title: '上报时间', dataIndex: 'submitted_at', key: 'submitted_at', width: 120, render: (v: string) => v || '-' },
{ {
...@@ -142,12 +142,12 @@ const AdminCompliancePage: React.FC = () => { ...@@ -142,12 +142,12 @@ const AdminCompliancePage: React.FC = () => {
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #52c41a' }}> <Card size="small" style={{ borderLeft: '3px solid #52c41a' }}>
<Statistic title="已上? value={mockReports.filter((r) => r.status === 'submitted').length} suffix="? /> <Statistic title="已上 value={mockReports.filter((r) => r.status === 'submitted').length} suffix="? />
</Card> </Card>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #722ed1' }}> <Card size="small" style={{ borderLeft: '3px solid #722ed1' }}>
<Statistic title="待生? value={mockReports.filter((r) => r.status === 'pending').length} valueStyle={{ color: '#fa8c16' }} suffix="? /> <Statistic title="待生 value={mockReports.filter((r) => r.status === 'pending').length} valueStyle={{ color: '#fa8c16' }} suffix="? />
</Card> </Card>
</Col> </Col>
</Row> </Row>
...@@ -181,10 +181,10 @@ const AdminCompliancePage: React.FC = () => { ...@@ -181,10 +181,10 @@ const AdminCompliancePage: React.FC = () => {
<Card size="small"> <Card size="small">
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{[ {[
{ title: '问诊数据', desc: '问诊记录、诊断、处?, icon: <FileSearchOutlined className="text-xl text-[#1890ff]" /> }, { title: '问诊数据', desc: '问诊记录、诊断、处方, icon: <FileSearchOutlined className="text-xl text-[#1890ff]" /> },
{ title: '用户数据', desc: '患者和医生基本信息', icon: <UserOutlined className="text-xl text-green-500" /> }, { title: '用户数据', desc: '患者和医生基本信息', icon: <UserOutlined className="text-xl text-green-500" /> },
{ title: '处方数据', desc: '处方记录、用药统?, icon: <AuditOutlined className="text-xl text-purple-500" /> }, { title: '处方数据', desc: '处方记录、用药统计, icon: <AuditOutlined className="text-xl text-purple-500" /> },
{ title: '系统日志', desc: '操作日志和审计记?, icon: <SettingOutlined className="text-xl text-orange-500" /> }, { title: '系统日志', desc: '操作日志和审计记录, icon: <SettingOutlined className="text-xl text-orange-500" /> },
].map((item, i) => ( ].map((item, i) => (
<Col span={6} key={i}> <Col span={6} key={i}>
<Card size="small" hoverable className="text-center" onClick={() => setExportModalVisible(true)}> <Card size="small" hoverable className="text-center" onClick={() => setExportModalVisible(true)}>
...@@ -205,7 +205,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -205,7 +205,7 @@ const AdminCompliancePage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs text-gray-400">管理合规报告,支持下载和上报</span> <span className="text-xs text-gray-400">管理合规报告,支持下载和上报</span>
<Button type="primary" size="small">生成新报?/Button> <Button type="primary" size="small">生成新报</Button>
</div> </div>
<Table columns={reportColumns} dataSource={mockReports} rowKey="id" size="small" pagination={false} /> <Table columns={reportColumns} dataSource={mockReports} rowKey="id" size="small" pagination={false} />
</Card> </Card>
...@@ -229,7 +229,7 @@ const AdminCompliancePage: React.FC = () => { ...@@ -229,7 +229,7 @@ const AdminCompliancePage: React.FC = () => {
<div> <div>
<Text strong className="text-xs!">数据脱敏</Text> <Text strong className="text-xs!">数据脱敏</Text>
<Select defaultValue="partial" size="small" style={{ width: '100%', marginTop: 4 }} options={[ <Select defaultValue="partial" size="small" style={{ width: '100%', marginTop: 4 }} options={[
{ label: '部分脱敏(推荐)', value: 'partial' }, { label: '完全脱敏', value: 'full' }, { label: '不脱?, value: 'none' }, { label: '部分脱敏(推荐)', value: 'partial' }, { label: '完全脱敏', value: 'full' }, { label: '不脱敏, value: 'none' },
]} /> ]} />
</div> </div>
</div> </div>
......
...@@ -12,11 +12,11 @@ const { Title } = Typography; ...@@ -12,11 +12,11 @@ const { Title } = Typography;
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待支?, color: 'orange' }, pending: { text: '待支付, color: 'orange' },
waiting: { text: '等待接诊', color: 'processing' }, waiting: { text: '等待接诊', color: 'processing' },
in_progress: { text: '进行?, color: 'blue' }, in_progress: { text: '进行中, color: 'blue' },
completed: { text: '已完?, color: 'green' }, completed: { text: '已完成, color: 'green' },
cancelled: { text: '已取?, color: 'red' }, cancelled: { text: '已取消, color: 'red' },
}; };
const AdminConsultationsPage: React.FC = () => { const AdminConsultationsPage: React.FC = () => {
...@@ -70,7 +70,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -70,7 +70,7 @@ const AdminConsultationsPage: React.FC = () => {
render: (v: string) => v ? v.substring(0, 8) + '...' : '-', render: (v: string) => v ? v.substring(0, 8) + '...' : '-',
}, },
{ {
title: '患?, title: '患者,
dataIndex: 'patient_name', dataIndex: 'patient_name',
key: 'patient_name', key: 'patient_name',
render: (name: string) => ( render: (name: string) => (
...@@ -84,7 +84,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -84,7 +84,7 @@ const AdminConsultationsPage: React.FC = () => {
title: '医生', title: '医生',
dataIndex: 'doctor_name', dataIndex: 'doctor_name',
key: 'doctor_name', key: 'doctor_name',
render: (name: string) => name || '未分?, render: (name: string) => name || '未分,
}, },
{ {
title: '类型', title: '类型',
...@@ -107,7 +107,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -107,7 +107,7 @@ const AdminConsultationsPage: React.FC = () => {
ellipsis: true, ellipsis: true,
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 100, width: 100,
...@@ -137,7 +137,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -137,7 +137,7 @@ const AdminConsultationsPage: React.FC = () => {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">问诊管理</h4> <h4 className="text-sm font-bold text-gray-800 m-0">问诊管理</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
...@@ -164,7 +164,7 @@ const AdminConsultationsPage: React.FC = () => { ...@@ -164,7 +164,7 @@ const AdminConsultationsPage: React.FC = () => {
]} ]}
/> />
<Select <Select
placeholder="状? placeholder="状态
style={{ width: 100 }} style={{ width: 100 }}
size="small" size="small"
allowClear allowClear
......
...@@ -40,7 +40,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -40,7 +40,7 @@ const AdminDashboardPage: React.FC = () => {
setStats(statsRes.data); setStats(statsRes.data);
setRecentReviews(reviewsRes.data.list?.slice(0, 3) || []); setRecentReviews(reviewsRes.data.list?.slice(0, 3) || []);
} catch (error) { } catch (error) {
console.error('获取仪表盘数据失?', error); console.error('获取仪表盘数据失', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -85,14 +85,14 @@ const AdminDashboardPage: React.FC = () => { ...@@ -85,14 +85,14 @@ const AdminDashboardPage: React.FC = () => {
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<div className="flex justify-between mb-1"> <div className="flex justify-between mb-1">
<Text className="text-xs!">医生在线?/Text> <Text className="text-xs!">医生在线</Text>
<Text strong className="text-xs!">{stats.total_doctors > 0 ? ((stats.online_doctors / stats.total_doctors) * 100).toFixed(0) : 0}%</Text> <Text strong className="text-xs!">{stats.total_doctors > 0 ? ((stats.online_doctors / stats.total_doctors) * 100).toFixed(0) : 0}%</Text>
</div> </div>
<Progress percent={stats.total_doctors > 0 ? Math.round((stats.online_doctors / stats.total_doctors) * 100) : 0} strokeColor="#52c41a" showInfo={false} size="small" /> <Progress percent={stats.total_doctors > 0 ? Math.round((stats.online_doctors / stats.total_doctors) * 100) : 0} strokeColor="#52c41a" showInfo={false} size="small" />
</div> </div>
<div> <div>
<div className="flex justify-between mb-1"> <div className="flex justify-between mb-1">
<Text className="text-xs!">今日完成?/Text> <Text className="text-xs!">今日完成</Text>
<Text strong className="text-xs!">{stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0}%</Text> <Text strong className="text-xs!">{stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0}%</Text>
</div> </div>
<Progress percent={stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0} strokeColor="#1890ff" showInfo={false} size="small" /> <Progress percent={stats.today_consultations > 0 ? Math.round((stats.today_consultations / (stats.today_consultations + stats.pending_doctor_reviews)) * 100) : 0} strokeColor="#1890ff" showInfo={false} size="small" />
...@@ -110,7 +110,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -110,7 +110,7 @@ const AdminDashboardPage: React.FC = () => {
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Card <Card
title={<span className="text-xs font-semibold">待审核医?<Tag color="orange" className="ml-1">{stats.pending_doctor_reviews}</Tag></span>} title={<span className="text-xs font-semibold">待审核医<Tag color="orange" className="ml-1">{stats.pending_doctor_reviews}</Tag></span>}
size="small" size="small"
className="h-full!" className="h-full!"
> >
...@@ -124,7 +124,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -124,7 +124,7 @@ const AdminDashboardPage: React.FC = () => {
title={<span className="text-xs"><Text strong>{item.name}</Text> <Tag className="ml-1">{item.title}</Tag></span>} title={<span className="text-xs"><Text strong>{item.name}</Text> <Tag className="ml-1">{item.title}</Tag></span>}
description={<span className="text-[11px]">{item.department_name} · {item.submitted_at}</span>} description={<span className="text-[11px]">{item.department_name} · {item.submitted_at}</span>}
/> />
<Tag color="orange" className="text-[10px]!">待审?/Tag> <Tag color="orange" className="text-[10px]!">待审</Tag>
</List.Item> </List.Item>
)} )}
/> />
...@@ -138,7 +138,7 @@ const AdminDashboardPage: React.FC = () => { ...@@ -138,7 +138,7 @@ const AdminDashboardPage: React.FC = () => {
{ icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', label: '累计问诊', value: stats.total_consultations.toLocaleString() }, { icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', label: '累计问诊', value: stats.total_consultations.toLocaleString() },
{ icon: <RiseOutlined />, color: '#fa8c16', bg: '#fff7e6', label: '本月收入', value: `¥${(stats.revenue_month / 100).toLocaleString()}` }, { icon: <RiseOutlined />, color: '#fa8c16', bg: '#fff7e6', label: '本月收入', value: `¥${(stats.revenue_month / 100).toLocaleString()}` },
{ icon: <ClockCircleOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '平均响应', value: '3.2分钟' }, { icon: <ClockCircleOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '平均响应', value: '3.2分钟' },
{ icon: <AuditOutlined />, color: '#722ed1', bg: '#f9f0ff', label: '待审?, value: stats.pending_doctor_reviews }, { icon: <AuditOutlined />, color: '#722ed1', bg: '#f9f0ff', label: '待审核, value: stats.pending_doctor_reviews },
].map((item, i) => ( ].map((item, i) => (
<Col xs={12} sm={6} key={i}> <Col xs={12} sm={6} key={i}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center">
......
...@@ -25,7 +25,7 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -25,7 +25,7 @@ const AdminDepartmentsPage: React.FC = () => {
const res = await adminApi.getDepartmentList(); const res = await adminApi.getDepartmentList();
setDepartments(res.data || []); setDepartments(res.data || []);
} catch (error) { } catch (error) {
message.error('鑾峰彇绉戝鍒楄〃澶辫触'); message.error('获取种戝列表失败');
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -49,15 +49,15 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -49,15 +49,15 @@ const AdminDepartmentsPage: React.FC = () => {
const handleDelete = (record: Department) => { const handleDelete = (record: Department) => {
Modal.confirm({ Modal.confirm({
title: '纭鍒犻櫎', title: '确删除',
content: `纭畾瑕佸垹闄ょ瀹?"${record.name}" 鍚楋紵璇ユ搷浣滀笉鍙仮澶嶃€俙, content: `确畾瑕佸垹闄ょ瀹"${record.name}" 鍚楋紵璇ユ搷浣滀笉号仮澶嶃€俙,
onOk: async () => { onOk: async () => {
try { try {
await adminApi.deleteDepartment(record.id); await adminApi.deleteDepartment(record.id);
message.success('宸插垹闄?); message.success('已删除);
fetchDepartments(); fetchDepartments();
} catch (error) { } catch (error) {
message.error('鍒犻櫎澶辫触'); message.error('删除失败');
} }
}, },
}); });
...@@ -68,15 +68,15 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -68,15 +68,15 @@ const AdminDepartmentsPage: React.FC = () => {
try { try {
if (editingDept) { if (editingDept) {
await adminApi.updateDepartment(editingDept.id, values); await adminApi.updateDepartment(editingDept.id, values);
message.success('绉戝鏇存柊鎴愬姛'); message.success('种戝鏇存柊成功');
} else { } else {
await adminApi.createDepartment(values); await adminApi.createDepartment(values);
message.success('绉戝鍒涘缓鎴愬姛'); message.success('种戝鍒涘缓成功');
} }
setIsModalOpen(false); setIsModalOpen(false);
fetchDepartments(); fetchDepartments();
} catch (error) { } catch (error) {
message.error('鎿嶄綔澶辫触'); message.error('操作失败');
} finally { } finally {
setSaveLoading(false); setSaveLoading(false);
} }
...@@ -84,33 +84,33 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -84,33 +84,33 @@ const AdminDepartmentsPage: React.FC = () => {
const columns: ColumnsType<Department> = [ const columns: ColumnsType<Department> = [
{ {
title: '鍥炬爣', title: '图标',
dataIndex: 'icon', dataIndex: 'icon',
key: 'icon', key: 'icon',
width: 60, width: 60,
render: (icon: string) => <span style={{ fontSize: 24 }}>{icon || '馃彞'}</span>, render: (icon: string) => <span style={{ fontSize: 24 }}>{icon || '🏥'}</span>,
}, },
{ {
title: '绉戝鍚嶇О', title: '种戝鍚嶇О',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
render: (name: string) => <Text strong>{name}</Text>, render: (name: string) => <Text strong>{name}</Text>,
}, },
{ {
title: '鎺掑簭', title: '排序',
dataIndex: 'sort_order', dataIndex: 'sort_order',
key: 'sort_order', key: 'sort_order',
}, },
{ {
title: '鎿嶄綔', title: '操作',
key: 'action', key: 'action',
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
缂栬緫 编辑
</Button> </Button>
<Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}> <Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}>
鍒犻櫎 删除
</Button> </Button>
</Space> </Space>
), ),
...@@ -120,8 +120,8 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -120,8 +120,8 @@ const AdminDepartmentsPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">绉戝绠$悊</h4> <h4 className="text-sm font-bold text-gray-800 m-0">种戝绠$悊</h4>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>娣诲姞绉戝</Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加科室</Button>
</div> </div>
<Card size="small"> <Card size="small">
...@@ -136,7 +136,7 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -136,7 +136,7 @@ const AdminDepartmentsPage: React.FC = () => {
</Card> </Card>
<Modal <Modal
title={editingDept ? '缂栬緫绉戝' : '娣诲姞绉戝'} title={editingDept ? '编辑科室' : '添加科室'}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
onOk={() => form.submit()} onOk={() => form.submit()}
...@@ -144,13 +144,13 @@ const AdminDepartmentsPage: React.FC = () => { ...@@ -144,13 +144,13 @@ const AdminDepartmentsPage: React.FC = () => {
width={400} width={400}
> >
<Form form={form} onFinish={handleSave} layout="vertical" size="small"> <Form form={form} onFinish={handleSave} layout="vertical" size="small">
<Form.Item name="name" label="绉戝鍚嶇О" rules={[{ required: true, message: '璇疯緭鍏ョ瀹ゅ悕绉? }]}> <Form.Item name="name" label="种戝鍚嶇О" rules={[{ required: true, message: '请输入ョ瀹ゅ悕种 }]}>
<Input placeholder="璇疯緭鍏ョ瀹ゅ悕绉? /> <Input placeholder="请输入ョ瀹ゅ悕种 />
</Form.Item> </Form.Item>
<Form.Item name="icon" label="鍥炬爣"> <Form.Item name="icon" label="图标">
<Input placeholder="璇疯緭鍏moji鍥炬爣" /> <Input placeholder="请输入moji图标" />
</Form.Item> </Form.Item>
<Form.Item name="sort_order" label="鎺掑簭鍙? initialValue={1}> <Form.Item name="sort_order" label="排序号 initialValue={1}>
<InputNumber min={1} style={{ width: '100%' }} /> <InputNumber min={1} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Form> </Form>
......
...@@ -7,9 +7,9 @@ import { adminApi, type DoctorReviewItem } from '../../../api/admin'; ...@@ -7,9 +7,9 @@ import { adminApi, type DoctorReviewItem } from '../../../api/admin';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待审?, color: 'orange' }, pending: { text: '待审核, color: 'orange' },
approved: { text: '已通过', color: 'green' }, approved: { text: '已通过', color: 'green' },
rejected: { text: '已拒?, color: 'red' }, rejected: { text: '已拒绝, color: 'red' },
}; };
const AdminDoctorReviewPage: React.FC = () => { const AdminDoctorReviewPage: React.FC = () => {
...@@ -56,8 +56,8 @@ const AdminDoctorReviewPage: React.FC = () => { ...@@ -56,8 +56,8 @@ const AdminDoctorReviewPage: React.FC = () => {
icon: <CloseCircleOutlined style={{ color: '#ff4d4f' }} />, icon: <CloseCircleOutlined style={{ color: '#ff4d4f' }} />,
onOk: async () => { onOk: async () => {
try { try {
await adminApi.rejectDoctorReview(record.id, '资质不符合要?); await adminApi.rejectDoctorReview(record.id, '资质不符合要);
message.success('已拒?); message.success('已拒);
fetchReviews(); fetchReviews();
} catch (error) { } catch (error) {
message.error('操作失败'); message.error('操作失败');
...@@ -76,10 +76,10 @@ const AdminDoctorReviewPage: React.FC = () => { ...@@ -76,10 +76,10 @@ const AdminDoctorReviewPage: React.FC = () => {
<Descriptions.Item label="电话">{record.phone}</Descriptions.Item> <Descriptions.Item label="电话">{record.phone}</Descriptions.Item>
<Descriptions.Item label="执业证号">{record.license_no}</Descriptions.Item> <Descriptions.Item label="执业证号">{record.license_no}</Descriptions.Item>
<Descriptions.Item label="职称">{record.title}</Descriptions.Item> <Descriptions.Item label="职称">{record.title}</Descriptions.Item>
<Descriptions.Item label="所属医?>{record.hospital}</Descriptions.Item> <Descriptions.Item label="所属医>{record.hospital}</Descriptions.Item>
<Descriptions.Item label="科室">{record.department_name}</Descriptions.Item> <Descriptions.Item label="科室">{record.department_name}</Descriptions.Item>
<Descriptions.Item label="提交时间">{record.submitted_at}</Descriptions.Item> <Descriptions.Item label="提交时间">{record.submitted_at}</Descriptions.Item>
<Descriptions.Item label="审核状?> <Descriptions.Item label="审核状>
<Tag color={statusMap[record.status].color}> <Tag color={statusMap[record.status].color}>
{statusMap[record.status].text} {statusMap[record.status].text}
</Tag> </Tag>
...@@ -131,7 +131,7 @@ const AdminDoctorReviewPage: React.FC = () => { ...@@ -131,7 +131,7 @@ const AdminDoctorReviewPage: React.FC = () => {
key: 'submitted_at', key: 'submitted_at',
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => { render: (status: string) => {
......
...@@ -89,12 +89,12 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -89,12 +89,12 @@ const AdminDoctorsPage: React.FC = () => {
const handleReject = async () => { const handleReject = async () => {
if (!currentDoctor?.review_id || !rejectReason) { if (!currentDoctor?.review_id || !rejectReason) {
message.error('请输入拒绝原?); message.error('请输入拒绝原);
return; return;
} }
try { try {
await adminApi.rejectDoctorReview(currentDoctor.review_id, rejectReason); await adminApi.rejectDoctorReview(currentDoctor.review_id, rejectReason);
message.success('已拒?); message.success('已拒);
setReviewModalVisible(false); setReviewModalVisible(false);
setRejectReason(''); setRejectReason('');
fetchDoctors(); fetchDoctors();
...@@ -122,7 +122,7 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -122,7 +122,7 @@ const AdminDoctorsPage: React.FC = () => {
const handleResetPassword = (record: DoctorItem) => { const handleResetPassword = (record: DoctorItem) => {
Modal.confirm({ Modal.confirm({
title: '确认重置密码?, title: '确认重置密码码,
content: `将重置医?${record.name} 的密码为默认密码 123456`, content: `将重置医?${record.name} 的密码为默认密码 123456`,
onOk: async () => { onOk: async () => {
try { try {
...@@ -137,10 +137,10 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -137,10 +137,10 @@ const AdminDoctorsPage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
const statusMap: Record<string, { color: string; text: string }> = { const statusMap: Record<string, { color: string; text: string }> = {
not_submitted: { color: 'default', text: '未提交认? }, not_submitted: { color: 'default', text: '未提交认证 },
pending: { color: 'orange', text: '待审? }, pending: { color: 'orange', text: '待审核 },
approved: { color: 'green', text: '已认? }, approved: { color: 'green', text: '已认证 },
rejected: { color: 'red', text: '已拒? }, rejected: { color: 'red', text: '已拒绝 },
}; };
const config = statusMap[status] || { color: 'default', text: status }; const config = statusMap[status] || { color: 'default', text: status };
return <Tag color={config.color}>{config.text}</Tag>; return <Tag color={config.color}>{config.text}</Tag>;
...@@ -275,20 +275,20 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -275,20 +275,20 @@ const AdminDoctorsPage: React.FC = () => {
render: (hospital: string) => hospital || '-', render: (hospital: string) => hospital || '-',
}, },
{ {
title: '认证状?, title: '认证状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 120, width: 120,
render: (status: string) => getStatusTag(status), render: (status: string) => getStatusTag(status),
}, },
{ {
title: '账号状?, title: '账号状态,
dataIndex: 'user_status', dataIndex: 'user_status',
key: 'user_status', key: 'user_status',
width: 100, width: 100,
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -336,14 +336,14 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -336,14 +336,14 @@ const AdminDoctorsPage: React.FC = () => {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">医生管理</h4> <h4 className="text-sm font-bold text-gray-800 m-0">医生管理</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
placeholder="搜索姓名/手机? placeholder="搜索姓名/手机构
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
value={keyword} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
...@@ -384,10 +384,10 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -384,10 +384,10 @@ const AdminDoctorsPage: React.FC = () => {
width={500} width={500}
> >
<Form form={addForm} layout="vertical" size="small" onFinish={handleAddDoctor}> <Form form={addForm} layout="vertical" size="small" onFinish={handleAddDoctor}>
<Form.Item name="phone" label="手机? rules={[{ required: true, message: '请输入手机号' }]}> <Form.Item name="phone" label="手机构 rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机? /> <Input placeholder="手机构 />
</Form.Item> </Form.Item>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓名 }]}>
<Input placeholder="医生姓名" /> <Input placeholder="医生姓名" />
</Form.Item> </Form.Item>
<Form.Item name="gender" label="性别"> <Form.Item name="gender" label="性别">
...@@ -399,17 +399,17 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -399,17 +399,17 @@ const AdminDoctorsPage: React.FC = () => {
<Form.Item name="password" label="初始密码"> <Form.Item name="password" label="初始密码">
<Input.Password placeholder="默认 123456" /> <Input.Password placeholder="默认 123456" />
</Form.Item> </Form.Item>
<Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证? }]}> <Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证书 }]}>
<Input placeholder="执业证号" /> <Input placeholder="执业证号" />
</Form.Item> </Form.Item>
<Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}> <Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}>
<Input placeholder="如:主治医师" /> <Input placeholder="如:主治医师" />
</Form.Item> </Form.Item>
<Form.Item name="hospital" label="所属医? rules={[{ required: true, message: '请输入医? }]}> <Form.Item name="hospital" label="所属医院 rules={[{ required: true, message: '请输入医院 }]}>
<Input placeholder="所属医? /> <Input placeholder="所属医院 />
</Form.Item> </Form.Item>
<Form.Item name="introduction" label="简?> <Form.Item name="introduction" label="简>
<Input.TextArea rows={3} placeholder="医生简? /> <Input.TextArea rows={3} placeholder="医生简介 />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Space> <Space>
...@@ -433,16 +433,16 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -433,16 +433,16 @@ const AdminDoctorsPage: React.FC = () => {
<div> <div>
<Descriptions column={2} bordered> <Descriptions column={2} bordered>
<Descriptions.Item label="姓名">{currentDoctor.name}</Descriptions.Item> <Descriptions.Item label="姓名">{currentDoctor.name}</Descriptions.Item>
<Descriptions.Item label="手机?>{currentDoctor.phone}</Descriptions.Item> <Descriptions.Item label="手机>{currentDoctor.phone}</Descriptions.Item>
<Descriptions.Item label="执业证号">{currentDoctor.license_no}</Descriptions.Item> <Descriptions.Item label="执业证号">{currentDoctor.license_no}</Descriptions.Item>
<Descriptions.Item label="职称">{currentDoctor.title}</Descriptions.Item> <Descriptions.Item label="职称">{currentDoctor.title}</Descriptions.Item>
<Descriptions.Item label="所属医? span={2}>{currentDoctor.hospital}</Descriptions.Item> <Descriptions.Item label="所属医院 span={2}>{currentDoctor.hospital}</Descriptions.Item>
<Descriptions.Item label="科室" span={2}>{currentDoctor.department_name}</Descriptions.Item> <Descriptions.Item label="科室" span={2}>{currentDoctor.department_name}</Descriptions.Item>
</Descriptions> </Descriptions>
{(currentDoctor.license_image || currentDoctor.qualification_image) && ( {(currentDoctor.license_image || currentDoctor.qualification_image) && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<Text strong>证照材料?/Text> <Text strong>证照材料</Text>
<div style={{ marginTop: 8 }}> <div style={{ marginTop: 8 }}>
<Space> <Space>
{currentDoctor.license_image && ( {currentDoctor.license_image && (
...@@ -507,20 +507,20 @@ const AdminDoctorsPage: React.FC = () => { ...@@ -507,20 +507,20 @@ const AdminDoctorsPage: React.FC = () => {
width={600} width={600}
> >
<Form form={editForm} layout="vertical" onFinish={handleEditSubmit}> <Form form={editForm} layout="vertical" onFinish={handleEditSubmit}>
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓名 }]}>
<Input placeholder="医生姓名" /> <Input placeholder="医生姓名" />
</Form.Item> </Form.Item>
<Form.Item name="phone" label="手机? rules={[{ required: true, message: '请输入手机号' }]}> <Form.Item name="phone" label="手机构 rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机? /> <Input placeholder="手机构 />
</Form.Item> </Form.Item>
<Form.Item name="title" label="职称"> <Form.Item name="title" label="职称">
<Input placeholder="如:主治医师" /> <Input placeholder="如:主治医师" />
</Form.Item> </Form.Item>
<Form.Item name="hospital" label="所属医?> <Form.Item name="hospital" label="所属医>
<Input placeholder="所属医? /> <Input placeholder="所属医院 />
</Form.Item> </Form.Item>
<Form.Item name="introduction" label="简?> <Form.Item name="introduction" label="简>
<Input.TextArea rows={3} placeholder="医生简? /> <Input.TextArea rows={3} placeholder="医生简介 />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Space> <Space>
......
...@@ -30,7 +30,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -30,7 +30,7 @@ const AdminPatientsPage: React.FC = () => {
setPatients(res.data.list || []); setPatients(res.data.list || []);
setTotal(res.data.total || 0); setTotal(res.data.total || 0);
} catch (error) { } catch (error) {
message.error('获取患者列表失?); message.error('获取患者列表失);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -64,7 +64,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -64,7 +64,7 @@ const AdminPatientsPage: React.FC = () => {
const handleResetPassword = (record: UserInfo) => { const handleResetPassword = (record: UserInfo) => {
Modal.confirm({ Modal.confirm({
title: '确认重置密码?, title: '确认重置密码码,
content: `将重置患?${record.real_name || record.phone} 的密码为默认密码`, content: `将重置患?${record.real_name || record.phone} 的密码为默认密码`,
onOk: async () => { onOk: async () => {
try { try {
...@@ -126,7 +126,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -126,7 +126,7 @@ const AdminPatientsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('患者添加成?); message.success('患者添加成);
} else if (editingRecord) { } else if (editingRecord) {
await adminApi.updatePatient(editingRecord.id, { await adminApi.updatePatient(editingRecord.id, {
real_name: values.real_name, real_name: values.real_name,
...@@ -134,7 +134,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -134,7 +134,7 @@ const AdminPatientsPage: React.FC = () => {
gender: values.gender, gender: values.gender,
age: values.age, age: values.age,
}); });
message.success('患者信息更新成?); message.success('患者信息更新成功);
} }
setModalVisible(false); setModalVisible(false);
modalForm.resetFields(); modalForm.resetFields();
...@@ -150,13 +150,13 @@ const AdminPatientsPage: React.FC = () => { ...@@ -150,13 +150,13 @@ const AdminPatientsPage: React.FC = () => {
const columns: ColumnsType<UserInfo> = [ const columns: ColumnsType<UserInfo> = [
{ {
title: '患?, title: '患者,
key: 'patient', key: 'patient',
render: (_, record) => ( render: (_, record) => (
<Space> <Space>
<Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#1890ff' }} /> <Avatar icon={<UserOutlined />} src={record.avatar} style={{ backgroundColor: '#1890ff' }} />
<div> <div>
<Text strong>{record.real_name || '未填?}</Text> <Text strong>{record.real_name || '未填写}</Text>
<br /> <br />
<Text type="secondary" style={{ fontSize: 12 }}>{record.phone}</Text> <Text type="secondary" style={{ fontSize: 12 }}>{record.phone}</Text>
</div> </div>
...@@ -182,16 +182,16 @@ const AdminPatientsPage: React.FC = () => { ...@@ -182,16 +182,16 @@ const AdminPatientsPage: React.FC = () => {
dataIndex: 'is_verified', dataIndex: 'is_verified',
key: 'is_verified', key: 'is_verified',
render: (verified: boolean) => ( render: (verified: boolean) => (
<Tag color={verified ? 'green' : 'default'}>{verified ? '已认? : '未认?}</Tag> <Tag color={verified ? 'green' : 'default'}>{verified ? '已认证 : '未认证}</Tag>
), ),
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -232,15 +232,15 @@ const AdminPatientsPage: React.FC = () => { ...@@ -232,15 +232,15 @@ const AdminPatientsPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">患者管?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">患者管理</h4>
<span className="text-xs text-gray-400">?{total} 条记?/span> <span className="text-xs text-gray-400">?{total} 条记</span>
</div> </div>
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
placeholder="搜索姓名/手机? placeholder="搜索姓名/手机构
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
value={keyword} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
...@@ -249,7 +249,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -249,7 +249,7 @@ const AdminPatientsPage: React.FC = () => {
size="small" size="small"
/> />
<Select <Select
placeholder="状? placeholder="状态
allowClear allowClear
size="small" size="small"
value={statusFilter || undefined} value={statusFilter || undefined}
...@@ -257,13 +257,13 @@ const AdminPatientsPage: React.FC = () => { ...@@ -257,13 +257,13 @@ const AdminPatientsPage: React.FC = () => {
style={{ width: 100 }} style={{ width: 100 }}
options={[ options={[
{ label: '正常', value: 'active' }, { label: '正常', value: 'active' },
{ label: '已禁?, value: 'disabled' }, { label: '已禁用, value: 'disabled' },
]} ]}
/> />
<Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button> <Button size="small" type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
<Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setStatusFilter(''); setPage(1); fetchPatients(); }}>重置</Button> <Button size="small" icon={<ReloadOutlined />} onClick={() => { setKeyword(''); setStatusFilter(''); setPage(1); fetchPatients(); }}>重置</Button>
</div> </div>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加患?/Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加患</Button>
</div> </div>
</Card> </Card>
...@@ -286,7 +286,7 @@ const AdminPatientsPage: React.FC = () => { ...@@ -286,7 +286,7 @@ const AdminPatientsPage: React.FC = () => {
</Card> </Card>
<Modal <Modal
title={modalType === 'add' ? '添加患? : '编辑患?} title={modalType === 'add' ? '添加患者 : '编辑患}
open={modalVisible} open={modalVisible}
onOk={handleModalSubmit} onOk={handleModalSubmit}
onCancel={() => setModalVisible(false)} onCancel={() => setModalVisible(false)}
...@@ -295,11 +295,11 @@ const AdminPatientsPage: React.FC = () => { ...@@ -295,11 +295,11 @@ const AdminPatientsPage: React.FC = () => {
width={440} width={440}
> >
<Form form={modalForm} layout="vertical" size="small"> <Form form={modalForm} layout="vertical" size="small">
<Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓? }]}> <Form.Item name="real_name" label="姓名" rules={[{ required: true, message: '请输入姓名 }]}>
<Input placeholder="患者姓? /> <Input placeholder="患者姓名 />
</Form.Item> </Form.Item>
<Form.Item name="phone" label="手机? rules={[{ required: true, message: '请输入手机号' }]}> <Form.Item name="phone" label="手机构 rules={[{ required: true, message: '请输入手机号' }]}>
<Input placeholder="手机? /> <Input placeholder="手机构 />
</Form.Item> </Form.Item>
{modalType === 'add' && ( {modalType === 'add' && (
<Form.Item name="password" label="初始密码"> <Form.Item name="password" label="初始密码">
......
...@@ -17,16 +17,16 @@ import type { Medicine, MedicineStats } from '../../../api/prescription'; ...@@ -17,16 +17,16 @@ import type { Medicine, MedicineStats } from '../../../api/prescription';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const categoryOptions = [ const categoryOptions = [
{ label: '鎶楃敓绱?, value: '鎶楃敓绱? }, { label: '抗生素, value: '抗生素 },
{ label: '瑙g儹闀囩棝', value: '瑙g儹闀囩棝' }, { label: '解热镇痛', value: '解热镇痛' },
{ label: '闄嶇硸鑽?, value: '闄嶇硸鑽? }, { label: '降糖药, value: '降糖药 },
{ label: '闄嶅帇鑽?, value: '闄嶅帇鑽? }, { label: '降压药, value: '降压药 },
{ label: '娑堝寲绯荤粺', value: '娑堝寲绯荤粺' }, { label: '消化系统', value: '消化系统' },
{ label: '蹇冭绠?, value: '蹇冭绠? }, { label: '蹇冭绠, value: '蹇冭绠 },
{ label: '鍛煎惛绯荤粺', value: '鍛煎惛绯荤粺' }, { label: '呼吸系统', value: '呼吸系统' },
{ label: '缁寸敓绱?, value: '缁寸敓绱? }, { label: '维生素, value: '维生素 },
{ label: '涓垚鑽?, value: '涓垚鑽? }, { label: '涓垚鑽, value: '涓垚鑽 },
{ label: '鍏朵粬', value: '鍏朵粬' }, { label: '其他', value: '其他' },
]; ];
const AdminPharmacyPage: React.FC = () => { const AdminPharmacyPage: React.FC = () => {
...@@ -87,9 +87,9 @@ const AdminPharmacyPage: React.FC = () => { ...@@ -87,9 +87,9 @@ const AdminPharmacyPage: React.FC = () => {
const handleDeleteDrug = async (id: string) => { const handleDeleteDrug = async (id: string) => {
try { try {
await medicineApi.delete(id); await medicineApi.delete(id);
message.success('鑽搧宸插垹闄?); message.success('鑽搧已删除);
fetchList(); fetchStats(); fetchList(); fetchStats();
} catch (err: any) { message.error(err?.message || '鍒犻櫎澶辫触'); } } catch (err: any) { message.error(err?.message || '删除失败'); }
}; };
const handleSaveDrug = async () => { const handleSaveDrug = async () => {
...@@ -100,10 +100,10 @@ const AdminPharmacyPage: React.FC = () => { ...@@ -100,10 +100,10 @@ const AdminPharmacyPage: React.FC = () => {
await medicineApi.update(editingDrug.id, { await medicineApi.update(editingDrug.id, {
...values, price: priceInCents, ...values, price: priceInCents,
}); });
message.success('鑽搧宸叉洿鏂?); message.success('鑽搧宸叉洿鏂);
} else { } else {
await medicineApi.create({ ...values, price: priceInCents }); await medicineApi.create({ ...values, price: priceInCents });
message.success('鑽搧宸叉坊鍔?); message.success('鑽搧宸叉坊鍔);
} }
setDrugModalVisible(false); setDrugModalVisible(false);
fetchList(); fetchStats(); fetchList(); fetchStats();
...@@ -114,29 +114,29 @@ const AdminPharmacyPage: React.FC = () => { ...@@ -114,29 +114,29 @@ const AdminPharmacyPage: React.FC = () => {
const getStockTag = (status: string) => { const getStockTag = (status: string) => {
switch (status) { switch (status) {
case 'normal': return <Tag color="green">搴撳瓨鍏呰冻</Tag>; case 'normal': return <Tag color="green">库存充足</Tag>;
case 'low_stock': return <Tag color="orange" icon={<WarningOutlined />}>搴撳瓨棰勮</Tag>; case 'low_stock': return <Tag color="orange" icon={<WarningOutlined />}>库存预警</Tag>;
case 'out_of_stock': return <Tag color="red">缂鸿揣</Tag>; case 'out_of_stock': return <Tag color="red">缺货</Tag>;
default: return <Tag>{status}</Tag>; default: return <Tag>{status}</Tag>;
} }
}; };
const drugColumns = [ const drugColumns = [
{ title: '鑽搧鍚嶇О', dataIndex: 'name', key: 'name', width: 150, render: (v: string) => <Text strong>{v}</Text> }, { title: '鑽搧鍚嶇О', dataIndex: 'name', key: 'name', width: 150, render: (v: string) => <Text strong>{v}</Text> },
{ title: '閫氱敤鍚?, dataIndex: 'generic_name', key: 'generic_name', width: 120 }, { title: '通用名, dataIndex: 'generic_name', key: 'generic_name', width: 120 },
{ title: '瑙勬牸', dataIndex: 'specification', key: 'specification', width: 140 }, { title: '规格', dataIndex: 'specification', key: 'specification', width: 140 },
{ title: '绫诲埆', dataIndex: 'category', key: 'category', width: 80, render: (v: string) => <Tag>{v}</Tag> }, { title: '类别', dataIndex: 'category', key: 'category', width: 80, render: (v: string) => <Tag>{v}</Tag> },
{ title: '鍘傚晢', dataIndex: 'manufacturer', key: 'manufacturer', width: 100 }, { title: '厂商', dataIndex: 'manufacturer', key: 'manufacturer', width: 100 },
{ title: '浠锋牸', dataIndex: 'price', key: 'price', width: 80, render: (v: number) => `楼${(v / 100).toFixed(2)}` }, { title: '价格', dataIndex: 'price', key: 'price', width: 80, render: (v: number) => `楼${(v / 100).toFixed(2)}` },
{ title: '搴撳瓨', dataIndex: 'stock', key: 'stock', width: 80 }, { title: '库存', dataIndex: 'stock', key: 'stock', width: 80 },
{ title: '鐘舵€?, dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStockTag(v) }, { title: '状态, dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStockTag(v) },
{ {
title: '鎿嶄綔', key: 'action', width: 140, title: '操作', key: 'action', width: 140,
render: (_: any, record: Medicine) => ( render: (_: any, record: Medicine) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEditDrug(record)}>缂栬緫</Button> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEditDrug(record)}>编辑</Button>
<Popconfirm title="纭畾鍒犻櫎璇ヨ嵂鍝侊紵" onConfirm={() => handleDeleteDrug(record.id)}> <Popconfirm title="确畾删除璇ヨ嵂鍝侊紵" onConfirm={() => handleDeleteDrug(record.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />}>鍒犻櫎</Button> <Button type="link" size="small" danger icon={<DeleteOutlined />}>删除</Button>
</Popconfirm> </Popconfirm>
</Space> </Space>
), ),
...@@ -146,33 +146,33 @@ const AdminPharmacyPage: React.FC = () => { ...@@ -146,33 +146,33 @@ const AdminPharmacyPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-bold text-gray-800 m-0"> <h4 className="text-sm font-bold text-gray-800 m-0">
<MedicineBoxOutlined className="mr-1" />鑽搧搴撶鐞? </h4> <MedicineBoxOutlined className="mr-1" />鑽搧搴撶鐞 </h4>
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #1890ff' }}> <Card size="small" style={{ borderLeft: '3px solid #1890ff' }}>
<Statistic title="鑽搧鎬绘暟" value={stats.total_count} suffix="绉? /> <Statistic title="鑽搧鎬绘暟" value={stats.total_count} suffix="种 />
</Card> </Card>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #52c41a' }}> <Card size="small" style={{ borderLeft: '3px solid #52c41a' }}>
<Statistic title="搴撳瓨鍏呰冻" value={stats.normal_count} valueStyle={{ color: '#52c41a' }} suffix="绉? /> <Statistic title="库存充足" value={stats.normal_count} valueStyle={{ color: '#52c41a' }} suffix="种 />
</Card> </Card>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #fa8c16' }}> <Card size="small" style={{ borderLeft: '3px solid #fa8c16' }}>
<Statistic title="搴撳瓨棰勮" value={stats.low_stock_count} valueStyle={{ color: '#fa8c16' }} suffix="绉? /> <Statistic title="库存预警" value={stats.low_stock_count} valueStyle={{ color: '#fa8c16' }} suffix="种 />
</Card> </Card>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card size="small" style={{ borderLeft: '3px solid #f5222d' }}> <Card size="small" style={{ borderLeft: '3px solid #f5222d' }}>
<Statistic title="缂鸿揣鑽搧" value={stats.out_of_stock_count} valueStyle={{ color: '#f5222d' }} suffix="绉? /> <Statistic title="缺货鑽搧" value={stats.out_of_stock_count} valueStyle={{ color: '#f5222d' }} suffix="种 />
</Card> </Card>
</Col> </Col>
</Row> </Row>
{(stats.low_stock_count > 0 || stats.out_of_stock_count > 0) && ( {(stats.low_stock_count > 0 || stats.out_of_stock_count > 0) && (
<Alert message={`褰撳墠鏈?${stats.low_stock_count} 绉嶈嵂鍝佸簱瀛樹笉瓒筹紝${stats.out_of_stock_count} 绉嶈嵂鍝佺己璐э紝璇峰強鏃惰ˉ璐} <Alert message={`当前有?${stats.low_stock_count} 种药品库存不足,${stats.out_of_stock_count} 种药品缺货,请及时补货}
type="warning" showIcon className="text-xs!" /> type="warning" showIcon className="text-xs!" />
)} )}
...@@ -184,24 +184,24 @@ const AdminPharmacyPage: React.FC = () => { ...@@ -184,24 +184,24 @@ const AdminPharmacyPage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center justify-between gap-2 mb-2"> <div className="flex flex-wrap items-center justify-between gap-2 mb-2">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input prefix={<SearchOutlined />} placeholder="鎼滅储鑽搧" style={{ width: 180 }} size="small" allowClear <Input prefix={<SearchOutlined />} placeholder="搜索鑽搧" style={{ width: 180 }} size="small" allowClear
value={keyword} onChange={(e) => setKeyword(e.target.value)} onPressEnter={() => { setPage(1); fetchList(); }} /> value={keyword} onChange={(e) => setKeyword(e.target.value)} onPressEnter={() => { setPage(1); fetchList(); }} />
<Select placeholder="绫诲埆" style={{ width: 100 }} size="small" allowClear options={categoryOptions} <Select placeholder="类别" style={{ width: 100 }} size="small" allowClear options={categoryOptions}
value={filterCategory || undefined} onChange={(v) => { setFilterCategory(v || ''); setPage(1); }} /> value={filterCategory || undefined} onChange={(v) => { setFilterCategory(v || ''); setPage(1); }} />
<Select placeholder="搴撳瓨" style={{ width: 100 }} size="small" allowClear <Select placeholder="库存" style={{ width: 100 }} size="small" allowClear
value={filterStatus || undefined} onChange={(v) => { setFilterStatus(v || ''); setPage(1); }} value={filterStatus || undefined} onChange={(v) => { setFilterStatus(v || ''); setPage(1); }}
options={[{ label: '鍏呰冻', value: 'normal' }, { label: '棰勮', value: 'low_stock' }, { label: '缂鸿揣', value: 'out_of_stock' }]} /> options={[{ label: '充足', value: 'normal' }, { label: '预警', value: 'low_stock' }, { label: '缺货', value: 'out_of_stock' }]} />
</div> </div>
<Button type="primary" size="small" icon={<PlusOutlined />} onClick={handleAddDrug}>娣诲姞鑽搧</Button> <Button type="primary" size="small" icon={<PlusOutlined />} onClick={handleAddDrug}>添加鑽搧</Button>
</div> </div>
<Table columns={drugColumns} dataSource={drugs} rowKey="id" loading={loading} size="small" <Table columns={drugColumns} dataSource={drugs} rowKey="id" loading={loading} size="small"
pagination={{ current: page, total, pageSize: 10, size: 'small', onChange: setPage, showTotal: (t) => `鍏?${t} 绉峘 }} /> pagination={{ current: page, total, pageSize: 10, size: 'small', onChange: setPage, showTotal: (t) => `共?${t} 种峘 }} />
</Card> </Card>
), ),
}, },
{ {
key: 'stock', key: 'stock',
label: <Badge count={stats.low_stock_count + stats.out_of_stock_count} offset={[10, 0]}>搴撳瓨棰勮</Badge>, label: <Badge count={stats.low_stock_count + stats.out_of_stock_count} offset={[10, 0]}>库存预警</Badge>,
children: ( children: (
<Card size="small"> <Card size="small">
<Table columns={drugColumns} size="small" <Table columns={drugColumns} size="small"
...@@ -212,59 +212,59 @@ const AdminPharmacyPage: React.FC = () => { ...@@ -212,59 +212,59 @@ const AdminPharmacyPage: React.FC = () => {
}, },
]} /> ]} />
<Modal title={editingDrug ? '缂栬緫鑽搧' : '娣诲姞鑽搧'} open={drugModalVisible} <Modal title={editingDrug ? '编辑鑽搧' : '添加鑽搧'} open={drugModalVisible}
onOk={handleSaveDrug} onCancel={() => setDrugModalVisible(false)} okText="淇濆瓨" width={500}> onOk={handleSaveDrug} onCancel={() => setDrugModalVisible(false)} okText="保存" width={500}>
<Form form={form} layout="vertical" size="small"> <Form form={form} layout="vertical" size="small">
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="鑽搧鍚嶇О" name="name" rules={[{ required: true, message: '璇疯緭鍏ヨ嵂鍝佸悕绉? }]}> <Form.Item label="鑽搧鍚嶇О" name="name" rules={[{ required: true, message: '请输入药品名称 }]}>
<Input placeholder="璇疯緭鍏ヨ嵂鍝佸悕绉? /> <Input placeholder="请输入药品名称 />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="閫氱敤鍚? name="generic_name"> <Form.Item label="通用名 name="generic_name">
<Input placeholder="璇疯緭鍏ラ€氱敤鍚? /> <Input placeholder="请输入通用名 />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="瑙勬牸" name="specification" rules={[{ required: true, message: '璇疯緭鍏ヨ鏍? }]}> <Form.Item label="规格" name="specification" rules={[{ required: true, message: '请输入ヨ鏍 }]}>
<Input placeholder="濡? 0.5g脳24绮?鐩? /> <Input placeholder="如 0.5g×24粒/盒 />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="绫诲埆" name="category" rules={[{ required: true, message: '璇烽€夋嫨绫诲埆' }]}> <Form.Item label="类别" name="category" rules={[{ required: true, message: '请选择类别' }]}>
<Select placeholder="閫夋嫨绫诲埆" options={categoryOptions} /> <Select placeholder="选择类别" options={categoryOptions} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>
<Col span={8}> <Col span={8}>
<Form.Item label="鐢熶骇鍘傚晢" name="manufacturer"> <Form.Item label="生产厂商" name="manufacturer">
<Input placeholder="璇疯緭鍏ュ巶鍟? /> <Input placeholder="请输入厂商 />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Item label="鍗曚綅" name="unit"> <Form.Item label="单位" name="unit">
<Select placeholder="閫夋嫨鍗曚綅" options={[{ label: '鐩?, value: '鐩? }, { label: '鐡?, value: '鐡? }, { label: '琚?, value: '琚? }, { label: '鏀?, value: '鏀? }]} /> <Select placeholder="选择单位" options={[{ label: '盒, value: '盒 }, { label: '瓶, value: '瓶 }, { label: '袋, value: '袋 }, { label: '支, value: '支 }]} />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Item label="浠锋牸锛堝厓锛? name="price" rules={[{ required: true, message: '璇疯緭鍏ヤ环鏍? }]}> <Form.Item label="价格(元) name="price" rules={[{ required: true, message: '请输入价格 }]}>
<InputNumber style={{ width: '100%' }} min={0} precision={2} placeholder="0.00" /> <InputNumber style={{ width: '100%' }} min={0} precision={2} placeholder="0.00" />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="搴撳瓨鏁伴噺" name="stock"> <Form.Item label="库存数量" name="stock">
<InputNumber style={{ width: '100%' }} min={0} placeholder="0" /> <InputNumber style={{ width: '100%' }} min={0} placeholder="0" />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="搴撳瓨棰勮绾? name="stock_warning"> <Form.Item label="库存预警绾 name="stock_warning">
<InputNumber style={{ width: '100%' }} min={0} placeholder="浣庝簬姝ゆ暟閲忛璀? /> <InputNumber style={{ width: '100%' }} min={0} placeholder="浣庝簬姝ゆ暟閲忛璀 />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
......
...@@ -76,7 +76,7 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -76,7 +76,7 @@ const AdminPrescriptionPage: React.FC = () => {
if (!currentRecord) return; if (!currentRecord) return;
try { try {
await prescriptionAdminApi.review(currentRecord.id, { action: 'reject', reason: rejectReason }); await prescriptionAdminApi.review(currentRecord.id, { action: 'reject', reason: rejectReason });
message.success('处方已驳?); message.success('处方已驳);
setRejectModalVisible(false); setRejectModalVisible(false);
setDetailVisible(false); setDetailVisible(false);
setRejectReason(''); setRejectReason('');
...@@ -95,7 +95,7 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -95,7 +95,7 @@ const AdminPrescriptionPage: React.FC = () => {
const columns = [ const columns = [
{ title: '处方编号', dataIndex: 'prescription_no', key: 'prescription_no', width: 160 }, { title: '处方编号', dataIndex: 'prescription_no', key: 'prescription_no', width: 160 },
{ title: '患?, dataIndex: 'patient_name', key: 'patient_name', width: 80 }, { title: '患者, dataIndex: 'patient_name', key: 'patient_name', width: 80 },
{ title: '医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 80 }, { title: '医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 80 },
{ title: '诊断', dataIndex: 'diagnosis', key: 'diagnosis', width: 140, ellipsis: true }, { title: '诊断', dataIndex: 'diagnosis', key: 'diagnosis', width: 140, ellipsis: true },
{ {
...@@ -108,9 +108,9 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -108,9 +108,9 @@ const AdminPrescriptionPage: React.FC = () => {
), ),
}, },
{ title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100, render: (v: number) => `¥${(v / 100).toFixed(2)}` }, { title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100, render: (v: number) => `¥${(v / 100).toFixed(2)}` },
{ title: '状?, dataIndex: 'warning_level', key: 'warning_level', width: 100, render: (v: string) => getWarningTag(v) }, { title: '状态, dataIndex: 'warning_level', key: 'warning_level', width: 100, render: (v: string) => getWarningTag(v) },
{ {
title: '开方时?, dataIndex: 'created_at', key: 'created_at', width: 160, title: '开方时, dataIndex: 'created_at', key: 'created_at', width: 160,
render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-', render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-',
}, },
{ {
...@@ -207,12 +207,12 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -207,12 +207,12 @@ const AdminPrescriptionPage: React.FC = () => {
<div> <div>
<Descriptions bordered size="small" column={2}> <Descriptions bordered size="small" column={2}>
<Descriptions.Item label="处方编号">{currentRecord.prescription_no}</Descriptions.Item> <Descriptions.Item label="处方编号">{currentRecord.prescription_no}</Descriptions.Item>
<Descriptions.Item label="状?>{getWarningTag(currentRecord.warning_level)}</Descriptions.Item> <Descriptions.Item label="状>{getWarningTag(currentRecord.warning_level)}</Descriptions.Item>
<Descriptions.Item label="患?>{currentRecord.patient_name}</Descriptions.Item> <Descriptions.Item label="患>{currentRecord.patient_name}</Descriptions.Item>
<Descriptions.Item label="医生">{currentRecord.doctor_name}</Descriptions.Item> <Descriptions.Item label="医生">{currentRecord.doctor_name}</Descriptions.Item>
<Descriptions.Item label="诊断" span={2}>{currentRecord.diagnosis}</Descriptions.Item> <Descriptions.Item label="诊断" span={2}>{currentRecord.diagnosis}</Descriptions.Item>
<Descriptions.Item label="金额">¥{(currentRecord.total_amount / 100).toFixed(2)}</Descriptions.Item> <Descriptions.Item label="金额">¥{(currentRecord.total_amount / 100).toFixed(2)}</Descriptions.Item>
<Descriptions.Item label="开方时?>{new Date(currentRecord.created_at).toLocaleString('zh-CN')}</Descriptions.Item> <Descriptions.Item label="开方时>{new Date(currentRecord.created_at).toLocaleString('zh-CN')}</Descriptions.Item>
</Descriptions> </Descriptions>
<Card title={<><MedicineBoxOutlined /> 药品明细</>} size="small" style={{ marginTop: 16, borderRadius: 8 }}> <Card title={<><MedicineBoxOutlined /> 药品明细</>} size="small" style={{ marginTop: 16, borderRadius: 8 }}>
{(currentRecord.items || []).map((item: PrescriptionItem, idx: number) => ( {(currentRecord.items || []).map((item: PrescriptionItem, idx: number) => (
...@@ -236,7 +236,7 @@ const AdminPrescriptionPage: React.FC = () => { ...@@ -236,7 +236,7 @@ const AdminPrescriptionPage: React.FC = () => {
<Modal title="驳回处方" open={rejectModalVisible} onCancel={() => { setRejectModalVisible(false); setRejectReason(''); }} <Modal title="驳回处方" open={rejectModalVisible} onCancel={() => { setRejectModalVisible(false); setRejectReason(''); }}
onOk={handleReject} okText="确认驳回" okButtonProps={{ danger: true }}> onOk={handleReject} okText="确认驳回" okButtonProps={{ danger: true }}>
<Input.TextArea rows={3} placeholder="请输入驳回原?.." value={rejectReason} onChange={(e) => setRejectReason(e.target.value)} /> <Input.TextArea rows={3} placeholder="请输入驳回原.." value={rejectReason} onChange={(e) => setRejectReason(e.target.value)} />
</Modal> </Modal>
</div> </div>
); );
......
...@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => { ...@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => {
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 12.5%</span>} /></Card> suffix={<span className="text-xs text-green-500"><RiseOutlined /> 12.5%</span>} /></Card>
</Col> </Col>
<Col xs={12} sm={6}> <Col xs={12} sm={6}>
<Card size="small"><Statistic title="问诊? value={1280} prefix={<MessageOutlined />} <Card size="small"><Statistic title="问诊量 value={1280} prefix={<MessageOutlined />}
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 8.3%</span>} /></Card> suffix={<span className="text-xs text-green-500"><RiseOutlined /> 8.3%</span>} /></Card>
</Col> </Col>
<Col xs={12} sm={6}> <Col xs={12} sm={6}>
<Card size="small"><Statistic title="总收? value={56800} prefix={<DollarOutlined />} precision={0} <Card size="small"><Statistic title="总收入 value={56800} prefix={<DollarOutlined />} precision={0}
suffix={<span className="text-xs text-green-500"><RiseOutlined /> 15.2%</span>} /></Card> suffix={<span className="text-xs text-green-500"><RiseOutlined /> 15.2%</span>} /></Card>
</Col> </Col>
<Col xs={12} sm={6}> <Col xs={12} sm={6}>
......
...@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo { ...@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo {
} }
const roleMap: Record<string, { text: string; color: string }> = { const roleMap: Record<string, { text: string; color: string }> = {
patient: { text: '患?, color: 'blue' }, patient: { text: '患者, color: 'blue' },
doctor: { text: '医生', color: 'green' }, doctor: { text: '医生', color: 'green' },
admin: { text: '管理?, color: 'purple' }, admin: { text: '管理员, color: 'purple' },
}; };
const AdminUsersPage: React.FC = () => { const AdminUsersPage: React.FC = () => {
...@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => { ...@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => {
onOk: async () => { onOk: async () => {
try { try {
await adminApi.resetUserPassword(userId); await adminApi.resetUserPassword(userId);
message.success('密码已重?); message.success('密码已重);
} catch (error) { } catch (error) {
message.error('重置密码失败'); message.error('重置密码失败');
} }
...@@ -115,15 +115,15 @@ const AdminUsersPage: React.FC = () => { ...@@ -115,15 +115,15 @@ const AdminUsersPage: React.FC = () => {
title: '实名认证', title: '实名认证',
dataIndex: 'is_verified', dataIndex: 'is_verified',
key: 'is_verified', key: 'is_verified',
render: (v: boolean) => v ? <Tag color="success">已认?/Tag> : <Tag color="warning">未认?/Tag>, render: (v: boolean) => v ? <Tag color="success">已认证</Tag> : <Tag color="warning">未认证</Tag>,
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => ( render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}> <Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? '正常' : '已禁?} {status === 'active' ? '正常' : '已禁}
</Tag> </Tag>
), ),
}, },
...@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => { ...@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input placeholder="搜索用户?手机? prefix={<SearchOutlined />} value={keyword} <Input placeholder="搜索用户?手机构 prefix={<SearchOutlined />} value={keyword}
onChange={(e) => setKeyword(e.target.value)} style={{ width: 180 }} size="small" /> onChange={(e) => setKeyword(e.target.value)} style={{ width: 180 }} size="small" />
<Select placeholder="角色" style={{ width: 100 }} size="small" allowClear value={roleFilter} <Select placeholder="角色" style={{ width: 100 }} size="small" allowClear value={roleFilter}
onChange={(v) => setRoleFilter(v)} options={[ onChange={(v) => setRoleFilter(v)} options={[
{ label: '患?, value: 'patient' }, { label: '医生', value: 'doctor' }, { label: '管理?, value: 'admin' }, { label: '患者, value: 'patient' }, { label: '医生', value: 'doctor' }, { label: '管理员, value: 'admin' },
]} /> ]} />
<Select placeholder="状? style={{ width: 100 }} size="small" allowClear <Select placeholder="状态 style={{ width: 100 }} size="small" allowClear
options={[{ label: '正常', value: 'active' }, { label: '已禁?, value: 'disabled' }]} /> options={[{ label: '正常', value: 'active' }, { label: '已禁用, value: 'disabled' }]} />
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button> <Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
</div> </div>
</Card> </Card>
......
...@@ -45,23 +45,23 @@ const AIAssistPage: React.FC = () => { ...@@ -45,23 +45,23 @@ const AIAssistPage: React.FC = () => {
const handleAnalyze = async () => { const handleAnalyze = async () => {
if (!symptoms.trim()) return; if (!symptoms.trim()) return;
setLoading(true); setLoading(true);
// TODO: 璋冪敤鍚庣 AI 璇婃柇鎺ュ彛 // TODO: 璋冪敤鍚庣 AI 诊断鎺ュ彛
// 妯℃嫙鏁版嵁 // 模拟数据
setTimeout(() => { setTimeout(() => {
setDiagnoses([ setDiagnoses([
{ disease: '鎬ユ€т笂鍛煎惛閬撴劅鏌?, probability: 85, description: '鐢辩梾姣掑紩璧风殑涓婂懠鍚搁亾鐐庣棁锛屽父瑙佺棁鐘朵负榧诲銆佹祦娑曘€佸捊鐥涖€佸挸鍡界瓑', icd_code: 'J06.9' }, { disease: '急性上呼吸道感染, probability: 85, description: '男辩梾姣掑紩璧风殑涓婂懠鍚搁亾鐐庣棁锛屽父瑙佺棁鐘朵负榧诲銆佹祦娑曘€佸捊鐥涖€佸挸鍡界瓑', icd_code: 'J06.9' },
{ disease: '鎬ユ€ф敮姘旂鐐?, probability: 45, description: '鏀皵绠¢粡鑶滅殑鎬ユ€х値鐥囷紝甯哥户鍙戜簬涓婂懠鍚搁亾鎰熸煋', icd_code: 'J20.9' }, { disease: '鎬ユ€ф敮姘旂鐐, probability: 45, description: '支皵绠¢粡鑶滅殑鎬ユ€х値鐥囷紝甯哥户号戜簬涓婂懠鍚搁亾鎰熸煋', icd_code: 'J20.9' },
{ disease: '杩囨晱鎬ч蓟鐐?, probability: 30, description: '榧婚粡鑶滈潪鎰熸煋鎬х値鎬х柧鐥咃紝浠ラ樀鍙戞€у柗鍤忋€佹竻姘存牱榧绘稌绛変负鐗瑰緛', icd_code: 'J30.4' }, { disease: '过敏性鼻炎, probability: 30, description: '鼻黏膜非感染性炎性疾病,以阵发性喷嚏、清水样鼻涕等为特征', icd_code: 'J30.4' },
]); ]);
setSimilarCases([ setSimilarCases([
{ id: '1', summary: '鎮h€咃紝鐢凤紝35宀侊紝鍜冲椊銆佹祦娑?澶╋紝浣撴俯37.8掳C', diagnosis: '鎬ユ€т笂鍛煎惛閬撴劅鏌?, treatment: '瀵圭棁娌荤枟锛屽彛鏈嶅竷娲涜姮閫€鐑紝姘ㄦ捍绱㈠寲鐥?, similarity: 92 }, { id: '1', summary: '患者,男,35岁,咳嗽、流涕5天,体温37.8°C', diagnosis: '急性上呼吸道感染, treatment: '瀵圭棁治疗:屽彛鏈嶅竷娲涜姮閫€鐑紝姘ㄦ捍绱㈠寲鐥, similarity: 92 },
{ id: '2', summary: '鎮h€咃紝濂筹紝28宀侊紝鍜界棝銆侀蓟濉?澶╋紝浼磋交寰挸鍡?, diagnosis: '鎬ユ€т笂鍛煎惛閬撴劅鏌?, treatment: '鍙f湇娓呭紑鐏甸绮掞紝鍚湇瑗跨摐闇滃惈鐗?, similarity: 87 }, { id: '2', summary: '患者咃紝女筹紝28岁侊紝鍜界棝銆侀蓟濉?澶╋紝浼磋交寰挸鍡, diagnosis: '急性上呼吸道感染, treatment: '口服娓呭紑鐏甸绮掞紝鍚湇瑗跨摐闇滃惈鐗, similarity: 87 },
{ id: '3', summary: '鎮h€咃紝鐢凤紝42宀侊紝鍜冲椊1鍛紝鐥版恫榛勮壊', diagnosis: '鎬ユ€ф敮姘旂鐐?, treatment: '鍙f湇闃胯帿瑗挎灄+姘ㄦ捍绱紝闆惧寲鍚稿叆甯冨湴濂堝痉', similarity: 75 }, { id: '3', summary: '患者咃紝男凤紝42岁侊紝鍜冲椊1鍛紝鐥版恫榛勮壊', diagnosis: '鎬ユ€ф敮姘旂鐐, treatment: '口服闃胯帿瑗挎灄+姘ㄦ捍绱紝雾化吸入甯冨湴女堝痉', similarity: 75 },
]); ]);
setDrugSuggestions([ setDrugSuggestions([
{ name: '甯冩礇鑺紦閲婅兌鍥?, specification: '0.3g/绮?, usage: '鍙f湇', dosage: '姣忔1绮掞紝姣忔棩2娆?, caution: '鑳冩簝鐤℃偅鑰呮厧鐢? }, { name: '甯冩礇鑺紦閲婅兌鍥, specification: '0.3g/粒, usage: '口服', dosage: '姣忔1绮掞紝每日2次, caution: '胃溃疡患者慎用 },
{ name: '姘ㄦ捍绱㈠彛鏈嶆恫', specification: '100ml/鐡?, usage: '鍙f湇', dosage: '姣忔10ml锛屾瘡鏃?娆?, caution: '瀛曟棭鏈熺鐢? }, { name: '氨溴索口服液', specification: '100ml/瓶, usage: '口服', dosage: '姣忔10ml锛屾瘡无娆, caution: '瀛曟棭鏈熺男 },
{ name: '瀵逛箼閰版皑鍩洪厷鐗?, specification: '0.5g/鐗?, usage: '鍙f湇', dosage: '姣忔1鐗囷紝鍙戠儹鏃舵湇鐢紝闂撮殧4-6灏忔椂', caution: '鑲濆姛鑳戒笉鍏ㄨ€呮厧鐢? }, { name: '瀵逛箼閰版皑鍩洪厷鐗, specification: '0.5g/片, usage: '口服', dosage: '姣忔1鐗囷紝号戠儹无舵湇男紝闂撮殧4-6小时', caution: '肝功能不全者慎用 },
]); ]);
setLoading(false); setLoading(false);
}, 1500); }, 1500);
...@@ -76,26 +76,26 @@ const AIAssistPage: React.FC = () => { ...@@ -76,26 +76,26 @@ const AIAssistPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-bold text-gray-800 m-0"> <h4 className="text-sm font-bold text-gray-800 m-0">
<RobotOutlined className="mr-1" />AI 杈呭姪璇婃柇 <RobotOutlined className="mr-1" />AI 辅助诊断
</h4> </h4>
<Alert message="AI 杈呭姪璇婃柇浠呬緵鍙傝€冿紝鏈€缁堣瘖鏂浠ュ尰鐢熶复搴婂垽鏂负鍑? type="info" showIcon className="text-xs!" /> <Alert message="AI 辅助诊断浠呬緵号傝€冿紝鏈€缁堣瘖鏂浠ュ尰男熶复搴婂垽鏂负鍑 type="info" showIcon className="text-xs!" />
<Card size="small"> <Card size="small">
<div className="text-xs font-semibold mb-1">鐥囩姸鎻忚堪</div> <div className="text-xs font-semibold mb-1">症状描述</div>
<TextArea rows={3} placeholder="璇疯緭鍏ユ偅鑰呯殑涓昏瘔鍜岀棁鐘舵弿杩?.." value={symptoms} <TextArea rows={3} placeholder="请输入患者的主诉和症状描述.." value={symptoms}
onChange={(e) => setSymptoms(e.target.value)} className="mb-2" size="small" /> onChange={(e) => setSymptoms(e.target.value)} className="mb-2" size="small" />
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleAnalyze} <Button type="primary" size="small" icon={<SearchOutlined />} onClick={handleAnalyze}
loading={loading} style={{ background: '#52c41a', borderColor: '#52c41a' }}>AI 鏅鸿兘鍒嗘瀽</Button> loading={loading} style={{ background: '#52c41a', borderColor: '#52c41a' }}>AI 智能分析</Button>
</Card> </Card>
{loading ? ( {loading ? (
<div className="text-center py-10"><Spin tip="AI 姝e湪鍒嗘瀽涓?.." /></div> <div className="text-center py-10"><Spin tip="AI 正在分析中.." /></div>
) : ( ) : (
<Tabs defaultActiveKey="diagnosis" size="small" items={[ <Tabs defaultActiveKey="diagnosis" size="small" items={[
{ {
key: 'diagnosis', key: 'diagnosis',
label: <span><ExperimentOutlined /> 閴村埆璇婃柇</span>, label: <span><ExperimentOutlined /> 閴村埆诊断</span>,
children: diagnoses.length > 0 ? ( children: diagnoses.length > 0 ? (
<List size="small" dataSource={diagnoses} renderItem={(item) => ( <List size="small" dataSource={diagnoses} renderItem={(item) => (
<Card size="small" className="mb-2" hoverable> <Card size="small" className="mb-2" hoverable>
...@@ -110,25 +110,25 @@ const AIAssistPage: React.FC = () => { ...@@ -110,25 +110,25 @@ const AIAssistPage: React.FC = () => {
<Col> <Col>
<div className="text-center"> <div className="text-center">
<div className="text-xl font-bold" style={{ color: getProbabilityColor(item.probability) }}>{item.probability}%</div> <div className="text-xl font-bold" style={{ color: getProbabilityColor(item.probability) }}>{item.probability}%</div>
<Text type="secondary" className="text-[11px]!">鍖归厤搴?/Text> <Text type="secondary" className="text-[11px]!">匹配度</Text>
</div> </div>
</Col> </Col>
</Row> </Row>
</Card> </Card>
)} /> )} />
) : <Empty description="璇疯緭鍏ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽" />, ) : <Empty description="请输入ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽" />,
}, },
{ {
key: 'cases', key: 'cases',
label: <span><FileTextOutlined /> 鐩镐技鐥呬緥</span>, label: <span><FileTextOutlined /> 相似病例</span>,
children: similarCases.length > 0 ? ( children: similarCases.length > 0 ? (
<List size="small" dataSource={similarCases} renderItem={(item) => ( <List size="small" dataSource={similarCases} renderItem={(item) => (
<Card size="small" className="mb-2" hoverable> <Card size="small" className="mb-2" hoverable>
<Row gutter={8}> <Row gutter={8}>
<Col flex="auto"> <Col flex="auto">
<div className="text-xs mb-0.5"><Text strong>鎽樿锛?/Text>{item.summary}</div> <div className="text-xs mb-0.5"><Text strong>鎽樿锛</Text>{item.summary}</div>
<div className="text-xs mb-0.5"><Text strong>璇婃柇锛?/Text><Tag color="blue">{item.diagnosis}</Tag></div> <div className="text-xs mb-0.5"><Text strong>诊断:</Text><Tag color="blue">{item.diagnosis}</Tag></div>
<div className="text-xs"><Text strong>娌荤枟锛?/Text>{item.treatment}</div> <div className="text-xs"><Text strong>治疗:</Text>{item.treatment}</div>
</Col> </Col>
<Col> <Col>
<Tag color={item.similarity >= 85 ? 'red' : 'orange'}>{item.similarity}%</Tag> <Tag color={item.similarity >= 85 ? 'red' : 'orange'}>{item.similarity}%</Tag>
...@@ -136,11 +136,11 @@ const AIAssistPage: React.FC = () => { ...@@ -136,11 +136,11 @@ const AIAssistPage: React.FC = () => {
</Row> </Row>
</Card> </Card>
)} /> )} />
) : <Empty description="璇疯緭鍏ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽" />, ) : <Empty description="请输入ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽" />,
}, },
{ {
key: 'drugs', key: 'drugs',
label: <span><MedicineBoxOutlined /> 鐢ㄨ嵂寤鸿</span>, label: <span><MedicineBoxOutlined /> 用药建议</span>,
children: drugSuggestions.length > 0 ? ( children: drugSuggestions.length > 0 ? (
<List size="small" dataSource={drugSuggestions} renderItem={(item) => ( <List size="small" dataSource={drugSuggestions} renderItem={(item) => (
<Card size="small" className="mb-2" hoverable> <Card size="small" className="mb-2" hoverable>
...@@ -150,11 +150,11 @@ const AIAssistPage: React.FC = () => { ...@@ -150,11 +150,11 @@ const AIAssistPage: React.FC = () => {
<div className="text-xs mb-0.5"><Tag color="green">{item.usage}</Tag>{item.dosage}</div> <div className="text-xs mb-0.5"><Tag color="green">{item.usage}</Tag>{item.dosage}</div>
<div className="text-xs"><BulbOutlined className="text-yellow-500 mr-0.5" /><Text type="warning">{item.caution}</Text></div> <div className="text-xs"><BulbOutlined className="text-yellow-500 mr-0.5" /><Text type="warning">{item.caution}</Text></div>
</Col> </Col>
<Col><Button type="primary" ghost size="small">娣诲姞澶勬柟</Button></Col> <Col><Button type="primary" ghost size="small">添加处方</Button></Col>
</Row> </Row>
</Card> </Card>
)} /> )} />
) : <Empty description="璇疯緭鍏ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽" />, ) : <Empty description="请输入ョ棁鐘舵弿杩板悗杩涜鍒嗘瀽" />,
}, },
]} /> ]} />
)} )}
......
...@@ -94,10 +94,10 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -94,10 +94,10 @@ const DoctorCertificationPage: React.FC = () => {
<div className="w-14 h-14 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4"> <div className="w-14 h-14 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4">
<CheckCircleOutlined className="text-2xl text-green-500" /> <CheckCircleOutlined className="text-2xl text-green-500" />
</div> </div>
<h3 className="text-base font-bold mb-2">认证申请已提?/h3> <h3 className="text-base font-bold mb-2">认证申请已提</h3>
<Paragraph type="secondary" className="text-xs!"> <Paragraph type="secondary" className="text-xs!">
您的资质认证申请已成功提交,管理员将?1-3 个工作日内完成审核? </Paragraph> 您的资质认证申请已成功提交,管理员将?1-3 个工作日内完成审核实 </Paragraph>
<Button type="primary" size="small" onClick={() => router.push('/doctor/workbench')}>返回工作?/Button> <Button type="primary" size="small" onClick={() => router.push('/doctor/workbench')}>返回工作台</Button>
</div> </div>
</Card> </Card>
</div> </div>
...@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => {
<Card size="small"> <Card size="small">
<Form form={form} layout="vertical" size="small" onFinish={handleSubmit}> <Form form={form} layout="vertical" size="small" onFinish={handleSubmit}>
<Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证? }]}> <Form.Item name="license_no" label="执业证号" rules={[{ required: true, message: '请输入执业证书 }]}>
<Input placeholder="请输入医师执业证? /> <Input placeholder="请输入医师执业证书 />
</Form.Item> </Form.Item>
<Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}> <Form.Item name="title" label="职称" rules={[{ required: true, message: '请选择职称' }]}>
<Select placeholder="请选择职称"> <Select placeholder="请选择职称">
<Select.Option value="主任医师">主任医师</Select.Option> <Select.Option value="主任医师">主任医师</Select.Option>
<Select.Option value="副主任医?>副主任医?/Select.Option> <Select.Option value="副主任医>副主任医院/Select.Option>
<Select.Option value="主治医师">主治医师</Select.Option> <Select.Option value="主治医师">主治医师</Select.Option>
<Select.Option value="住院医师">住院医师</Select.Option> <Select.Option value="住院医师">住院医师</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item name="hospital" label="所属医? rules={[{ required: true, message: '请输入所属医? }]}> <Form.Item name="hospital" label="所属医院 rules={[{ required: true, message: '请输入所属医院 }]}>
<Input placeholder="请输入您所在的医院全称" /> <Input placeholder="请输入您所在的医院全称" />
</Form.Item> </Form.Item>
<Form.Item name="department_name" label="科室" rules={[{ required: true, message: '请选择科室' }]}> <Form.Item name="department_name" label="科室" rules={[{ required: true, message: '请选择科室' }]}>
...@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => {
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item label="执业证照? required> <Form.Item label="执业证照片 required>
<Upload <Upload
listType="picture-card" listType="picture-card"
fileList={licenseFileList} fileList={licenseFileList}
...@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => {
maxCount={1} maxCount={1}
> >
{licenseFileList.length === 0 && ( {licenseFileList.length === 0 && (
<div><UploadOutlined /><div className="mt-1 text-xs">上传执业?/div></div> <div><UploadOutlined /><div className="mt-1 text-xs">上传执业</div></div>
)} )}
</Upload> </Upload>
<Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text> <Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text>
</Form.Item> </Form.Item>
<Form.Item label="资格证照? required> <Form.Item label="资格证照片 required>
<Upload <Upload
listType="picture-card" listType="picture-card"
fileList={qualificationFileList} fileList={qualificationFileList}
...@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => { ...@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => {
maxCount={1} maxCount={1}
> >
{qualificationFileList.length === 0 && ( {qualificationFileList.length === 0 && (
<div><UploadOutlined /><div className="mt-1 text-xs">上传资格?/div></div> <div><UploadOutlined /><div className="mt-1 text-xs">上传资格</div></div>
)} )}
</Upload> </Upload>
<Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text> <Text type="secondary" className="text-[11px]!">支持 JPG、PNG,不超过 5MB</Text>
......
...@@ -41,8 +41,8 @@ const mockData: ChronicPrescription[] = [ ...@@ -41,8 +41,8 @@ const mockData: ChronicPrescription[] = [
last_prescription_date: '2026-01-25', last_prescription_date: '2026-01-25',
ai_draft: { ai_draft: {
drugs: [ drugs: [
{ name: '二甲双胍缓释?, spec: '0.5g/?, dosage: '每次1?, frequency: '每日2?, days: 30 }, { name: '二甲双胍缓释放, spec: '0.5g/?, dosage: '每次1?, frequency: '每日2?, days: 30 },
{ name: '格列美脲?, spec: '2mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 }, { name: '格列美脲类, spec: '2mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
], ],
note: '患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%', note: '患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%',
}, },
...@@ -54,14 +54,14 @@ const mockData: ChronicPrescription[] = [ ...@@ -54,14 +54,14 @@ const mockData: ChronicPrescription[] = [
patient_name: '李四', patient_name: '李四',
patient_age: 65, patient_age: 65,
patient_gender: '?, patient_gender: '?,
disease: '高血?, disease: '高血压,
last_prescription_date: '2026-01-20', last_prescription_date: '2026-01-20',
ai_draft: { ai_draft: {
drugs: [ drugs: [
{ name: '氨氯地平?, spec: '5mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 }, { name: '氨氯地平均, spec: '5mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
{ name: '缬沙坦胶?, spec: '80mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 }, { name: '缬沙坦胶囊, spec: '80mg/?, dosage: '每次1?, frequency: '每日1?, days: 30 },
], ],
note: '患者血压控制稳定,建议继续联合用药方案。近期血? 130/85mmHg', note: '患者血压控制稳定,建议继续联合用药方案。近期血压 130/85mmHg',
}, },
status: 'pending', status: 'pending',
submitted_at: '2026-02-25 10:15:00', submitted_at: '2026-02-25 10:15:00',
...@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => {
const handleApprove = (id: string) => { const handleApprove = (id: string) => {
setData(data.map((d) => (d.id === id ? { ...d, status: 'approved' as const } : d))); setData(data.map((d) => (d.id === id ? { ...d, status: 'approved' as const } : d)));
message.success('续方已审核通过并签名发?); message.success('续方已审核通过并签名发送);
setDetailModalVisible(false); setDetailModalVisible(false);
}; };
...@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => { ...@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => {
Modal.confirm({ Modal.confirm({
title: '拒绝续方申请', title: '拒绝续方申请',
content: ( content: (
<TextArea placeholder="请输入拒绝理?.." rows={3} /> <TextArea placeholder="请输入拒绝理.." rows={3} />
), ),
onOk: () => { onOk: () => {
setData(data.map((d) => (d.id === id ? { ...d, status: 'rejected' as const } : d))); setData(data.map((d) => (d.id === id ? { ...d, status: 'rejected' as const } : d)));
message.info('续方申请已拒?); message.info('续方申请已拒);
setDetailModalVisible(false); setDetailModalVisible(false);
}, },
}); });
...@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => { ...@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
const map: Record<string, { color: string; text: string }> = { const map: Record<string, { color: string; text: string }> = {
pending: { color: 'orange', text: '待审? }, pending: { color: 'orange', text: '待审核 },
approved: { color: 'green', text: '已通过' }, approved: { color: 'green', text: '已通过' },
rejected: { color: 'red', text: '已拒? }, rejected: { color: 'red', text: '已拒绝 },
modified: { color: 'blue', text: '已修? }, modified: { color: 'blue', text: '已修 },
}; };
const s = map[status] || { color: 'default', text: status }; const s = map[status] || { color: 'default', text: status };
return <Tag color={s.color}>{s.text}</Tag>; return <Tag color={s.color}>{s.text}</Tag>;
}; };
const columns = [ const columns = [
{ title: '患者姓?, dataIndex: 'patient_name', key: 'patient_name', width: 100 }, { title: '患者姓名, dataIndex: 'patient_name', key: 'patient_name', width: 100 },
{ {
title: '基本信息', title: '基本信息',
key: 'info', key: 'info',
...@@ -119,7 +119,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -119,7 +119,7 @@ const ChronicReviewPage: React.FC = () => {
render: (_: any, r: ChronicPrescription) => `${r.patient_gender} / ${r.patient_age}岁`, render: (_: any, r: ChronicPrescription) => `${r.patient_gender} / ${r.patient_age}岁`,
}, },
{ title: '慢病诊断', dataIndex: 'disease', key: 'disease', width: 120 }, { title: '慢病诊断', dataIndex: 'disease', key: 'disease', width: 120 },
{ title: '上次开?, dataIndex: 'last_prescription_date', key: 'last_prescription_date', width: 120 }, { title: '上次开始, dataIndex: 'last_prescription_date', key: 'last_prescription_date', width: 120 },
{ {
title: '药品数量', title: '药品数量',
key: 'drug_count', key: 'drug_count',
...@@ -127,7 +127,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -127,7 +127,7 @@ const ChronicReviewPage: React.FC = () => {
render: (_: any, r: ChronicPrescription) => <Tag color="blue">{r.ai_draft.drugs.length} ?/Tag>, render: (_: any, r: ChronicPrescription) => <Tag color="blue">{r.ai_draft.drugs.length} ?/Tag>,
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 100, width: 100,
...@@ -164,7 +164,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -164,7 +164,7 @@ const ChronicReviewPage: React.FC = () => {
<Tabs defaultActiveKey="pending" size="small" items={[ <Tabs defaultActiveKey="pending" size="small" items={[
{ {
key: 'pending', key: 'pending',
label: <Badge count={pendingCount} offset={[10, 0]}>待审?/Badge>, label: <Badge count={pendingCount} offset={[10, 0]}>待审</Badge>,
children: ( children: (
<Card size="small"> <Card size="small">
<Table columns={columns} dataSource={data.filter((d) => d.status === 'pending')} <Table columns={columns} dataSource={data.filter((d) => d.status === 'pending')}
...@@ -195,7 +195,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -195,7 +195,7 @@ const ChronicReviewPage: React.FC = () => {
<CloseCircleOutlined /> 拒绝 <CloseCircleOutlined /> 拒绝
</Button>, </Button>,
<Button key="modify" icon={<EditOutlined />} onClick={() => message.info('修改功能开发中')}> <Button key="modify" icon={<EditOutlined />} onClick={() => message.info('修改功能开发中')}>
修改后签? </Button>, 修改后签名 </Button>,
<Button key="approve" type="primary" style={{ background: '#52c41a', borderColor: '#52c41a' }} onClick={() => handleApprove(currentRecord.id)}> <Button key="approve" type="primary" style={{ background: '#52c41a', borderColor: '#52c41a' }} onClick={() => handleApprove(currentRecord.id)}>
<SafetyCertificateOutlined /> 签名发布 <SafetyCertificateOutlined /> 签名发布
</Button>, </Button>,
...@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => { ...@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => {
{currentRecord && ( {currentRecord && (
<div> <div>
<Descriptions bordered size="small" column={2} style={{ marginBottom: 16 }}> <Descriptions bordered size="small" column={2} style={{ marginBottom: 16 }}>
<Descriptions.Item label="患者姓?>{currentRecord.patient_name}</Descriptions.Item> <Descriptions.Item label="患者姓>{currentRecord.patient_name}</Descriptions.Item>
<Descriptions.Item label="性别/年龄">{currentRecord.patient_gender} / {currentRecord.patient_age}?/Descriptions.Item> <Descriptions.Item label="性别/年龄">{currentRecord.patient_gender} / {currentRecord.patient_age}?/Descriptions.Item>
<Descriptions.Item label="慢病诊断">{currentRecord.disease}</Descriptions.Item> <Descriptions.Item label="慢病诊断">{currentRecord.disease}</Descriptions.Item>
<Descriptions.Item label="上次开?>{currentRecord.last_prescription_date}</Descriptions.Item> <Descriptions.Item label="上次开>{currentRecord.last_prescription_date}</Descriptions.Item>
</Descriptions> </Descriptions>
<Card title="AI 续方草?" size="small" style={{ marginBottom: 16, borderColor: '#52c41a' }}> <Card title="AI 续方草" size="small" style={{ marginBottom: 16, borderColor: '#52c41a' }}>
<Table <Table
size="small" size="small"
pagination={false} pagination={false}
...@@ -225,7 +225,7 @@ const ChronicReviewPage: React.FC = () => { ...@@ -225,7 +225,7 @@ const ChronicReviewPage: React.FC = () => {
]} ]}
/> />
<div style={{ marginTop: 12, padding: 8, background: '#f6ffed', borderRadius: 6 }}> <div style={{ marginTop: 12, padding: 8, background: '#f6ffed', borderRadius: 6 }}>
<Text type="secondary">AI 备注?/Text> <Text type="secondary">AI 备注</Text>
<Text>{currentRecord.ai_draft.note}</Text> <Text>{currentRecord.ai_draft.note}</Text>
</div> </div>
</Card> </Card>
......
...@@ -40,7 +40,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -40,7 +40,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
const res = await consultApi.aiAssist(activeConsultId, scene); const res = await consultApi.aiAssist(activeConsultId, scene);
setContent(res.data?.content || '暂无分析结果'); setContent(res.data?.content || '暂无分析结果');
} catch (err: any) { } catch (err: any) {
setContent('AI分析失败: ' + (err?.message || '请稍后重?)); setContent('AI分析失败: ' + (err?.message || '请稍后重试));
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -63,7 +63,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -63,7 +63,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
{chatMsgs.map((msg, i) => ( {chatMsgs.map((msg, i) => (
<div key={i} style={{ fontSize: 12, marginTop: 6, paddingLeft: 8, borderLeft: msg.role === 'user' ? '2px solid #1890ff' : '2px solid #52c41a' }}> <div key={i} style={{ fontSize: 12, marginTop: 6, paddingLeft: 8, borderLeft: msg.role === 'user' ? '2px solid #1890ff' : '2px solid #52c41a' }}>
<Text strong style={{ fontSize: 12, color: msg.role === 'user' ? '#1890ff' : '#52c41a' }}> <Text strong style={{ fontSize: 12, color: msg.role === 'user' ? '#1890ff' : '#52c41a' }}>
{msg.role === 'user' ? '患? : 'AI'}? </Text> {msg.role === 'user' ? '患者 : 'AI'}? </Text>
<div style={{ marginTop: 2, color: '#333' }}>{msg.content}</div> <div style={{ marginTop: 2, color: '#333' }}>{msg.content}</div>
</div> </div>
))} ))}
...@@ -71,14 +71,15 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -71,14 +71,15 @@ const AIPanel: React.FC<AIPanelProps> = ({
); );
}; };
// 渲染预问诊内容(使用?tab? const renderPreConsultContent = () => { //
const renderPreConsultContent = () => {
if (!preConsultReport) return null; if (!preConsultReport) return null;
const patientInfo = ( const patientInfo = (
<div style={{ padding: 8, background: '#f9f0ff', borderRadius: 6, marginBottom: 8 }}> <div style={{ padding: 8, background: '#f9f0ff', borderRadius: 6, marginBottom: 8 }}>
<Text strong style={{ fontSize: 13, color: '#722ed1' }}> <Text strong style={{ fontSize: 13, color: '#722ed1' }}>
<UserOutlined style={{ marginRight: 4 }} /> <UserOutlined style={{ marginRight: 4 }} />
{preConsultReport.patient_name || '患?} {preConsultReport.patient_name || '患}
</Text> </Text>
{(preConsultReport.patient_gender || preConsultReport.patient_age) && ( {(preConsultReport.patient_gender || preConsultReport.patient_age) && (
<Text type="secondary" style={{ fontSize: 12, marginLeft: 8 }}> <Text type="secondary" style={{ fontSize: 12, marginLeft: 8 }}>
...@@ -105,7 +106,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -105,7 +106,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
{tags} {tags}
{preConsultReport.chief_complaint && ( {preConsultReport.chief_complaint && (
<div style={{ fontSize: 12, marginBottom: 8 }}> <div style={{ fontSize: 12, marginBottom: 8 }}>
<Text strong>主诉?/Text>{preConsultReport.chief_complaint} <Text strong>主诉</Text>{preConsultReport.chief_complaint}
</div> </div>
)} )}
<Tabs <Tabs
...@@ -157,22 +158,22 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -157,22 +158,22 @@ const AIPanel: React.FC<AIPanelProps> = ({
label: ( label: (
<span> <span>
<FileTextOutlined /> <FileTextOutlined />
{' '}预问? {hasPreConsultData && <Badge dot offset={[4, -2]} />} {' '}预问诊 {hasPreConsultData && <Badge dot offset={[4, -2]} />}
</span> </span>
), ),
children: preConsultLoading ? ( children: preConsultLoading ? (
<div style={{ textAlign: 'center', padding: 20 }}><Spin tip="加载?.." /></div> <div style={{ textAlign: 'center', padding: 20 }}><Spin tip="加载中.." /></div>
) : hasPreConsultData ? ( ) : hasPreConsultData ? (
renderPreConsultContent() renderPreConsultContent()
) : ( ) : (
<Alert message="该患者未进行AI预问? type="info" showIcon style={{ fontSize: 12 }} /> <Alert message="该患者未进行AI预问诊 type="info" showIcon style={{ fontSize: 12 }} />
), ),
}, },
{ {
key: 'diagnosis', key: 'diagnosis',
label: '鉴别诊断', label: '鉴别诊断',
children: diagnosisLoading ? ( children: diagnosisLoading ? (
<div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析?.." /></div> <div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析.." /></div>
) : diagnosisContent ? ( ) : diagnosisContent ? (
<div> <div>
<div style={{ maxHeight: 400, overflow: 'auto' }}> <div style={{ maxHeight: 400, overflow: 'auto' }}>
...@@ -186,7 +187,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -186,7 +187,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
) : ( ) : (
<div style={{ textAlign: 'center', padding: 16 }}> <div style={{ textAlign: 'center', padding: 16 }}>
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}> <Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}>
基于问诊对话内容,AI将辅助生成鉴别诊? </Text> 基于问诊对话内容,AI将辅助生成鉴别诊量 </Text>
<Button type="primary" size="small" icon={<ThunderboltOutlined />} onClick={() => handleAIAssist('consult_diagnosis')}> <Button type="primary" size="small" icon={<ThunderboltOutlined />} onClick={() => handleAIAssist('consult_diagnosis')}>
生成鉴别诊断 生成鉴别诊断
</Button> </Button>
...@@ -197,7 +198,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -197,7 +198,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
key: 'drugs', key: 'drugs',
label: '用药建议', label: '用药建议',
children: medicationLoading ? ( children: medicationLoading ? (
<div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析?.." /></div> <div style={{ textAlign: 'center', padding: 20 }}><Spin tip="AI分析.." /></div>
) : medicationContent ? ( ) : medicationContent ? (
<div> <div>
<div style={{ maxHeight: 400, overflow: 'auto' }}> <div style={{ maxHeight: 400, overflow: 'auto' }}>
...@@ -211,7 +212,7 @@ const AIPanel: React.FC<AIPanelProps> = ({ ...@@ -211,7 +212,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
) : ( ) : (
<div style={{ textAlign: 'center', padding: 16 }}> <div style={{ textAlign: 'center', padding: 16 }}>
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}> <Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 12 }}>
基于问诊对话内容,AI将辅助生成用药建? </Text> 基于问诊对话内容,AI将辅助生成用药建 </Text>
<Button type="primary" size="small" icon={<MedicineBoxOutlined />} onClick={() => handleAIAssist('consult_medication')}> <Button type="primary" size="small" icon={<MedicineBoxOutlined />} onClick={() => handleAIAssist('consult_medication')}>
生成用药建议 生成用药建议
</Button> </Button>
......
...@@ -23,7 +23,8 @@ interface ActiveConsult { ...@@ -23,7 +23,8 @@ interface ActiveConsult {
interface ChatPanelProps { interface ChatPanelProps {
activeConsult: ActiveConsult | null; activeConsult: ActiveConsult | null;
consultStatus?: string; // 当前选中问诊的状? messages: ConsultMessage[]; consultStatus?: string; //
messages: ConsultMessage[];
onSend: (content: string) => void; onSend: (content: string) => void;
onEndConsult: () => void; onEndConsult: () => void;
onToggleAI: () => void; onToggleAI: () => void;
...@@ -58,7 +59,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -58,7 +59,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
const handleEndConsult = () => { const handleEndConsult = () => {
Modal.confirm({ Modal.confirm({
title: '确认结束问诊', title: '确认结束问诊',
content: '结束后患者将无法继续发送消息,确认结束本次问诊?, content: '结束后患者将无法继续发送消息,确认结束本次问诊量,
okText: '确认结束', okText: '确认结束',
okButtonProps: { danger: true }, okButtonProps: { danger: true },
cancelText: '取消', cancelText: '取消',
...@@ -87,7 +88,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -87,7 +88,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
<Tag color={activeConsult.type === 'video' ? 'blue' : 'green'}> <Tag color={activeConsult.type === 'video' ? 'blue' : 'green'}>
{activeConsult.type === 'video' ? '视频问诊' : '图文问诊'} {activeConsult.type === 'video' ? '视频问诊' : '图文问诊'}
</Tag> </Tag>
{isCompleted && <Tag color="default">已结?/Tag>} {isCompleted && <Tag color="default">已结束</Tag>}
</Space> </Space>
) : '问诊对话' ) : '问诊对话'
} }
...@@ -174,7 +175,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -174,7 +175,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
textAlign: 'center', textAlign: 'center',
background: '#fafafa', background: '#fafafa',
}}> }}>
<Text type="secondary" style={{ fontSize: 13 }}>本次问诊已结?/Text> <Text type="secondary" style={{ fontSize: 13 }}>本次问诊已结束</Text>
</div> </div>
) : ( ) : (
<div style={{ padding: 12, borderTop: '1px solid #f0f0f0' }}> <div style={{ padding: 12, borderTop: '1px solid #f0f0f0' }}>
...@@ -199,14 +200,14 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ ...@@ -199,14 +200,14 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
loading={sending} loading={sending}
style={{ height: 'auto', borderRadius: '0 8px 8px 0', background: '#52c41a', borderColor: '#52c41a' }} style={{ height: 'auto', borderRadius: '0 8px 8px 0', background: '#52c41a', borderColor: '#52c41a' }}
> >
发? </Button> 发送 </Button>
</Space.Compact> </Space.Compact>
</div> </div>
)} )}
</> </>
) : ( ) : (
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Empty description="请从左侧列表中选择患者接? /> <Empty description="请从左侧列表中选择患者接诊 />
</div> </div>
)} )}
</Card> </Card>
......
...@@ -14,17 +14,19 @@ interface PatientListProps { ...@@ -14,17 +14,19 @@ interface PatientListProps {
} }
const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, onAccept, onReject, selectedConsultId }) => { const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, onAccept, onReject, selectedConsultId }) => {
// 鐘舵€侀厤缃? const statusConfig: Record<string, { color: string; bgColor: string; text: string }> = { //
in_progress: { color: '#52c41a', bgColor: '#f6ffed', text: '杩涜涓? }, const statusConfig: Record<string, { color: string; bgColor: string; text: string }> = {
pending: { color: '#1890ff', bgColor: '#e6f7ff', text: '寰呮帴璇? }, in_progress: { color: '#52c41a', bgColor: '#f6ffed', text: '杩涜涓 },
waiting: { color: '#1890ff', bgColor: '#e6f7ff', text: '寰呮帴璇? }, pending: { color: '#1890ff', bgColor: '#e6f7ff', text: '待接诊 },
completed: { color: '#8c8c8c', bgColor: '#fafafa', text: '宸插畬鎴? }, waiting: { color: '#1890ff', bgColor: '#e6f7ff', text: '待接诊 },
completed: { color: '#8c8c8c', bgColor: '#fafafa', text: '已完成 },
}; };
// 鏍煎紡鍖栫瓑寰呮椂闂? const formatWaitingTime = (seconds: number) => { //
if (seconds < 60) return `${seconds}绉抈; const formatWaitingTime = (seconds: number) => {
if (seconds < 3600) return `${Math.floor(seconds / 60)}鍒嗛挓`; if (seconds < 60) return `${seconds}秒;
return `${Math.floor(seconds / 3600)}灏忔椂`; if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟`;
return `${Math.floor(seconds / 3600)}小时`;
}; };
return ( return (
...@@ -32,7 +34,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -32,7 +34,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
title={ title={
<span className="text-xs"> <span className="text-xs">
<UserOutlined className="text-[#1890ff] mr-1" /> <UserOutlined className="text-[#1890ff] mr-1" />
<Text strong className="text-xs!">鎮h€呭垪琛?/Text> <Text strong className="text-xs!">患者列表</Text>
<Badge count={patients.length} style={{ backgroundColor: '#52c41a' }} className="ml-1" /> <Badge count={patients.length} style={{ backgroundColor: '#52c41a' }} className="ml-1" />
</span> </span>
} }
...@@ -46,7 +48,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -46,7 +48,7 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
emptyText: ( emptyText: (
<div className="text-center py-8 text-gray-400"> <div className="text-center py-8 text-gray-400">
<UserOutlined className="text-3xl text-gray-300 mb-2 block" /> <UserOutlined className="text-3xl text-gray-300 mb-2 block" />
<div className="text-xs">鏆傛棤鎮h€?/div> <div className="text-xs">暂无患者/div>
</div> </div>
) )
}} }}
...@@ -99,23 +101,23 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -99,23 +101,23 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
<div className="text-[11px] text-gray-500 mb-1"> <div className="text-[11px] text-gray-500 mb-1">
<Text ellipsis className="text-[11px]! text-gray-500!"> <Text ellipsis className="text-[11px]! text-gray-500!">
涓昏瘔锛歿patient.chief_complaint || '鏆傛棤'} 主诉:{patient.chief_complaint || '暂无'}
</Text> </Text>
</div> </div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-[10px] text-gray-400"> <span className="text-[10px] text-gray-400">
{patient.patient_gender} 路 {patient.patient_age}宀? </span> {patient.patient_gender} · {patient.patient_age}岁 </span>
{/* {patient.status === 'pending' || patient.status === 'waiting' ? ( {/* {patient.status === 'pending' || patient.status === 'waiting' ? (
<Space size={4} style={{ fontSize: 11, color: '#ff4d4f' }}> <Space size={4} style={{ fontSize: 11, color: '#ff4d4f' }}>
<ClockCircleOutlined /> <ClockCircleOutlined />
<span>绛夊緟 {formatWaitingTime(patient.waiting_seconds)}</span> <span>等待 {formatWaitingTime(patient.waiting_seconds)}</span>
</Space> </Space>
) : null} */} ) : null} */}
</div> </div>
{/* 寰呮帴璇婃偅鑰呮樉绀烘帴璇?鎷掔粷鎸夐挳 */} {/* 待接诊患者显示接诊/拒绝按钮 */}
{(patient.status === 'pending' || patient.status === 'waiting') && ( {(patient.status === 'pending' || patient.status === 'waiting') && (
<div className="mt-1.5 flex gap-1.5"> <div className="mt-1.5 flex gap-1.5">
<Button <Button
...@@ -125,17 +127,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -125,17 +127,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
console.log('鎺ヨ瘖鎸夐挳琚偣鍑?, patient); console.log('接诊鎸夐挳袋偣鍑, patient);
console.log('onAccept 鍑芥暟:', onAccept); console.log('onAccept 函数:', onAccept);
if (typeof onAccept === 'function') { if (typeof onAccept === 'function') {
onAccept(patient); onAccept(patient);
} else { } else {
console.error('onAccept 涓嶆槸涓€涓嚱鏁?'); console.error('onAccept 涓嶆槸涓€涓嚱鏁');
} }
}} }}
className="flex-1 text-[11px]!" className="flex-1 text-[11px]!"
> >
鎺ヨ瘖 接诊
</Button> </Button>
<Button <Button
danger danger
...@@ -144,17 +146,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on ...@@ -144,17 +146,17 @@ const PatientList: React.FC<PatientListProps> = ({ patients, onSelectPatient, on
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
console.log('鎷掔粷鎸夐挳琚偣鍑?, patient); console.log('拒绝鎸夐挳袋偣鍑, patient);
console.log('onReject 鍑芥暟:', onReject); console.log('onReject 函数:', onReject);
if (typeof onReject === 'function') { if (typeof onReject === 'function') {
onReject(patient); onReject(patient);
} else { } else {
console.error('onReject 涓嶆槸涓€涓嚱鏁?'); console.error('onReject 涓嶆槸涓€涓嚱鏁');
} }
}} }}
className="flex-1 text-[11px]!" className="flex-1 text-[11px]!"
> >
鎷掔粷 拒绝
</Button> </Button>
</div> </div>
)} )}
......
...@@ -18,10 +18,10 @@ interface WaitingQueueProps { ...@@ -18,10 +18,10 @@ interface WaitingQueueProps {
} }
const formatWaitingTime = (seconds: number) => { const formatWaitingTime = (seconds: number) => {
if (seconds < 60) return '鍒氬垰'; if (seconds < 60) return '刚刚';
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}鍒嗛挓`; if (minutes < 60) return `${minutes}分钟`;
return `${Math.floor(minutes / 60)}灏忔椂${minutes % 60}鍒哷; return `${Math.floor(minutes / 60)}小时${minutes % 60}分;
}; };
const WaitingQueue: React.FC<WaitingQueueProps> = ({ const WaitingQueue: React.FC<WaitingQueueProps> = ({
...@@ -47,9 +47,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -47,9 +47,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Space> <Space>
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} /> <Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} />
<Text strong>{patient.patient_name || '鎮h€?}</Text> <Text strong>{patient.patient_name || '患者}</Text>
<Tag color={patient.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}> <Tag color={patient.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}>
{patient.type === 'video' ? '瑙嗛' : '鍥炬枃'} {patient.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
</Space> </Space>
</div> </div>
...@@ -58,7 +58,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -58,7 +58,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
? patient.chief_complaint.length > 30 ? patient.chief_complaint.length > 30
? patient.chief_complaint.substring(0, 30) + '...' ? patient.chief_complaint.substring(0, 30) + '...'
: patient.chief_complaint : patient.chief_complaint
: '鏈~鍐欎富璇?} : '鏈~鍐欎富璇}
</Text> </Text>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Tag icon={<ClockCircleOutlined />} color="orange" style={{ fontSize: 11 }}> <Tag icon={<ClockCircleOutlined />} color="orange" style={{ fontSize: 11 }}>
...@@ -66,10 +66,10 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -66,10 +66,10 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
</Tag> </Tag>
<Space size={4}> <Space size={4}>
<Button size="small" type="primary" onClick={() => onAccept(patient)}> <Button size="small" type="primary" onClick={() => onAccept(patient)}>
鎺ヨ瘖 接诊
</Button> </Button>
<Button size="small" danger onClick={() => onReject(patient)}> <Button size="small" danger onClick={() => onReject(patient)}>
鎷掕瘖 拒诊
</Button> </Button>
</Space> </Space>
</div> </div>
...@@ -92,9 +92,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -92,9 +92,9 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Space> <Space>
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#1890ff' }} /> <Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#1890ff' }} />
<Text strong>{consult.patient_name || '鎮h€?}</Text> <Text strong>{consult.patient_name || '患者}</Text>
<Tag color={consult.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}> <Tag color={consult.type === 'video' ? 'blue' : 'green'} style={{ fontSize: 11 }}>
{consult.type === 'video' ? '瑙嗛' : '鍥炬枃'} {consult.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
</Space> </Space>
</div> </div>
...@@ -103,7 +103,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -103,7 +103,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
? consult.chief_complaint.length > 30 ? consult.chief_complaint.length > 30
? consult.chief_complaint.substring(0, 30) + '...' ? consult.chief_complaint.substring(0, 30) + '...'
: consult.chief_complaint : consult.chief_complaint
: '鏈~鍐欎富璇?} : '鏈~鍐欎富璇}
</Text> </Text>
</Card> </Card>
); );
...@@ -113,12 +113,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -113,12 +113,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
key: 'waiting', key: 'waiting',
label: ( label: (
<Space> <Space>
<span>寰呮帴璇?/span> <span>待接诊/span>
<Badge count={waitingList.length} style={{ backgroundColor: '#fa8c16' }} /> <Badge count={waitingList.length} style={{ backgroundColor: '#fa8c16' }} />
</Space> </Space>
), ),
children: waitingList.length === 0 ? ( children: waitingList.length === 0 ? (
<Empty description="鏆傛棤绛夊緟鎮h€? style={{ marginTop: 40 }} /> <Empty description="暂无等待患者 style={{ marginTop: 40 }} />
) : ( ) : (
<div>{waitingList.map(renderWaitingItem)}</div> <div>{waitingList.map(renderWaitingItem)}</div>
), ),
...@@ -128,12 +128,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -128,12 +128,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
label: ( label: (
<Space> <Space>
<MessageOutlined /> <MessageOutlined />
<span>杩涜涓?/span> <span>杩涜涓</span>
<Badge count={inProgressList.length} style={{ backgroundColor: '#52c41a' }} /> <Badge count={inProgressList.length} style={{ backgroundColor: '#52c41a' }} />
</Space> </Space>
), ),
children: inProgressList.length === 0 ? ( children: inProgressList.length === 0 ? (
<Empty description="鏆傛棤杩涜涓棶璇? style={{ marginTop: 40 }} /> <Empty description="暂无杩涜涓棶璇 style={{ marginTop: 40 }} />
) : ( ) : (
<div>{inProgressList.map(renderInProgressItem)}</div> <div>{inProgressList.map(renderInProgressItem)}</div>
), ),
...@@ -143,12 +143,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -143,12 +143,12 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
label: ( label: (
<Space> <Space>
<CheckCircleOutlined /> <CheckCircleOutlined />
<span>宸插畬鎴?/span> <span>已完成/span>
<Badge count={completedList.length} style={{ backgroundColor: '#999' }} /> <Badge count={completedList.length} style={{ backgroundColor: '#999' }} />
</Space> </Space>
), ),
children: completedList.length === 0 ? ( children: completedList.length === 0 ? (
<Empty description="鏆傛棤宸插畬鎴愰棶璇? style={{ marginTop: 40 }} /> <Empty description="暂无已完成愰棶璇 style={{ marginTop: 40 }} />
) : ( ) : (
<div>{completedList.map((consult) => ( <div>{completedList.map((consult) => (
<Card <Card
...@@ -167,8 +167,8 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -167,8 +167,8 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<Space> <Space>
<Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#999' }} /> <Avatar size="small" icon={<UserOutlined />} style={{ backgroundColor: '#999' }} />
<Text strong>{consult.patient_name || '鎮h€?}</Text> <Text strong>{consult.patient_name || '患者}</Text>
<Tag color="default" style={{ fontSize: 11 }}>宸插畬鎴?/Tag> <Tag color="default" style={{ fontSize: 11 }}>已完成/Tag>
</Space> </Space>
</div> </div>
<Text type="secondary" style={{ fontSize: 12, display: 'block' }}> <Text type="secondary" style={{ fontSize: 12, display: 'block' }}>
...@@ -176,7 +176,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({ ...@@ -176,7 +176,7 @@ const WaitingQueue: React.FC<WaitingQueueProps> = ({
? consult.chief_complaint.length > 30 ? consult.chief_complaint.length > 30
? consult.chief_complaint.substring(0, 30) + '...' ? consult.chief_complaint.substring(0, 30) + '...'
: consult.chief_complaint : consult.chief_complaint
: '鏈~鍐欎富璇?} : '鏈~鍐欎富璇}
</Text> </Text>
</Card> </Card>
))}</div> ))}</div>
......
...@@ -30,7 +30,8 @@ const ConsultPage: React.FC = () => { ...@@ -30,7 +30,8 @@ const ConsultPage: React.FC = () => {
const [preConsultLoading, setPreConsultLoading] = useState(false); const [preConsultLoading, setPreConsultLoading] = useState(false);
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
// 获取统一患者列? const fetchPatientList = useCallback(async () => { //
const fetchPatientList = useCallback(async () => {
try { try {
const res = await consultApi.getPatientList(); const res = await consultApi.getPatientList();
setPatientList(res.data || []); setPatientList(res.data || []);
...@@ -49,7 +50,8 @@ const ConsultPage: React.FC = () => { ...@@ -49,7 +50,8 @@ const ConsultPage: React.FC = () => {
} }
}, []); }, []);
// 定时刷新患者列? useEffect(() => { //
useEffect(() => {
fetchPatientList(); fetchPatientList();
const timer = setInterval(() => { const timer = setInterval(() => {
fetchPatientList(); fetchPatientList();
...@@ -90,7 +92,7 @@ const ConsultPage: React.FC = () => { ...@@ -90,7 +92,7 @@ const ConsultPage: React.FC = () => {
// 接诊 // 接诊
const handleAccept = async (patient: PatientListItem) => { const handleAccept = async (patient: PatientListItem) => {
console.log('handleAccept 被调?, patient); console.log('handleAccept 被调整, patient);
try { try {
console.log('调用 acceptConsult API', patient.consult_id); console.log('调用 acceptConsult API', patient.consult_id);
await consultApi.acceptConsult(patient.consult_id); await consultApi.acceptConsult(patient.consult_id);
...@@ -98,7 +100,7 @@ const ConsultPage: React.FC = () => { ...@@ -98,7 +100,7 @@ const ConsultPage: React.FC = () => {
setActiveConsult({ setActiveConsult({
consult_id: patient.consult_id, consult_id: patient.consult_id,
patient_id: patient.patient_id, patient_id: patient.patient_id,
patient_name: patient.patient_name || '患?, patient_name: patient.patient_name || '患者,
patient_gender: patient.patient_gender, patient_gender: patient.patient_gender,
patient_age: patient.patient_age, patient_age: patient.patient_age,
chief_complaint: patient.chief_complaint, chief_complaint: patient.chief_complaint,
...@@ -106,7 +108,7 @@ const ConsultPage: React.FC = () => { ...@@ -106,7 +108,7 @@ const ConsultPage: React.FC = () => {
status: 'in_progress', status: 'in_progress',
}); });
setPreConsultReport(null); setPreConsultReport(null);
message.success(`已接诊患?${patient.patient_name || '患?}`); message.success(`已接诊患?${patient.patient_name || '患}`);
fetchPreConsultReport(patient.consult_id, patient.patient_id); fetchPreConsultReport(patient.consult_id, patient.patient_id);
fetchPatientList(); fetchPatientList();
fetchMessages(patient.consult_id); fetchMessages(patient.consult_id);
...@@ -121,7 +123,7 @@ const ConsultPage: React.FC = () => { ...@@ -121,7 +123,7 @@ const ConsultPage: React.FC = () => {
setActiveConsult({ setActiveConsult({
consult_id: patient.consult_id, consult_id: patient.consult_id,
patient_id: patient.patient_id, patient_id: patient.patient_id,
patient_name: patient.patient_name || '患?, patient_name: patient.patient_name || '患者,
patient_gender: patient.patient_gender, patient_gender: patient.patient_gender,
patient_age: patient.patient_age, patient_age: patient.patient_age,
chief_complaint: patient.chief_complaint, chief_complaint: patient.chief_complaint,
...@@ -135,16 +137,16 @@ const ConsultPage: React.FC = () => { ...@@ -135,16 +137,16 @@ const ConsultPage: React.FC = () => {
// 拒诊 // 拒诊
const handleReject = (patient: PatientListItem) => { const handleReject = (patient: PatientListItem) => {
console.log('handleReject 被调?, patient); console.log('handleReject 被调整, patient);
Modal.confirm({ Modal.confirm({
title: '确认拒诊', title: '确认拒诊',
content: `确定拒绝接诊患?${patient.patient_name || '患?}?`, content: `确定拒绝接诊患?${patient.patient_name || '患}?`,
onOk: async () => { onOk: async () => {
try { try {
console.log('调用 rejectConsult API', patient.consult_id); console.log('调用 rejectConsult API', patient.consult_id);
await consultApi.rejectConsult(patient.consult_id); await consultApi.rejectConsult(patient.consult_id);
console.log('rejectConsult API 调用成功'); console.log('rejectConsult API 调用成功');
message.info('已拒?); message.info('已拒);
fetchPatientList(); fetchPatientList();
} catch (err: any) { } catch (err: any) {
console.error('拒诊失败', err); console.error('拒诊失败', err);
...@@ -155,14 +157,15 @@ const ConsultPage: React.FC = () => { ...@@ -155,14 +157,15 @@ const ConsultPage: React.FC = () => {
}; };
// 发送消? const handleSendMessage = async (content: string) => { //
const handleSendMessage = async (content: string) => {
if (!activeConsult) return; if (!activeConsult) return;
setSending(true); setSending(true);
try { try {
await consultApi.sendMessage(activeConsult.consult_id, content); await consultApi.sendMessage(activeConsult.consult_id, content);
fetchMessages(activeConsult.consult_id); fetchMessages(activeConsult.consult_id);
} catch { } catch {
message.error('发送失?); message.error('发送失败);
} finally { } finally {
setSending(false); setSending(false);
} }
...@@ -174,7 +177,7 @@ const ConsultPage: React.FC = () => { ...@@ -174,7 +177,7 @@ const ConsultPage: React.FC = () => {
try { try {
await consultApi.endConsult(activeConsult.consult_id); await consultApi.endConsult(activeConsult.consult_id);
message.success('问诊已结?); message.success('问诊已结束);
setActiveConsult(null); setActiveConsult(null);
setMessages([]); setMessages([]);
setPreConsultReport(null); setPreConsultReport(null);
......
...@@ -22,27 +22,27 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -22,27 +22,27 @@ const DoctorConsultRoomPage: React.FC = () => {
patient_avatar: '', patient_avatar: '',
type: 'text', type: 'text',
status: 'in_progress', status: 'in_progress',
chief_complaint: '头痛、乏力,持续3天?, chief_complaint: '头痛、乏力,持续3天数,
medical_history: '高血压病?年,长期服用降压药?, medical_history: '高血压病?年,长期服用降压药物,
}; };
const messages = [ const messages = [
{ {
id: '1', id: '1',
sender_type: 'system', sender_type: 'system',
content: '问诊已开?, content: '问诊已开始,
created_at: '2026-02-25T13:00:00Z', created_at: '2026-02-25T13:00:00Z',
}, },
{ {
id: '2', id: '2',
sender_type: 'patient', sender_type: 'patient',
content: '医生您好,我最近三天一直头痛,感觉很乏力,精神也不好?, content: '医生您好,我最近三天一直头痛,感觉很乏力,精神也不好评,
created_at: '2026-02-25T13:01:00Z', created_at: '2026-02-25T13:01:00Z',
}, },
{ {
id: '3', id: '3',
sender_type: 'patient', sender_type: 'patient',
content: '我有高血压,一直在吃药控制?, content: '我有高血压,一直在吃药控制定,
created_at: '2026-02-25T13:01:30Z', created_at: '2026-02-25T13:01:30Z',
}, },
]; ];
...@@ -53,7 +53,7 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -53,7 +53,7 @@ const DoctorConsultRoomPage: React.FC = () => {
gender: '?, gender: '?,
phone: '138****8888', phone: '138****8888',
medical_history: '高血压病??, medical_history: '高血压病??,
allergy_history: '青霉素过?, allergy_history: '青霉素过敏,
consultation_count: 3, consultation_count: 3,
}; };
...@@ -80,7 +80,7 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -80,7 +80,7 @@ const DoctorConsultRoomPage: React.FC = () => {
<Tag className="ml-1" color={currentConsult.type === 'video' ? 'blue' : 'green'}> <Tag className="ml-1" color={currentConsult.type === 'video' ? 'blue' : 'green'}>
{currentConsult.type === 'video' ? '视频' : '图文'} {currentConsult.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
<Tag color="processing">问诊?/Tag> <Tag color="processing">问诊中</Tag>
</span> </span>
} }
extra={ extra={
...@@ -122,14 +122,14 @@ const DoctorConsultRoomPage: React.FC = () => { ...@@ -122,14 +122,14 @@ const DoctorConsultRoomPage: React.FC = () => {
<TextArea value={inputValue} onChange={(e) => setInputValue(e.target.value)} <TextArea value={inputValue} onChange={(e) => setInputValue(e.target.value)}
placeholder="输入回复..." autoSize={{ minRows: 1, maxRows: 3 }} size="small" placeholder="输入回复..." autoSize={{ minRows: 1, maxRows: 3 }} size="small"
onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} /> onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} />
<Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend}>发?/Button> <Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend}>发送</Button>
</Space.Compact> </Space.Compact>
</div> </div>
</Card> </Card>
</Col> </Col>
<Col span={8} style={{ height: '100%' }}> <Col span={8} style={{ height: '100%' }}>
<Card title={<span className="text-xs font-semibold">患者信?/span>} size="small" <Card title={<span className="text-xs font-semibold">患者信息</span>} size="small"
style={{ height: '100%', overflow: 'auto' }}> style={{ height: '100%', overflow: 'auto' }}>
<div className="text-center mb-2"> <div className="text-center mb-2">
<Avatar size={48} icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} /> <Avatar size={48} icon={<UserOutlined />} style={{ backgroundColor: '#87d068' }} />
......
...@@ -19,9 +19,9 @@ interface ConsultRecord { ...@@ -19,9 +19,9 @@ interface ConsultRecord {
} }
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
completed: { text: '已完?, color: 'green' }, completed: { text: '已完成, color: 'green' },
cancelled: { text: '已取?, color: 'red' }, cancelled: { text: '已取消, color: 'red' },
in_progress: { text: '进行?, color: 'blue' }, in_progress: { text: '进行中, color: 'blue' },
}; };
const DoctorHistoryPage: React.FC = () => { const DoctorHistoryPage: React.FC = () => {
...@@ -35,7 +35,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -35,7 +35,7 @@ const DoctorHistoryPage: React.FC = () => {
patient_avatar: '', patient_avatar: '',
type: 'text', type: 'text',
status: 'completed', status: 'completed',
chief_complaint: '头痛、乏?, chief_complaint: '头痛、乏力,
created_at: '2026-02-24T10:00:00Z', created_at: '2026-02-24T10:00:00Z',
ended_at: '2026-02-24T10:30:00Z', ended_at: '2026-02-24T10:30:00Z',
}, },
...@@ -55,7 +55,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -55,7 +55,7 @@ const DoctorHistoryPage: React.FC = () => {
patient_avatar: '', patient_avatar: '',
type: 'text', type: 'text',
status: 'cancelled', status: 'cancelled',
chief_complaint: '复诊开?, chief_complaint: '复诊开始,
created_at: '2026-02-23T09:00:00Z', created_at: '2026-02-23T09:00:00Z',
ended_at: null, ended_at: null,
}, },
...@@ -63,7 +63,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -63,7 +63,7 @@ const DoctorHistoryPage: React.FC = () => {
const columns: ColumnsType<ConsultRecord> = [ const columns: ColumnsType<ConsultRecord> = [
{ {
title: '患?, title: '患者,
dataIndex: 'patient_name', dataIndex: 'patient_name',
key: 'patient_name', key: 'patient_name',
render: (name: string, record) => ( render: (name: string, record) => (
...@@ -93,7 +93,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -93,7 +93,7 @@ const DoctorHistoryPage: React.FC = () => {
ellipsis: true, ellipsis: true,
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
render: (status: string) => { render: (status: string) => {
...@@ -123,7 +123,7 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -123,7 +123,7 @@ const DoctorHistoryPage: React.FC = () => {
<Card size="small"> <Card size="small">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
placeholder="搜索患者姓? placeholder="搜索患者姓名
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
value={keyword} value={keyword}
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
...@@ -132,8 +132,8 @@ const DoctorHistoryPage: React.FC = () => { ...@@ -132,8 +132,8 @@ const DoctorHistoryPage: React.FC = () => {
/> />
<Select placeholder="类型" style={{ width: 100 }} size="small" allowClear <Select placeholder="类型" style={{ width: 100 }} size="small" allowClear
options={[{ label: '图文', value: 'text' }, { label: '视频', value: 'video' }]} /> options={[{ label: '图文', value: 'text' }, { label: '视频', value: 'video' }]} />
<Select placeholder="状? style={{ width: 100 }} size="small" allowClear <Select placeholder="状态 style={{ width: 100 }} size="small" allowClear
options={[{ label: '已完?, value: 'completed' }, { label: '已取?, value: 'cancelled' }]} /> options={[{ label: '已完成, value: 'completed' }, { label: '已取消, value: 'cancelled' }]} />
<RangePicker size="small" /> <RangePicker size="small" />
</div> </div>
</Card> </Card>
......
...@@ -61,7 +61,7 @@ const IncomePage: React.FC = () => { ...@@ -61,7 +61,7 @@ const IncomePage: React.FC = () => {
const incomeColumns = [ const incomeColumns = [
{ title: '时间', dataIndex: 'date', key: 'date', width: 160 }, { title: '时间', dataIndex: 'date', key: 'date', width: 160 },
{ title: '患?, dataIndex: 'patient_name', key: 'patient_name', width: 100 }, { title: '患者, dataIndex: 'patient_name', key: 'patient_name', width: 100 },
{ {
title: '类型', title: '类型',
dataIndex: 'type', dataIndex: 'type',
...@@ -80,13 +80,13 @@ const IncomePage: React.FC = () => { ...@@ -80,13 +80,13 @@ const IncomePage: React.FC = () => {
render: (v: number) => <Text strong style={{ color: '#52c41a' }}>¥{(v / 100).toFixed(2)}</Text>, render: (v: number) => <Text strong style={{ color: '#52c41a' }}>¥{(v / 100).toFixed(2)}</Text>,
}, },
{ {
title: '状?, title: '状态,
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 80, width: 80,
render: (v: string) => ( render: (v: string) => (
<Tag color={v === 'settled' ? 'green' : 'orange'}> <Tag color={v === 'settled' ? 'green' : 'orange'}>
{v === 'settled' ? '已结? : '待结?} {v === 'settled' ? '已结算 : '待结}
</Tag> </Tag>
), ),
}, },
...@@ -103,7 +103,7 @@ const IncomePage: React.FC = () => { ...@@ -103,7 +103,7 @@ const IncomePage: React.FC = () => {
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
<Col span={8}> <Col span={8}>
<div className="rounded-lg p-3 text-white" style={{ background: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)' }}> <div className="rounded-lg p-3 text-white" style={{ background: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)' }}>
<div className="text-xs text-white/80 mb-1">可提现余?/div> <div className="text-xs text-white/80 mb-1">可提现余</div>
<div className="text-2xl font-bold">¥{(totalBalance / 100).toFixed(2)}</div> <div className="text-2xl font-bold">¥{(totalBalance / 100).toFixed(2)}</div>
<Button ghost size="small" className="mt-2 text-white! border-white/60!" icon={<WalletOutlined />} <Button ghost size="small" className="mt-2 text-white! border-white/60!" icon={<WalletOutlined />}
onClick={() => setWithdrawModalVisible(true)}>申请提现</Button> onClick={() => setWithdrawModalVisible(true)}>申请提现</Button>
...@@ -114,13 +114,13 @@ const IncomePage: React.FC = () => { ...@@ -114,13 +114,13 @@ const IncomePage: React.FC = () => {
<div className="text-xs text-gray-500 mb-1">本月收入</div> <div className="text-xs text-gray-500 mb-1">本月收入</div>
<div className="text-xl font-bold text-green-500">¥{(monthIncome / 100).toFixed(2)}</div> <div className="text-xl font-bold text-green-500">¥{(monthIncome / 100).toFixed(2)}</div>
<div className="mt-1 text-[11px] text-gray-400"> <div className="mt-1 text-[11px] text-gray-400">
<RiseOutlined className="text-green-500 mr-0.5" />较上?<span className="text-green-500">+12.5%</span> <RiseOutlined className="text-green-500 mr-0.5" />较上<span className="text-green-500">+12.5%</span>
</div> </div>
</div> </div>
</Col> </Col>
<Col span={8}> <Col span={8}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa]"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa]">
<div className="text-xs text-gray-500 mb-1">本月问诊?/div> <div className="text-xs text-gray-500 mb-1">本月问诊中</div>
<div className="text-xl font-bold text-[#1890ff]">{monthConsults} <span className="text-xs font-normal text-gray-400">?/span></div> <div className="text-xl font-bold text-[#1890ff]">{monthConsults} <span className="text-xs font-normal text-gray-400">?/span></div>
<div className="mt-1 text-[11px] text-gray-400">日均 {(monthConsults / 25).toFixed(1)} ?/div> <div className="mt-1 text-[11px] text-gray-400">日均 {(monthConsults / 25).toFixed(1)} ?/div>
</div> </div>
...@@ -165,7 +165,7 @@ const IncomePage: React.FC = () => { ...@@ -165,7 +165,7 @@ const IncomePage: React.FC = () => {
</Col> </Col>
<Col span={5}> <Col span={5}>
<Statistic <Statistic
title="总收? title="总收入
value={bill.total_income / 100} value={bill.total_income / 100}
precision={2} precision={2}
prefix="¥" prefix="¥"
...@@ -173,7 +173,7 @@ const IncomePage: React.FC = () => { ...@@ -173,7 +173,7 @@ const IncomePage: React.FC = () => {
/> />
</Col> </Col>
<Col span={3}> <Col span={3}>
<Statistic title="问诊? value={bill.consult_count} suffix="? valueStyle={{ fontSize: 16 }} /> <Statistic title="问诊量 value={bill.consult_count} suffix="? valueStyle={{ fontSize: 16 }} />
</Col> </Col>
<Col span={12}> <Col span={12}>
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
...@@ -220,7 +220,7 @@ const IncomePage: React.FC = () => { ...@@ -220,7 +220,7 @@ const IncomePage: React.FC = () => {
{ title: '申请日期', dataIndex: 'date', key: 'date' }, { title: '申请日期', dataIndex: 'date', key: 'date' },
{ title: '金额', dataIndex: 'amount', key: 'amount', render: (v: number) => `¥${(v / 100).toFixed(2)}` }, { title: '金额', dataIndex: 'amount', key: 'amount', render: (v: number) => `¥${(v / 100).toFixed(2)}` },
{ title: '提现账户', dataIndex: 'bank', key: 'bank' }, { title: '提现账户', dataIndex: 'bank', key: 'bank' },
{ title: '状?, dataIndex: 'status', key: 'status', render: () => <Tag color="green">已到?/Tag> }, { title: '状态, dataIndex: 'status', key: 'status', render: () => <Tag color="green">已到</Tag> },
]} ]}
/> />
</Card> </Card>
...@@ -237,20 +237,20 @@ const IncomePage: React.FC = () => { ...@@ -237,20 +237,20 @@ const IncomePage: React.FC = () => {
okText="确认提现" okText="确认提现"
> >
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="可提现金?> <Form.Item label="可提现金>
<Text strong style={{ fontSize: 24, color: '#52c41a' }}>¥{(totalBalance / 100).toFixed(2)}</Text> <Text strong style={{ fontSize: 24, color: '#52c41a' }}>¥{(totalBalance / 100).toFixed(2)}</Text>
</Form.Item> </Form.Item>
<Form.Item label="提现金额" required> <Form.Item label="提现金额" required>
<InputNumber <InputNumber
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder="请输入提现金? placeholder="请输入提现金额
min={1} min={1}
max={totalBalance / 100} max={totalBalance / 100}
precision={2} precision={2}
prefix="¥" prefix="¥"
/> />
</Form.Item> </Form.Item>
<Form.Item label="到账银行?> <Form.Item label="到账银行>
<Input value="招商银行 ****5678" disabled /> <Input value="招商银行 ****5678" disabled />
</Form.Item> </Form.Item>
</Form> </Form>
......
...@@ -30,10 +30,10 @@ const consultStatusColor: Record<string, string> = { ...@@ -30,10 +30,10 @@ const consultStatusColor: Record<string, string> = {
}; };
const consultStatusLabel: Record<string, string> = { const consultStatusLabel: Record<string, string> = {
completed: '已完?, completed: '已完成,
in_progress: '进行?, in_progress: '进行中,
pending: '待接?, pending: '待接诊,
cancelled: '已取?, cancelled: '已取消,
}; };
const prescriptionStatusColor: Record<string, string> = { const prescriptionStatusColor: Record<string, string> = {
...@@ -46,12 +46,12 @@ const prescriptionStatusColor: Record<string, string> = { ...@@ -46,12 +46,12 @@ const prescriptionStatusColor: Record<string, string> = {
}; };
const prescriptionStatusLabel: Record<string, string> = { const prescriptionStatusLabel: Record<string, string> = {
completed: '已完?, completed: '已完成,
dispensed: '已取?, dispensed: '已取消,
approved: '已审?, approved: '已审核,
signed: '已签?, signed: '已签名,
pending: '待审?, pending: '待审核,
rejected: '已拒?, rejected: '已拒绝,
}; };
const PatientRecordPage: React.FC = () => { const PatientRecordPage: React.FC = () => {
...@@ -70,7 +70,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -70,7 +70,7 @@ const PatientRecordPage: React.FC = () => {
setPatients(res.data.list || []); setPatients(res.data.list || []);
setTotal(res.data.total || 0); setTotal(res.data.total || 0);
} catch { } catch {
message.error('获取患者列表失?); message.error('获取患者列表失);
} finally { } finally {
setListLoading(false); setListLoading(false);
} }
...@@ -93,7 +93,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -93,7 +93,7 @@ const PatientRecordPage: React.FC = () => {
const res = await doctorPortalApi.getPatientDetail(patientId); const res = await doctorPortalApi.getPatientDetail(patientId);
setSelectedPatient(res.data); setSelectedPatient(res.data);
} catch { } catch {
message.error('获取患者详情失?); message.error('获取患者详情失);
} finally { } finally {
setDetailLoading(false); setDetailLoading(false);
} }
...@@ -107,7 +107,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -107,7 +107,7 @@ const PatientRecordPage: React.FC = () => {
}, },
{ title: '主诉', dataIndex: 'chief_complaint', key: 'chief_complaint', ellipsis: true }, { title: '主诉', dataIndex: 'chief_complaint', key: 'chief_complaint', ellipsis: true },
{ {
title: '状?, dataIndex: 'status', key: 'status', width: 80, title: '状态, dataIndex: 'status', key: 'status', width: 80,
render: (v: string) => <Tag color={consultStatusColor[v] ?? 'default'}>{consultStatusLabel[v] ?? v}</Tag>, render: (v: string) => <Tag color={consultStatusColor[v] ?? 'default'}>{consultStatusLabel[v] ?? v}</Tag>,
}, },
]; ];
...@@ -126,7 +126,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -126,7 +126,7 @@ const PatientRecordPage: React.FC = () => {
), ),
}, },
{ {
title: '状?, dataIndex: 'status', key: 'status', width: 80, title: '状态, dataIndex: 'status', key: 'status', width: 80,
render: (v: string) => <Tag color={prescriptionStatusColor[v] ?? 'default'}>{prescriptionStatusLabel[v] ?? v}</Tag>, render: (v: string) => <Tag color={prescriptionStatusColor[v] ?? 'default'}>{prescriptionStatusLabel[v] ?? v}</Tag>,
}, },
]; ];
...@@ -134,11 +134,11 @@ const PatientRecordPage: React.FC = () => { ...@@ -134,11 +134,11 @@ const PatientRecordPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-bold text-gray-800 m-0"> <h4 className="text-sm font-bold text-gray-800 m-0">
<UserOutlined className="mr-1" />患者档? </h4> <UserOutlined className="mr-1" />患者档 </h4>
<Row gutter={8}> <Row gutter={8}>
<Col span={8}> <Col span={8}>
<Card title={<span className="text-xs font-semibold">患者列?/span>} size="small" <Card title={<span className="text-xs font-semibold">患者列表</span>} size="small"
extra={<Text type="secondary" className="text-[11px]!">{total} ?/Text>}> extra={<Text type="secondary" className="text-[11px]!">{total} ?/Text>}>
<Input prefix={<SearchOutlined />} placeholder="搜索姓名/手机" size="small" <Input prefix={<SearchOutlined />} placeholder="搜索姓名/手机" size="small"
value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)} value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}
...@@ -147,7 +147,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -147,7 +147,7 @@ const PatientRecordPage: React.FC = () => {
className="mb-2" /> className="mb-2" />
<Spin spinning={listLoading}> <Spin spinning={listLoading}>
<List size="small" dataSource={patients} <List size="small" dataSource={patients}
locale={{ emptyText: <Empty description="暂无患? /> }} locale={{ emptyText: <Empty description="暂无患者 /> }}
pagination={total > 20 ? { current: page, pageSize: 20, total, size: 'small', pagination={total > 20 ? { current: page, pageSize: 20, total, size: 'small',
onChange: (p) => { setPage(p); fetchPatients(searchKeyword, p); } } : false} onChange: (p) => { setPage(p); fetchPatients(searchKeyword, p); } } : false}
renderItem={(patient) => ( renderItem={(patient) => (
...@@ -187,9 +187,9 @@ const PatientRecordPage: React.FC = () => { ...@@ -187,9 +187,9 @@ const PatientRecordPage: React.FC = () => {
<Descriptions.Item label="性别">{selectedPatient.gender === 'male' ? '? : selectedPatient.gender === 'female' ? '? : selectedPatient.gender}</Descriptions.Item> <Descriptions.Item label="性别">{selectedPatient.gender === 'male' ? '? : selectedPatient.gender === 'female' ? '? : selectedPatient.gender}</Descriptions.Item>
<Descriptions.Item label="年龄">{selectedPatient.age}?/Descriptions.Item> <Descriptions.Item label="年龄">{selectedPatient.age}?/Descriptions.Item>
<Descriptions.Item label="手机">{selectedPatient.phone}</Descriptions.Item> <Descriptions.Item label="手机">{selectedPatient.phone}</Descriptions.Item>
<Descriptions.Item label="紧急联?>{selectedPatient.emergency_contact || <Text type="secondary">未填</Text>}</Descriptions.Item> <Descriptions.Item label="紧急联>{selectedPatient.emergency_contact || <Text type="secondary">未填</Text>}</Descriptions.Item>
<Descriptions.Item label="医保">{selectedPatient.insurance_type || <Text type="secondary">未填</Text>}</Descriptions.Item> <Descriptions.Item label="医保">{selectedPatient.insurance_type || <Text type="secondary">未填</Text>}</Descriptions.Item>
<Descriptions.Item label="过敏? span={3}> <Descriptions.Item label="过敏感 span={3}>
{selectedPatient.allergy_history ? <Tag color={selectedPatient.allergy_history === '? ? 'green' : 'red'}>{selectedPatient.allergy_history}</Tag> : <Tag color="green">?/Tag>} {selectedPatient.allergy_history ? <Tag color={selectedPatient.allergy_history === '? ? 'green' : 'red'}>{selectedPatient.allergy_history}</Tag> : <Tag color="green">?/Tag>}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="病史" span={3}>{selectedPatient.medical_history || <Text type="secondary">?/Text>}</Descriptions.Item> <Descriptions.Item label="病史" span={3}>{selectedPatient.medical_history || <Text type="secondary">?/Text>}</Descriptions.Item>
...@@ -224,7 +224,7 @@ const PatientRecordPage: React.FC = () => { ...@@ -224,7 +224,7 @@ const PatientRecordPage: React.FC = () => {
</div> </div>
) : ( ) : (
<Card size="small" className="text-center py-10"> <Card size="small" className="text-center py-10">
<Empty description="请从左侧选择患者查看档? /> <Empty description="请从左侧选择患者查看档 />
</Card> </Card>
)} )}
</Spin> </Spin>
......
...@@ -58,7 +58,8 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -58,7 +58,8 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
const [remarkText, setRemarkText] = useState(''); const [remarkText, setRemarkText] = useState('');
const [form] = Form.useForm(); const [form] = Form.useForm();
// 鎵撳紑鏃跺垵濮嬪寲鎮h€呬俊鎭? React.useEffect(() => { //
React.useEffect(() => {
if (open) { if (open) {
form.setFieldsValue({ form.setFieldsValue({
patient_name: patientName || '', patient_name: patientName || '',
...@@ -90,13 +91,13 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -90,13 +91,13 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
const med = searchResults.find((m) => m.id === medicineId); const med = searchResults.find((m) => m.id === medicineId);
if (!med) return; if (!med) return;
if (drugs.find((d) => d.medicine_id === med.id)) { if (drugs.find((d) => d.medicine_id === med.id)) {
message.warning('璇ヨ嵂鍝佸凡娣诲姞'); return; message.warning('该药品已添加'); return;
} }
setDrugs([...drugs, { setDrugs([...drugs, {
key: med.id, medicine_id: med.id, name: med.name, key: med.id, medicine_id: med.id, name: med.name,
specification: med.specification, usage: '鍙f湇', dosage: '', specification: med.specification, usage: '口服', dosage: '',
frequency: '姣忔棩3娆?, days: 3, quantity: 1, frequency: '每日3次, days: 3, quantity: 1,
unit: med.unit || '鐩?, price: med.price, note: '', unit: med.unit || '盒, price: med.price, note: '',
}]); }]);
setSearchValue(''); setSearchValue('');
setSearchResults([]); setSearchResults([]);
...@@ -110,7 +111,7 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -110,7 +111,7 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
const totalAmount = drugs.reduce((sum, d) => sum + d.price * d.quantity, 0); const totalAmount = drugs.reduce((sum, d) => sum + d.price * d.quantity, 0);
const handleSign = async () => { const handleSign = async () => {
if (drugs.length === 0) { message.warning('璇疯嚦灏戞坊鍔犱竴绉嶈嵂鍝?); return; } if (drugs.length === 0) { message.warning('请至少添加一种药品); return; }
try { await form.validateFields(); } catch { return; } try { await form.validateFields(); } catch { return; }
setSignModalVisible(true); setSignModalVisible(true);
}; };
...@@ -135,11 +136,11 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -135,11 +136,11 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
quantity: d.quantity, unit: d.unit, price: d.price, note: d.note, quantity: d.quantity, unit: d.unit, price: d.price, note: d.note,
})), })),
}); });
message.success('澶勬柟宸茬鍚嶅苟鎻愪氦'); message.success('处方宸茬鍚嶅苟提交');
setSignModalVisible(false); setSignModalVisible(false);
handleClose(); handleClose();
} catch (err: any) { } catch (err: any) {
message.error(err?.message || '鎻愪氦澶勬柟澶辫触'); message.error(err?.message || '提交处方失败');
} finally { } finally {
setSubmitting(false); setSubmitting(false);
} }
...@@ -147,47 +148,47 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -147,47 +148,47 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
const columns = [ const columns = [
{ title: '鑽搧鍚嶇О', dataIndex: 'name', key: 'name', width: 140, render: (t: string) => <Text strong>{t}</Text> }, { title: '鑽搧鍚嶇О', dataIndex: 'name', key: 'name', width: 140, render: (t: string) => <Text strong>{t}</Text> },
{ title: '瑙勬牸', dataIndex: 'specification', key: 'specification', width: 90 }, { title: '规格', dataIndex: 'specification', key: 'specification', width: 90 },
{ {
title: '鐢ㄦ硶', dataIndex: 'usage', key: 'usage', width: 100, title: '用法', dataIndex: 'usage', key: 'usage', width: 100,
render: (_: string, r: DrugItem) => ( render: (_: string, r: DrugItem) => (
<Select value={r.usage} size="small" style={{ width: 90 }} <Select value={r.usage} size="small" style={{ width: 90 }}
onChange={(v) => handleDrugChange(r.key, 'usage', v)} onChange={(v) => handleDrugChange(r.key, 'usage', v)}
options={[ options={[
{ label: '鍙f湇', value: '鍙f湇' }, { label: '澶栫敤', value: '澶栫敤' }, { label: '口服', value: '口服' }, { label: '外用', value: '外用' },
{ label: '闈欒剦娉ㄥ皠', value: '闈欒剦娉ㄥ皠' }, { label: '鑲岃倝娉ㄥ皠', value: '鑲岃倝娉ㄥ皠' }, { label: '静脉注射', value: '静脉注射' }, { label: '肌肉注射', value: '肌肉注射' },
{ label: '闆惧寲鍚稿叆', value: '闆惧寲鍚稿叆' }, { label: '雾化吸入', value: '雾化吸入' },
]} /> ]} />
), ),
}, },
{ {
title: '鍗曟鍓傞噺', dataIndex: 'dosage', key: 'dosage', width: 110, title: '鍗曟鍓傞噺', dataIndex: 'dosage', key: 'dosage', width: 110,
render: (_: string, r: DrugItem) => ( render: (_: string, r: DrugItem) => (
<Input size="small" placeholder="濡? 1绮? value={r.dosage} <Input size="small" placeholder="如 1粒 value={r.dosage}
onChange={(e) => handleDrugChange(r.key, 'dosage', e.target.value)} /> onChange={(e) => handleDrugChange(r.key, 'dosage', e.target.value)} />
), ),
}, },
{ {
title: '棰戞', dataIndex: 'frequency', key: 'frequency', width: 110, title: '频次', dataIndex: 'frequency', key: 'frequency', width: 110,
render: (_: string, r: DrugItem) => ( render: (_: string, r: DrugItem) => (
<Select value={r.frequency} size="small" style={{ width: 100 }} <Select value={r.frequency} size="small" style={{ width: 100 }}
onChange={(v) => handleDrugChange(r.key, 'frequency', v)} onChange={(v) => handleDrugChange(r.key, 'frequency', v)}
options={[ options={[
{ label: '姣忔棩1娆?, value: '姣忔棩1娆? }, { label: '姣忔棩2娆?, value: '姣忔棩2娆? }, { label: '每日1次, value: '每日1次 }, { label: '每日2次, value: '每日2次 },
{ label: '姣忔棩3娆?, value: '姣忔棩3娆? }, { label: '蹇呰鏃?, value: '蹇呰鏃? }, { label: '每日3次, value: '每日3次 }, { label: '蹇呰无, value: '蹇呰无 },
{ label: '鐫″墠', value: '鐫″墠' }, { label: '睡前', value: '睡前' },
]} /> ]} />
), ),
}, },
{ {
title: '澶╂暟', dataIndex: 'days', key: 'days', width: 70, title: '天数', dataIndex: 'days', key: 'days', width: 70,
render: (_: number, r: DrugItem) => ( render: (_: number, r: DrugItem) => (
<InputNumber size="small" min={1} max={30} value={r.days} <InputNumber size="small" min={1} max={30} value={r.days}
onChange={(v) => handleDrugChange(r.key, 'days', v)} style={{ width: 60 }} /> onChange={(v) => handleDrugChange(r.key, 'days', v)} style={{ width: 60 }} />
), ),
}, },
{ {
title: '鏁伴噺', dataIndex: 'quantity', key: 'quantity', width: 100, title: '数量', dataIndex: 'quantity', key: 'quantity', width: 100,
render: (_: number, r: DrugItem) => ( render: (_: number, r: DrugItem) => (
<Space size={4}> <Space size={4}>
<InputNumber size="small" min={1} value={r.quantity} <InputNumber size="small" min={1} value={r.quantity}
...@@ -197,11 +198,11 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -197,11 +198,11 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
), ),
}, },
{ {
title: '鍗曚环', dataIndex: 'price', key: 'price', width: 70, title: '单价', dataIndex: 'price', key: 'price', width: 70,
render: (v: number) => `楼${(v / 100).toFixed(2)}`, render: (v: number) => `楼${(v / 100).toFixed(2)}`,
}, },
{ {
title: '鎿嶄綔', key: 'action', width: 50, title: '操作', key: 'action', width: 50,
render: (_: any, r: DrugItem) => ( render: (_: any, r: DrugItem) => (
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleRemoveDrug(r.key)} /> <Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleRemoveDrug(r.key)} />
), ),
...@@ -211,24 +212,24 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -211,24 +212,24 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
return ( return (
<> <>
<Modal <Modal
title={<span className="text-xs"><MedicineBoxOutlined className="text-green-500 mr-1" />寮€鍏峰鏂?/span>} title={<span className="text-xs"><MedicineBoxOutlined className="text-green-500 mr-1" />寮€共峰鏂</span>}
open={open} onCancel={handleClose} width={960} footer={null} destroyOnClose> open={open} onCancel={handleClose} width={960} footer={null} destroyOnClose>
<Form layout="inline" form={form} size="small" className="mb-2 p-2 bg-gray-50 rounded"> <Form layout="inline" form={form} size="small" className="mb-2 p-2 bg-gray-50 rounded">
<Form.Item label="濮撳悕" name="patient_name" rules={[{ required: true, message: '璇疯緭鍏? }]}> <Form.Item label="姓名" name="patient_name" rules={[{ required: true, message: '请输入 }]}>
<Input placeholder="璇疯緭鍏? style={{ width: 100 }} /> <Input placeholder="请输入 style={{ width: 100 }} />
</Form.Item> </Form.Item>
<Form.Item label="鎬у埆" name="patient_gender"> <Form.Item label="性别" name="patient_gender">
<Select placeholder="鎬у埆" style={{ width: 70 }} <Select placeholder="性别" style={{ width: 70 }}
options={[{ label: '鐢?, value: '鐢? }, { label: '濂?, value: '濂? }]} allowClear /> options={[{ label: '男, value: '男 }, { label: '女, value: '女 }]} allowClear />
</Form.Item> </Form.Item>
<Form.Item label="骞撮緞" name="patient_age"> <Form.Item label="年龄" name="patient_age">
<InputNumber placeholder="宀? style={{ width: 60 }} min={0} max={150} /> <InputNumber placeholder="岁 style={{ width: 60 }} min={0} max={150} />
</Form.Item> </Form.Item>
<Form.Item label="璇婃柇" name="diagnosis" rules={[{ required: true, message: '璇疯緭鍏? }]}> <Form.Item label="诊断" name="diagnosis" rules={[{ required: true, message: '请输入 }]}>
<Input placeholder="璇婃柇" style={{ width: 160 }} /> <Input placeholder="诊断" style={{ width: 160 }} />
</Form.Item> </Form.Item>
<Form.Item label="杩囨晱" name="allergy"> <Form.Item label="过敏" name="allergy">
<Input placeholder="杩囨晱鍙? style={{ width: 110 }} /> <Input placeholder="过敏史 style={{ width: 110 }} />
</Form.Item> </Form.Item>
</Form> </Form>
...@@ -236,12 +237,12 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -236,12 +237,12 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
<Space size={4}> <Space size={4}>
<MedicineBoxOutlined /> <MedicineBoxOutlined />
<Text strong className="text-xs!">鑽搧</Text> <Text strong className="text-xs!">鑽搧</Text>
<Tag color="blue">{drugs.length} 绉?/Tag> <Tag color="blue">{drugs.length} 种</Tag>
</Space> </Space>
<AutoComplete value={searchValue} <AutoComplete value={searchValue}
options={searchResults.map((m) => ({ value: m.id, label: `${m.name} (${m.specification}) 搴撳瓨:${m.stock}` }))} options={searchResults.map((m) => ({ value: m.id, label: `${m.name} (${m.specification}) 库存:${m.stock}` }))}
onSelect={handleAddDrug} onSearch={handleSearch} onChange={setSearchValue} style={{ width: 260 }}> onSelect={handleAddDrug} onSearch={handleSearch} onChange={setSearchValue} style={{ width: 260 }}>
<Input prefix={<SearchOutlined />} placeholder="鎼滅储鑽搧..." allowClear size="small" /> <Input prefix={<SearchOutlined />} placeholder="搜索鑽搧..." allowClear size="small" />
</AutoComplete> </AutoComplete>
</div> </div>
...@@ -249,7 +250,7 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -249,7 +250,7 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
<Table columns={columns} dataSource={drugs} pagination={false} size="small" <Table columns={columns} dataSource={drugs} pagination={false} size="small"
rowKey="key" scroll={{ x: 800 }} className="mb-2" /> rowKey="key" scroll={{ x: 800 }} className="mb-2" />
) : ( ) : (
<Empty description="璇锋悳绱㈠苟娣诲姞鑽搧" className="my-3" /> <Empty description="璇锋悳绱㈠苟添加鑽搧" className="my-3" />
)} )}
{drugs.length > 0 && ( {drugs.length > 0 && (
...@@ -258,26 +259,26 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({ ...@@ -258,26 +259,26 @@ const PrescriptionModal: React.FC<PrescriptionModalProps> = ({
</div> </div>
)} )}
<TextArea rows={2} placeholder="鍖诲槺澶囨敞..." size="small" <TextArea rows={2} placeholder="医嘱备注..." size="small"
value={remarkText} onChange={(e) => setRemarkText(e.target.value)} className="mb-2" /> value={remarkText} onChange={(e) => setRemarkText(e.target.value)} className="mb-2" />
<div className="flex justify-end gap-1"> <div className="flex justify-end gap-1">
<Button size="small" icon={<PrinterOutlined />} onClick={() => message.info('鎵撳嵃鍔熻兘寮€鍙戜腑')}>鎵撳嵃</Button> <Button size="small" icon={<PrinterOutlined />} onClick={() => message.info('打印功能开发中')}>打印</Button>
<Button size="small" onClick={handleClose}>鍙栨秷</Button> <Button size="small" onClick={handleClose}>取消</Button>
<Button type="primary" size="small" icon={<SafetyCertificateOutlined />} <Button type="primary" size="small" icon={<SafetyCertificateOutlined />}
style={{ background: '#52c41a', borderColor: '#52c41a' }} onClick={handleSign}>绛惧悕鎻愪氦</Button> style={{ background: '#52c41a', borderColor: '#52c41a' }} onClick={handleSign}>签名提交</Button>
</div> </div>
</Modal> </Modal>
<Modal title="CA 绛惧悕纭" open={signModalVisible} onOk={handleConfirmSign} <Modal title="CA 签名确认" open={signModalVisible} onOk={handleConfirmSign}
onCancel={() => setSignModalVisible(false)} okText="纭绛惧悕" cancelText="鍙栨秷" onCancel={() => setSignModalVisible(false)} okText="确绛惧悕" cancelText="取消"
confirmLoading={submitting} width={380}> confirmLoading={submitting} width={380}>
<div className="text-center py-3"> <div className="text-center py-3">
<SafetyCertificateOutlined className="text-3xl text-green-500 mb-2" /> <SafetyCertificateOutlined className="text-3xl text-green-500 mb-2" />
<div className="text-xs font-semibold mb-1">纭浣跨敤 CA 鏁板瓧璇佷功绛剧讲姝ゅ鏂癸紵</div> <div className="text-xs font-semibold mb-1">确浣跨敤 CA 鏁板瓧璇佷功绛剧讲姝ゅ鏂癸紵</div>
<Text type="secondary" className="text-xs!">绛剧讲鍚庡鏂瑰皢鍏锋湁娉曞緥鏁堝姏</Text> <Text type="secondary" className="text-xs!">绛剧讲鍚庡鏂瑰皢共锋湁娉曞緥鏁堝姏</Text>
<div className="mt-3 p-2 bg-green-50 rounded text-xs"> <div className="mt-3 p-2 bg-green-50 rounded text-xs">
<div>鑽搧锛?Text strong>{drugs.length}</Text> 绉?/div> <div>鑽搧锛?Text strong>{drugs.length}</Text> 种</div>
<div>閲戦锛?Text strong className="text-red-500!">楼{(totalAmount / 100).toFixed(2)}</Text></div> <div>閲戦锛?Text strong className="text-red-500!">楼{(totalAmount / 100).toFixed(2)}</Text></div>
</div> </div>
</div> </div>
......
...@@ -20,8 +20,8 @@ const DoctorProfilePage: React.FC = () => { ...@@ -20,8 +20,8 @@ const DoctorProfilePage: React.FC = () => {
department_name: '内科', department_name: '内科',
hospital: '北京协和医院', hospital: '北京协和医院',
license_no: 'XXXXX', license_no: 'XXXXX',
introduction: '从事内科临床工作20余年,擅长高血压、糖尿病等慢性病的诊治?, introduction: '从事内科临床工作20余年,擅长高血压、糖尿病等慢性病的诊治,
specialties: ['高血?, '糖尿?, '冠心?, '心律失常'], specialties: ['高血压, '糖尿, '冠心, '心律失常'],
price: 5000, price: 5000,
is_online: false, is_online: false,
ai_assist_enabled: true, ai_assist_enabled: true,
...@@ -43,7 +43,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -43,7 +43,7 @@ const DoctorProfilePage: React.FC = () => {
</div> </div>
<div className="text-xs text-gray-500 mb-1">{doctorProfile.hospital} · {doctorProfile.department_name}</div> <div className="text-xs text-gray-500 mb-1">{doctorProfile.hospital} · {doctorProfile.department_name}</div>
<Space size={4}> <Space size={4}>
<Tag icon={<SafetyCertificateOutlined />} color="success">已认?/Tag> <Tag icon={<SafetyCertificateOutlined />} color="success">已认证</Tag>
<Tag>证号:{doctorProfile.license_no}</Tag> <Tag>证号:{doctorProfile.license_no}</Tag>
</Space> </Space>
</Col> </Col>
...@@ -72,7 +72,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -72,7 +72,7 @@ const DoctorProfilePage: React.FC = () => {
</Space> </Space>
</Card> </Card>
<Card title={<span className="text-xs font-semibold">个人简?/span>} size="small"> <Card title={<span className="text-xs font-semibold">个人简</span>} size="small">
<Text className="text-xs!">{doctorProfile.introduction}</Text> <Text className="text-xs!">{doctorProfile.introduction}</Text>
</Card> </Card>
...@@ -81,7 +81,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -81,7 +81,7 @@ const DoctorProfilePage: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<Text strong className="text-xs!">AI 辅助接诊</Text> <Text strong className="text-xs!">AI 辅助接诊</Text>
<div className="text-[11px] text-gray-400">开启后 AI 将协助分析患者症?/div> <div className="text-[11px] text-gray-400">开启后 AI 将协助分析患者症</div>
</div> </div>
<Switch size="small" checked={doctorProfile.ai_assist_enabled} /> <Switch size="small" checked={doctorProfile.ai_assist_enabled} />
</div> </div>
...@@ -89,7 +89,7 @@ const DoctorProfilePage: React.FC = () => { ...@@ -89,7 +89,7 @@ const DoctorProfilePage: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<Text strong className="text-xs!">自动接诊</Text> <Text strong className="text-xs!">自动接诊</Text>
<div className="text-[11px] text-gray-400">开启后图文问诊将自动接?/div> <div className="text-[11px] text-gray-400">开启后图文问诊将自动接诊</div>
</div> </div>
<Switch size="small" /> <Switch size="small" />
</div> </div>
......
...@@ -22,7 +22,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -22,7 +22,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c1', consult_id: 'c1',
patient_name: '张三', patient_name: '张三',
patient_avatar: '', patient_avatar: '',
chief_complaint: '头痛、乏力,持续3天。既往有高血压病史?, chief_complaint: '头痛、乏力,持续3天。既往有高血压病史记,
type: 'text' as const, type: 'text' as const,
waiting_time: 300, waiting_time: 300,
created_at: '2026-02-25T13:00:00Z', created_at: '2026-02-25T13:00:00Z',
...@@ -32,7 +32,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -32,7 +32,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c2', consult_id: 'c2',
patient_name: '李四', patient_name: '李四',
patient_avatar: '', patient_avatar: '',
chief_complaint: '咳嗽有痰,偶尔发热,已持续一周?, chief_complaint: '咳嗽有痰,偶尔发热,已持续一周期,
type: 'video' as const, type: 'video' as const,
waiting_time: 180, waiting_time: 180,
created_at: '2026-02-25T13:05:00Z', created_at: '2026-02-25T13:05:00Z',
...@@ -42,7 +42,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -42,7 +42,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c3', consult_id: 'c3',
patient_name: '王五', patient_name: '王五',
patient_avatar: '', patient_avatar: '',
chief_complaint: '复诊开药,糖尿病用药续方?, chief_complaint: '复诊开药,糖尿病用药续方案,
type: 'text' as const, type: 'text' as const,
waiting_time: 60, waiting_time: 60,
created_at: '2026-02-25T13:10:00Z', created_at: '2026-02-25T13:10:00Z',
...@@ -52,7 +52,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -52,7 +52,7 @@ const DoctorQueuePage: React.FC = () => {
consult_id: 'c4', consult_id: 'c4',
patient_name: '赵六', patient_name: '赵六',
patient_avatar: '', patient_avatar: '',
chief_complaint: '皮肤瘙痒红肿,疑似过敏?, chief_complaint: '皮肤瘙痒红肿,疑似过敏感,
type: 'video' as const, type: 'video' as const,
waiting_time: 30, waiting_time: 30,
created_at: '2026-02-25T13:12:00Z', created_at: '2026-02-25T13:12:00Z',
...@@ -79,7 +79,7 @@ const DoctorQueuePage: React.FC = () => { ...@@ -79,7 +79,7 @@ const DoctorQueuePage: React.FC = () => {
<Card size="small"> <Card size="small">
{waitingPatients.length === 0 ? ( {waitingPatients.length === 0 ? (
<Empty description="暂无等待患? /> <Empty description="暂无等待患者 />
) : ( ) : (
<List <List
itemLayout="horizontal" itemLayout="horizontal"
......
...@@ -19,7 +19,7 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -19,7 +19,7 @@ const DoctorSchedulePage: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
// TODO: 浠嶢PI鑾峰彇鐪熷疄鏁版嵁 // TODO: 从API获取真实数据
const scheduleData: Record<string, ScheduleSlot[]> = { const scheduleData: Record<string, ScheduleSlot[]> = {
'2026-02-25': [ '2026-02-25': [
{ id: '1', date: '2026-02-25', start_time: '09:00', end_time: '12:00', max_count: 10, remaining: 5 }, { id: '1', date: '2026-02-25', start_time: '09:00', end_time: '12:00', max_count: 10, remaining: 5 },
...@@ -57,8 +57,8 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -57,8 +57,8 @@ const DoctorSchedulePage: React.FC = () => {
}; };
const handleSaveSchedule = async (values: any) => { const handleSaveSchedule = async (values: any) => {
// TODO: 璋冪敤API淇濆瓨鎺掔彮 // TODO: 调用API保存排班
console.log('淇濆瓨鎺掔彮:', { console.log('保存排班:', {
date: selectedDate.format('YYYY-MM-DD'), date: selectedDate.format('YYYY-MM-DD'),
start_time: values.time_range[0].format('HH:mm'), start_time: values.time_range[0].format('HH:mm'),
end_time: values.time_range[1].format('HH:mm'), end_time: values.time_range[1].format('HH:mm'),
...@@ -68,12 +68,12 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -68,12 +68,12 @@ const DoctorSchedulePage: React.FC = () => {
}; };
const handleDeleteSlot = (slotId: string) => { const handleDeleteSlot = (slotId: string) => {
// TODO: 璋冪敤API鍒犻櫎鎺掔彮 // TODO: 调用API删除排班
Modal.confirm({ Modal.confirm({
title: '纭鍒犻櫎', title: '确删除',
content: '纭畾瑕佸垹闄ゆ鎺掔彮鏃舵鍚楋紵', content: '确畾瑕佸垹闄ゆ排班无舵鍚楋紵',
onOk: () => { onOk: () => {
console.log('鍒犻櫎鎺掔彮:', slotId); console.log('删除排班:', slotId);
}, },
}); });
}; };
...@@ -81,8 +81,8 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -81,8 +81,8 @@ const DoctorSchedulePage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">鎺掔彮绠$悊</h4> <h4 className="text-sm font-bold text-gray-800 m-0">排班管理</h4>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAddSchedule}>娣诲姞鎺掔彮</Button> <Button size="small" type="primary" icon={<PlusOutlined />} onClick={handleAddSchedule}>添加排班</Button>
</div> </div>
<Row gutter={12}> <Row gutter={12}>
...@@ -99,15 +99,15 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -99,15 +99,15 @@ const DoctorSchedulePage: React.FC = () => {
<Col span={8}> <Col span={8}>
<Card <Card
title={<span className="text-xs font-semibold"><CalendarOutlined className="mr-1" />{selectedDate.format('MM鏈圖D鏃?)} 鎺掔彮</span>} title={<span className="text-xs font-semibold"><CalendarOutlined className="mr-1" />{selectedDate.format('MM月DD日)} 排班</span>}
extra={<Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule}>娣诲姞</Button>} extra={<Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule}>添加</Button>}
size="small" size="small"
> >
{todaySchedule.length === 0 ? ( {todaySchedule.length === 0 ? (
<div className="text-center py-6"> <div className="text-center py-6">
<Text type="secondary" className="text-xs!">褰撴棩鏆傛棤鎺掔彮</Text> <Text type="secondary" className="text-xs!">当日暂无排班</Text>
<br /> <br />
<Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule} className="mt-1">娣诲姞鎺掔彮</Button> <Button type="link" size="small" icon={<PlusOutlined />} onClick={handleAddSchedule} className="mt-1">添加排班</Button>
</div> </div>
) : ( ) : (
<List <List
...@@ -124,8 +124,8 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -124,8 +124,8 @@ const DoctorSchedulePage: React.FC = () => {
<Text strong className="text-xs!">{slot.start_time} - {slot.end_time}</Text> <Text strong className="text-xs!">{slot.start_time} - {slot.end_time}</Text>
</div> </div>
<Space size={4}> <Space size={4}>
<Tag color="blue">鏈€澶?{slot.max_count} 浜?/Tag> <Tag color="blue">最大 {slot.max_count} 人</Tag>
<Tag color={slot.remaining > 0 ? 'green' : 'red'}>鍓╀綑 {slot.remaining} 鍙?/Tag> <Tag color={slot.remaining > 0 ? 'green' : 'red'}>剩余 {slot.remaining} 号</Tag>
</Space> </Space>
</div> </div>
</List.Item> </List.Item>
...@@ -137,17 +137,17 @@ const DoctorSchedulePage: React.FC = () => { ...@@ -137,17 +137,17 @@ const DoctorSchedulePage: React.FC = () => {
</Row> </Row>
<Modal <Modal
title={`娣诲姞鎺掔彮 - ${selectedDate.format('YYYY-MM-DD')}`} title={`添加排班 - ${selectedDate.format('YYYY-MM-DD')}`}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
onOk={() => form.submit()} onOk={() => form.submit()}
width={400} width={400}
> >
<Form form={form} onFinish={handleSaveSchedule} layout="vertical" size="small"> <Form form={form} onFinish={handleSaveSchedule} layout="vertical" size="small">
<Form.Item name="time_range" label="鏃堕棿娈? rules={[{ required: true, message: '璇烽€夋嫨鏃堕棿娈? }]}> <Form.Item name="time_range" label="时间段 rules={[{ required: true, message: '请选择时间段 }]}>
<TimePicker.RangePicker format="HH:mm" minuteStep={30} style={{ width: '100%' }} /> <TimePicker.RangePicker format="HH:mm" minuteStep={30} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
<Form.Item name="max_count" label="鏈€澶ф帴璇婃暟" rules={[{ required: true, message: '璇疯緭鍏ユ渶澶ф帴璇婃暟' }]} initialValue={10}> <Form.Item name="max_count" label="最大接诊数" rules={[{ required: true, message: '请输入最大接诊数' }]} initialValue={10}>
<InputNumber min={1} max={50} style={{ width: '100%' }} /> <InputNumber min={1} max={50} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Form> </Form>
......
'use client'; 'use client';
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Card, Row, Col, Statistic, Typography, List, Avatar, Tag, Button, Space, Badge, Alert } from 'antd'; import { Card, Row, Col, Statistic, Typography, List, Avatar, Tag, Button, Space, Badge, Alert } from 'antd';
...@@ -39,7 +39,7 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -39,7 +39,7 @@ const DoctorWorkbenchPage: React.FC = () => {
// 检查医生是否已认证 // 检查医生是否已认证
const isVerified = user?.is_verified; const isVerified = user?.is_verified;
// 获取工作台数? // 获取工作台数量
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
try { try {
const [statsRes, patientsRes] = await Promise.all([ const [statsRes, patientsRes] = await Promise.all([
...@@ -47,13 +47,13 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -47,13 +47,13 @@ const DoctorWorkbenchPage: React.FC = () => {
consultApi.getPatientList(), consultApi.getPatientList(),
]); ]);
setStats(statsRes.data); setStats(statsRes.data);
// 只显示待接诊的患? // 只显示待接诊的患者
const waiting = (patientsRes.data || []).filter( const waiting = (patientsRes.data || []).filter(
p => p.status === 'pending' || p.status === 'waiting' p => p.status === 'pending' || p.status === 'waiting'
); );
setWaitingPatients(waiting.slice(0, 5)); setWaitingPatients(waiting.slice(0, 5));
} catch (error) { } catch (error) {
console.error('获取工作台数据失?', error); console.error('获取工作台数据失败', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -75,13 +75,13 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -75,13 +75,13 @@ const DoctorWorkbenchPage: React.FC = () => {
if (!isVerified) { if (!isVerified) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<h4 className="text-sm font-bold text-gray-800 m-0">医生工作?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">医生工作台</h4>
<Alert <Alert
message="请先完成资质认证" message="请先完成资质认证"
description={ description={
<div className="text-xs"> <div className="text-xs">
<p className="mb-1">您的账号尚未完成医生资质认证,无法接诊患者?/p> <p className="mb-1">您的账号尚未完成医生资质认证,无法接诊患者</p>
<p className="mb-2">请提交您的执业证、资格证等相关资料,审核通过后即可开始接诊?/p> <p className="mb-2">请提交您的执业证、资格证等相关资料,审核通过后即可开始接诊量</p>
<Button type="primary" size="small" icon={<ExclamationCircleOutlined />} <Button type="primary" size="small" icon={<ExclamationCircleOutlined />}
onClick={() => router.push('/doctor/certification')}> onClick={() => router.push('/doctor/certification')}>
立即认证 立即认证
...@@ -98,17 +98,17 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -98,17 +98,17 @@ const DoctorWorkbenchPage: React.FC = () => {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">医生工作?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">医生工作台</h4>
<span className="text-xs text-gray-400">?0秒自动刷?/span> <span className="text-xs text-gray-400">每10秒自动刷新</span>
</div> </div>
{/* 核心指标 */} {/* 核心指标 */}
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{[ {[
{ title: '今日问诊', value: stats.today_consult_count, icon: <MessageOutlined />, color: '#1890ff', bg: '#e6f7ff', suffix: '人次' }, { title: '今日问诊', value: stats.today_consult_count, icon: <MessageOutlined />, color: '#1890ff', bg: '#e6f7ff', suffix: '人次' },
{ title: '等待接诊', value: stats.waiting_count, icon: <ClockCircleOutlined />, color: '#fa8c16', bg: '#fff7e6', suffix: '? }, { title: '等待接诊', value: stats.waiting_count, icon: <ClockCircleOutlined />, color: '#fa8c16', bg: '#fff7e6', suffix: '人' },
{ title: '今日完成', value: stats.completed_today, icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', suffix: '? }, { title: '今日完成', value: stats.completed_today, icon: <CheckCircleOutlined />, color: '#52c41a', bg: '#f6ffed', suffix: '例' },
{ title: '今日收入', value: stats.income_today, icon: <DollarOutlined />, color: '#722ed1', bg: '#f9f0ff', suffix: '? }, { title: '今日收入', value: stats.income_today, icon: <DollarOutlined />, color: '#722ed1', bg: '#f9f0ff', suffix: '元' },
].map((item, i) => ( ].map((item, i) => (
<Col xs={12} sm={6} key={i}> <Col xs={12} sm={6} key={i}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa] hover:shadow-md transition-shadow"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa] hover:shadow-md transition-shadow">
...@@ -127,10 +127,10 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -127,10 +127,10 @@ const DoctorWorkbenchPage: React.FC = () => {
{/* 综合数据 */} {/* 综合数据 */}
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{[ {[
{ icon: <TeamOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '累计患?, value: stats.total_patients }, { icon: <TeamOutlined />, color: '#1890ff', bg: '#e6f7ff', label: '累计患者', value: stats.total_patients },
{ icon: <StarOutlined />, color: '#fadb14', bg: '#fffbe6', label: '综合评分', value: stats.rating }, { icon: <StarOutlined />, color: '#fadb14', bg: '#fffbe6', label: '综合评分', value: stats.rating },
{ icon: <RiseOutlined />, color: '#52c41a', bg: '#f6ffed', label: '本月收入', value: `¥${stats.income_month}` }, { icon: <RiseOutlined />, color: '#52c41a', bg: '#f6ffed', label: '本月收入', value: `¥${stats.income_month}` },
{ icon: <MessageOutlined />, color: '#13c2c2', bg: '#e6fffb', label: '进行?, value: stats.in_progress_count }, { icon: <MessageOutlined />, color: '#13c2c2', bg: '#e6fffb', label: '进行中', value: stats.in_progress_count },
].map((item, i) => ( ].map((item, i) => (
<Col xs={12} sm={6} key={i}> <Col xs={12} sm={6} key={i}>
<div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center"> <div className="bg-white rounded-lg p-3 border border-[#e6f0fa] text-center">
...@@ -152,7 +152,7 @@ const DoctorWorkbenchPage: React.FC = () => { ...@@ -152,7 +152,7 @@ const DoctorWorkbenchPage: React.FC = () => {
<List <List
size="small" size="small"
dataSource={waitingPatients} dataSource={waitingPatients}
locale={{ emptyText: '暂无等待接诊的患? }} locale={{ emptyText: '暂无等待接诊的患者' }}
renderItem={(patient) => ( renderItem={(patient) => (
<List.Item <List.Item
actions={[ actions={[
......
...@@ -24,8 +24,8 @@ const controlStatusMap: Record<string, { color: string; label: string }> = { ...@@ -24,8 +24,8 @@ const controlStatusMap: Record<string, { color: string; label: string }> = {
}; };
const metricTypeMap: Record<string, { label: string; unit: string; hasValue2: boolean; v1Label: string; v2Label?: string }> = { const metricTypeMap: Record<string, { label: string; unit: string; hasValue2: boolean; v1Label: string; v2Label?: string }> = {
blood_pressure: { label: '血?, unit: 'mmHg', hasValue2: true, v1Label: '收缩?, v2Label: '舒张? }, blood_pressure: { label: '血压, unit: 'mmHg', hasValue2: true, v1Label: '收缩压, v2Label: '舒张压 },
blood_sugar: { label: '血?, unit: 'mmol/L', hasValue2: false, v1Label: '血糖? }, blood_sugar: { label: '血压, unit: 'mmol/L', hasValue2: false, v1Label: '血糖化 },
weight: { label: '体重', unit: 'kg', hasValue2: false, v1Label: '体重' }, weight: { label: '体重', unit: 'kg', hasValue2: false, v1Label: '体重' },
heart_rate: { label: '心率', unit: 'bpm', hasValue2: false, v1Label: '心率' }, heart_rate: { label: '心率', unit: 'bpm', hasValue2: false, v1Label: '心率' },
temperature: { label: '体温', unit: '?, hasValue2: false, v1Label: '体温' }, temperature: { label: '体温', unit: '?, hasValue2: false, v1Label: '体温' },
...@@ -82,7 +82,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -82,7 +82,7 @@ const ChronicRecordsTab: React.FC = () => {
{ title: '确诊医院', dataIndex: 'hospital', key: 'hospital', ellipsis: true }, { title: '确诊医院', dataIndex: 'hospital', key: 'hospital', ellipsis: true },
{ title: '主治医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 100 }, { title: '主治医生', dataIndex: 'doctor_name', key: 'doctor_name', width: 100 },
{ {
title: '控制状?, dataIndex: 'control_status', key: 'control_status', width: 100, title: '控制状态, dataIndex: 'control_status', key: 'control_status', width: 100,
render: (v: string) => { render: (v: string) => {
const s = controlStatusMap[v] || { color: 'default', label: v }; const s = controlStatusMap[v] || { color: 'default', label: v };
return <Tag color={s.color}>{s.label}</Tag>; return <Tag color={s.color}>{s.label}</Tag>;
...@@ -97,7 +97,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -97,7 +97,7 @@ const ChronicRecordsTab: React.FC = () => {
render: (_: any, r: ChronicRecord) => ( render: (_: any, r: ChronicRecord) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} /> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} />
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除外 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />} /> <Button type="link" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
</Space> </Space>
...@@ -129,7 +129,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -129,7 +129,7 @@ const ChronicRecordsTab: React.FC = () => {
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="control_status" label="控制状? initialValue="stable"> <Form.Item name="control_status" label="控制状态 initialValue="stable">
<Select options={Object.entries(controlStatusMap).map(([v, s]) => ({ value: v, label: s.label }))} /> <Select options={Object.entries(controlStatusMap).map(([v, s]) => ({ value: v, label: s.label }))} />
</Form.Item> </Form.Item>
</Col> </Col>
...@@ -147,7 +147,7 @@ const ChronicRecordsTab: React.FC = () => { ...@@ -147,7 +147,7 @@ const ChronicRecordsTab: React.FC = () => {
</Col> </Col>
</Row> </Row>
<Form.Item name="current_meds" label="当前用药"> <Form.Item name="current_meds" label="当前用药">
<TextArea rows={2} placeholder="当前服用的药? /> <TextArea rows={2} placeholder="当前服用的药物 />
</Form.Item> </Form.Item>
<Form.Item name="notes" label="备注"> <Form.Item name="notes" label="备注">
<TextArea rows={2} /> <TextArea rows={2} />
...@@ -175,7 +175,7 @@ const RenewalsTab: React.FC = () => { ...@@ -175,7 +175,7 @@ const RenewalsTab: React.FC = () => {
medicines: values.medicines?.split('\n').filter(Boolean) || [], medicines: values.medicines?.split('\n').filter(Boolean) || [],
}), }),
onSuccess: () => { onSuccess: () => {
message.success('续方申请已提?); message.success('续方申请已提);
qc.invalidateQueries({ queryKey: ['renewals'] }); qc.invalidateQueries({ queryKey: ['renewals'] });
setModalOpen(false); setModalOpen(false);
form.resetFields(); form.resetFields();
...@@ -196,9 +196,9 @@ const RenewalsTab: React.FC = () => { ...@@ -196,9 +196,9 @@ const RenewalsTab: React.FC = () => {
}; };
const statusMap: Record<string, { color: string; label: string }> = { const statusMap: Record<string, { color: string; label: string }> = {
pending: { color: 'orange', label: '待审? }, pending: { color: 'orange', label: '待审核 },
approved: { color: 'green', label: '已通过' }, approved: { color: 'green', label: '已通过' },
rejected: { color: 'red', label: '已拒? }, rejected: { color: 'red', label: '已拒绝 },
}; };
const columns = [ const columns = [
...@@ -212,7 +212,7 @@ const RenewalsTab: React.FC = () => { ...@@ -212,7 +212,7 @@ const RenewalsTab: React.FC = () => {
}, },
{ title: '申请原因', dataIndex: 'reason', key: 'reason', ellipsis: true }, { title: '申请原因', dataIndex: 'reason', key: 'reason', ellipsis: true },
{ {
title: '状?, dataIndex: 'status', key: 'status', width: 90, title: '状态, dataIndex: 'status', key: 'status', width: 90,
render: (v: string) => { const s = statusMap[v] || { color: 'default', label: v }; return <Tag color={s.color}>{s.label}</Tag>; }, render: (v: string) => { const s = statusMap[v] || { color: 'default', label: v }; return <Tag color={s.color}>{s.label}</Tag>; },
}, },
{ {
...@@ -254,13 +254,13 @@ const RenewalsTab: React.FC = () => { ...@@ -254,13 +254,13 @@ const RenewalsTab: React.FC = () => {
options={(records?.data || []).map(r => ({ value: r.id, label: r.disease_name }))} /> options={(records?.data || []).map(r => ({ value: r.id, label: r.disease_name }))} />
</Form.Item> </Form.Item>
<Form.Item name="disease_name" label="疾病名称" rules={[{ required: true }]}> <Form.Item name="disease_name" label="疾病名称" rules={[{ required: true }]}>
<Input placeholder="如:高血? /> <Input placeholder="如:高血压 />
</Form.Item> </Form.Item>
<Form.Item name="medicines" label="申请药品(每行一种)" rules={[{ required: true }]}> <Form.Item name="medicines" label="申请药品(每行一种)" rules={[{ required: true }]}>
<TextArea rows={3} placeholder="苯磺酸氨氯地平片&#10;厄贝沙坦? /> <TextArea rows={3} placeholder="苯磺酸氨氯地平片&#10;厄贝沙坦类 />
</Form.Item> </Form.Item>
<Form.Item name="reason" label="续方原因" rules={[{ required: true }]}> <Form.Item name="reason" label="续方原因" rules={[{ required: true }]}>
<TextArea rows={2} placeholder="请说明续方原? /> <TextArea rows={2} placeholder="请说明续方原件 />
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
...@@ -323,7 +323,7 @@ const RemindersTab: React.FC = () => { ...@@ -323,7 +323,7 @@ const RemindersTab: React.FC = () => {
render: (v: string) => { try { return (JSON.parse(v) as string[]).map((t, i) => <Tag key={i}>{t}</Tag>); } catch { return v; } }, render: (v: string) => { try { return (JSON.parse(v) as string[]).map((t, i) => <Tag key={i}>{t}</Tag>); } catch { return v; } },
}, },
{ {
title: '状?, dataIndex: 'is_active', key: 'is_active', width: 80, title: '状态, dataIndex: 'is_active', key: 'is_active', width: 80,
render: (v: boolean, r: MedicationReminder) => ( render: (v: boolean, r: MedicationReminder) => (
<Switch checked={v} size="small" onChange={() => toggleMutation.mutate(r.id)} /> <Switch checked={v} size="small" onChange={() => toggleMutation.mutate(r.id)} />
), ),
...@@ -333,7 +333,7 @@ const RemindersTab: React.FC = () => { ...@@ -333,7 +333,7 @@ const RemindersTab: React.FC = () => {
render: (_: any, r: MedicationReminder) => ( render: (_: any, r: MedicationReminder) => (
<Space> <Space>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} /> <Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)} />
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除外 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />} /> <Button type="link" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
</Space> </Space>
...@@ -364,13 +364,13 @@ const RemindersTab: React.FC = () => { ...@@ -364,13 +364,13 @@ const RemindersTab: React.FC = () => {
</Col> </Col>
</Row> </Row>
<Form.Item name="frequency" label="服药频次" rules={[{ required: true }]}> <Form.Item name="frequency" label="服药频次" rules={[{ required: true }]}>
<Select options={['每日一?,'每日两次','每日三次','每周一?,'按需服用'].map(v => ({ value: v, label: v }))} /> <Select options={['每日一次,'每日两次','每日三次','每周一次,'按需服用'].map(v => ({ value: v, label: v }))} />
</Form.Item> </Form.Item>
<Form.Item name="remind_times" label="提醒时间" rules={[{ required: true }]}> <Form.Item name="remind_times" label="提醒时间" rules={[{ required: true }]}>
<TimePicker.RangePicker format="HH:mm" style={{ width: '100%' }} /> <TimePicker.RangePicker format="HH:mm" style={{ width: '100%' }} />
</Form.Item> </Form.Item>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}><Form.Item name="start_date" label="开始日?><DatePicker style={{ width: '100%' }} /></Form.Item></Col> <Col span={12}><Form.Item name="start_date" label="开始日><DatePicker style={{ width: '100%' }} /></Form.Item></Col>
<Col span={12}><Form.Item name="end_date" label="结束日期"><DatePicker style={{ width: '100%' }} /></Form.Item></Col> <Col span={12}><Form.Item name="end_date" label="结束日期"><DatePicker style={{ width: '100%' }} /></Form.Item></Col>
</Row> </Row>
<Form.Item name="notes" label="备注"><Input /></Form.Item> <Form.Item name="notes" label="备注"><Input /></Form.Item>
...@@ -420,7 +420,7 @@ const MetricsTab: React.FC = () => { ...@@ -420,7 +420,7 @@ const MetricsTab: React.FC = () => {
const columns = [ const columns = [
{ {
title: '数?, key: 'value', title: '数量, key: 'value',
render: (_: any, r: HealthMetric) => ( render: (_: any, r: HealthMetric) => (
<Text strong style={{ color: '#1890ff' }}> <Text strong style={{ color: '#1890ff' }}>
{meta.hasValue2 ? `${r.value1}/${r.value2}` : r.value1} {r.unit} {meta.hasValue2 ? `${r.value1}/${r.value2}` : r.value1} {r.unit}
...@@ -435,14 +435,15 @@ const MetricsTab: React.FC = () => { ...@@ -435,14 +435,15 @@ const MetricsTab: React.FC = () => {
{ {
title: '操作', key: 'action', width: 80, title: '操作', key: 'action', width: 80,
render: (_: any, r: HealthMetric) => ( render: (_: any, r: HealthMetric) => (
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除外 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />} /> <Button type="link" size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
), ),
}, },
]; ];
// 最新值统? const latest = list[0]; //
const latest = list[0];
return ( return (
<> <>
...@@ -458,7 +459,7 @@ const MetricsTab: React.FC = () => { ...@@ -458,7 +459,7 @@ const MetricsTab: React.FC = () => {
<Row gutter={16} style={{ marginBottom: 16 }}> <Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={8}> <Col span={8}>
<Card size="small" style={{ borderRadius: 8, background: '#f0f5ff' }}> <Card size="small" style={{ borderRadius: 8, background: '#f0f5ff' }}>
<Statistic title="最新? value={meta.hasValue2 ? `${latest.value1}/${latest.value2}` : latest.value1} <Statistic title="最新增 value={meta.hasValue2 ? `${latest.value1}/${latest.value2}` : latest.value1}
suffix={meta.unit} valueStyle={{ color: '#1890ff' }} /> suffix={meta.unit} valueStyle={{ color: '#1890ff' }} />
</Card> </Card>
</Col> </Col>
...@@ -469,7 +470,7 @@ const MetricsTab: React.FC = () => { ...@@ -469,7 +470,7 @@ const MetricsTab: React.FC = () => {
</Col> </Col>
<Col span={8}> <Col span={8}>
<Card size="small" style={{ borderRadius: 8, background: '#fff7e6' }}> <Card size="small" style={{ borderRadius: 8, background: '#fff7e6' }}>
<Statistic title="最近记? value={dayjs(latest.recorded_at).format('MM-DD')} <Statistic title="最近记录 value={dayjs(latest.recorded_at).format('MM-DD')}
valueStyle={{ color: '#fa8c16' }} /> valueStyle={{ color: '#fa8c16' }} />
</Card> </Card>
</Col> </Col>
......
...@@ -25,7 +25,7 @@ const { TextArea } = Input; ...@@ -25,7 +25,7 @@ const { TextArea } = Input;
const ConsultCreatePage: React.FC = () => { const ConsultCreatePage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const [searchParams] = useSearchParams(); const searchParams = useSearchParams();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [doctor, setDoctor] = useState<Doctor | null>(null); const [doctor, setDoctor] = useState<Doctor | null>(null);
...@@ -51,7 +51,8 @@ const ConsultCreatePage: React.FC = () => { ...@@ -51,7 +51,8 @@ const ConsultCreatePage: React.FC = () => {
if (preConsultId) { if (preConsultId) {
const res = await preConsultApi.getDetail(preConsultId); const res = await preConsultApi.getDetail(preConsultId);
setPreConsult(res.data); setPreConsult(res.data);
// 用预问诊的主诉填? form.setFieldsValue({ //
form.setFieldsValue({
chief_complaint: res.data.chief_complaint || chiefComplaintParam, chief_complaint: res.data.chief_complaint || chiefComplaintParam,
}); });
} else if (chiefComplaintParam) { } else if (chiefComplaintParam) {
...@@ -79,16 +80,17 @@ const ConsultCreatePage: React.FC = () => { ...@@ -79,16 +80,17 @@ const ConsultCreatePage: React.FC = () => {
pre_consult_id: preConsultId || undefined, pre_consult_id: preConsultId || undefined,
}); });
message.success('问诊创建成功?); message.success('问诊创建成功!);
// 跳转到问诊对话页? if (consultType === 'video') { //
if (consultType === 'video') {
router.push(`/patient/consult/video/${res.data.id}`); router.push(`/patient/consult/video/${res.data.id}`);
} else { } else {
router.push(`/patient/consult/text/${res.data.id}`); router.push(`/patient/consult/text/${res.data.id}`);
} }
} catch (error: any) { } catch (error: any) {
if (error?.errorFields) return; if (error?.errorFields) return;
message.error('创建问诊失败: ' + (error?.response?.data?.message || error?.message || '请稍后重?)); message.error('创建问诊失败: ' + (error?.response?.data?.message || error?.message || '请稍后重试));
} finally { } finally {
setSubmitting(false); setSubmitting(false);
} }
...@@ -97,7 +99,7 @@ const ConsultCreatePage: React.FC = () => { ...@@ -97,7 +99,7 @@ const ConsultCreatePage: React.FC = () => {
if (loading) { if (loading) {
return ( return (
<div style={{ textAlign: 'center', padding: 80 }}> <div style={{ textAlign: 'center', padding: 80 }}>
<Spin size="large" tip="加载?.." /> <Spin size="large" tip="加载中.." />
</div> </div>
); );
} }
...@@ -109,7 +111,7 @@ const ConsultCreatePage: React.FC = () => { ...@@ -109,7 +111,7 @@ const ConsultCreatePage: React.FC = () => {
{consultType === 'video' ? <VideoCameraOutlined className="mr-1" /> : <MessageOutlined className="mr-1" />} {consultType === 'video' ? <VideoCameraOutlined className="mr-1" /> : <MessageOutlined className="mr-1" />}
创建{consultType === 'video' ? '视频' : '图文'}问诊 创建{consultType === 'video' ? '视频' : '图文'}问诊
</h4> </h4>
<Button size="small" icon={<ArrowLeftOutlined />} onClick={() => router.push(-1)}>返回</Button> <Button size="small" icon={<ArrowLeftOutlined />} onClick={() => router.back()}>返回</Button>
</div> </div>
{doctor && ( {doctor && (
...@@ -124,7 +126,7 @@ const ConsultCreatePage: React.FC = () => { ...@@ -124,7 +126,7 @@ const ConsultCreatePage: React.FC = () => {
</div> </div>
<Text type="secondary" className="text-xs!">{doctor.hospital}</Text> <Text type="secondary" className="text-xs!">{doctor.hospital}</Text>
<div className="mt-0.5"> <div className="mt-0.5">
<Tag color="orange">¥{(doctor.price / 100).toFixed(0)}/?/Tag> <Tag color="orange">¥{(doctor.price / 100).toFixed(0)}/次</Tag>
<Text type="secondary" className="text-xs!">评分 {doctor.rating} · {doctor.consult_count}?/Text> <Text type="secondary" className="text-xs!">评分 {doctor.rating} · {doctor.consult_count}?/Text>
</div> </div>
</div> </div>
...@@ -152,8 +154,8 @@ const ConsultCreatePage: React.FC = () => { ...@@ -152,8 +154,8 @@ const ConsultCreatePage: React.FC = () => {
<Card size="small"> <Card size="small">
<Form form={form} layout="vertical" size="small"> <Form form={form} layout="vertical" size="small">
<Form.Item label="主诉(症状描述)" name="chief_complaint" rules={[{ required: true, message: '请描述您的症? }]}> <Form.Item label="主诉(症状描述)" name="chief_complaint" rules={[{ required: true, message: '请描述您的症状 }]}>
<TextArea rows={3} placeholder="请详细描述您的症?.." maxLength={500} showCount /> <TextArea rows={3} placeholder="请详细描述您的症状.." maxLength={500} showCount />
</Form.Item> </Form.Item>
<Form.Item label="既往病史" name="medical_history"> <Form.Item label="既往病史" name="medical_history">
<TextArea rows={2} placeholder="如有慢性病、手术史等请填写" /> <TextArea rows={2} placeholder="如有慢性病、手术史等请填写" />
...@@ -161,9 +163,9 @@ const ConsultCreatePage: React.FC = () => { ...@@ -161,9 +163,9 @@ const ConsultCreatePage: React.FC = () => {
<Divider className="my-2!" /> <Divider className="my-2!" />
<Form.Item className="text-center mb-0!"> <Form.Item className="text-center mb-0!">
<Space size={8}> <Space size={8}>
<Button size="small" onClick={() => router.push(-1)}>取消</Button> <Button size="small" onClick={() => router.back()}>取消</Button>
<Button type="primary" size="small" onClick={handleSubmit} loading={submitting}> <Button type="primary" size="small" onClick={handleSubmit} loading={submitting}>
{submitting ? '提交?..' : `发起${consultType === 'video' ? '视频' : '图文'}问诊`} {submitting ? '提交中..' : `发起${consultType === 'video' ? '视频' : '图文'}问诊`}
</Button> </Button>
</Space> </Space>
</Form.Item> </Form.Item>
......
'use client'; 'use client';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { import {
...@@ -15,11 +15,11 @@ import dayjs from 'dayjs'; ...@@ -15,11 +15,11 @@ import dayjs from 'dayjs';
const { Text, Title } = Typography; const { Text, Title } = Typography;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '寰呮帴璇?, color: 'orange' }, pending: { text: '待接诊', color: 'orange' },
waiting: { text: '绛夊緟涓?, color: 'blue' }, waiting: { text: '等待中', color: 'blue' },
in_progress: { text: '闂瘖涓?, color: 'green' }, in_progress: { text: '问诊中', color: 'green' },
completed: { text: '宸插畬鎴?, color: 'default' }, completed: { text: '已完成', color: 'default' },
cancelled: { text: '宸插彇娑?, color: 'red' }, cancelled: { text: '已取消', color: 'red' },
}; };
const statusIcon: Record<string, React.ReactNode> = { const statusIcon: Record<string, React.ReactNode> = {
...@@ -42,7 +42,7 @@ const PatientConsultListPage: React.FC = () => { ...@@ -42,7 +42,7 @@ const PatientConsultListPage: React.FC = () => {
const res = await consultApi.getConsultList(status); const res = await consultApi.getConsultList(status);
setConsultList(res.data || []); setConsultList(res.data || []);
} catch { } catch {
message.error('鑾峰彇闂瘖鍒楄〃澶辫触'); message.error('获取问诊列表失败');
} finally { } finally {
setLoading(false); setLoading(false);
} }
...@@ -64,10 +64,10 @@ const PatientConsultListPage: React.FC = () => { ...@@ -64,10 +64,10 @@ const PatientConsultListPage: React.FC = () => {
const handleCancel = async (consult: Consultation) => { const handleCancel = async (consult: Consultation) => {
try { try {
await consultApi.cancelConsult(consult.id); await consultApi.cancelConsult(consult.id);
message.success('闂瘖宸插彇娑?); message.success('问诊已取消');
fetchList(activeTab === 'all' ? undefined : activeTab as ConsultStatus); fetchList(activeTab === 'all' ? undefined : activeTab as ConsultStatus);
} catch { } catch {
message.error('鍙栨秷澶辫触'); message.error('取消失败');
} }
}; };
...@@ -76,26 +76,26 @@ const PatientConsultListPage: React.FC = () => { ...@@ -76,26 +76,26 @@ const PatientConsultListPage: React.FC = () => {
if (consult.status === 'in_progress') { if (consult.status === 'in_progress') {
actions.push( actions.push(
<Button type="primary" size="small" onClick={() => handleEnterConsult(consult)}> <Button type="primary" size="small" onClick={() => handleEnterConsult(consult)}>
杩涘叆闂瘖 进入问诊
</Button> </Button>
); );
} }
if (consult.status === 'pending' || consult.status === 'waiting') { if (consult.status === 'pending' || consult.status === 'waiting') {
actions.push( actions.push(
<Button size="small" onClick={() => handleEnterConsult(consult)}> <Button size="small" onClick={() => handleEnterConsult(consult)}>
鏌ョ湅璇︽儏 查看详情
</Button> </Button>
); );
actions.push( actions.push(
<Button size="small" danger onClick={() => handleCancel(consult)}> <Button size="small" danger onClick={() => handleCancel(consult)}>
鍙栨秷 取消
</Button> </Button>
); );
} }
if (consult.status === 'completed' && consult.prescription_id) { if (consult.status === 'completed' && consult.prescription_id) {
actions.push( actions.push(
<Button size="small" onClick={() => router.push(`/patient/prescription/${consult.prescription_id}`)}> <Button size="small" onClick={() => router.push(`/patient/prescription/${consult.prescription_id}`)}>
鏌ョ湅澶勬柟 查看处方
</Button> </Button>
); );
} }
...@@ -107,9 +107,9 @@ const PatientConsultListPage: React.FC = () => { ...@@ -107,9 +107,9 @@ const PatientConsultListPage: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0"> <h4 className="text-sm font-bold text-gray-800 m-0">
<MedicineBoxOutlined className="mr-1 text-[#1890ff]" /> <MedicineBoxOutlined className="mr-1 text-[#1890ff]" />
鎴戠殑闂瘖 我的问诊
</h4> </h4>
<Button size="small" type="primary" onClick={() => router.push('/patient/doctors')}>鍙戣捣闂瘖</Button> <Button size="small" type="primary" onClick={() => router.push('/patient/doctors')}>发起问诊</Button>
</div> </div>
<Card size="small"> <Card size="small">
...@@ -118,11 +118,11 @@ const PatientConsultListPage: React.FC = () => { ...@@ -118,11 +118,11 @@ const PatientConsultListPage: React.FC = () => {
onChange={setActiveTab} onChange={setActiveTab}
size="small" size="small"
items={[ items={[
{ key: 'all', label: '鍏ㄩ儴' }, { key: 'all', label: '全部' },
{ key: 'in_progress', label: '杩涜涓? }, { key: 'in_progress', label: '进行中' },
{ key: 'pending', label: '寰呮帴璇? }, { key: 'pending', label: '待接诊' },
{ key: 'completed', label: '宸插畬鎴? }, { key: 'completed', label: '已完成' },
{ key: 'cancelled', label: '宸插彇娑? }, { key: 'cancelled', label: '已取消' },
]} ]}
/> />
...@@ -130,7 +130,7 @@ const PatientConsultListPage: React.FC = () => { ...@@ -130,7 +130,7 @@ const PatientConsultListPage: React.FC = () => {
<List <List
size="small" size="small"
dataSource={consultList} dataSource={consultList}
locale={{ emptyText: <Empty description="鏆傛棤闂瘖璁板綍" /> }} locale={{ emptyText: <Empty description="暂无问诊记录" /> }}
renderItem={(consult: Consultation) => { renderItem={(consult: Consultation) => {
const status = statusMap[consult.status] || { text: consult.status, color: 'default' }; const status = statusMap[consult.status] || { text: consult.status, color: 'default' };
return ( return (
...@@ -151,18 +151,18 @@ const PatientConsultListPage: React.FC = () => { ...@@ -151,18 +151,18 @@ const PatientConsultListPage: React.FC = () => {
icon={consult.type === 'video' ? <VideoCameraOutlined /> : <MessageOutlined />} icon={consult.type === 'video' ? <VideoCameraOutlined /> : <MessageOutlined />}
color={consult.type === 'video' ? 'blue' : 'cyan'} color={consult.type === 'video' ? 'blue' : 'cyan'}
> >
{consult.type === 'video' ? '瑙嗛' : '鍥炬枃'} {consult.type === 'video' ? '视频' : '图文'}
</Tag> </Tag>
<Tag icon={statusIcon[consult.status]} color={status.color}>{status.text}</Tag> <Tag icon={statusIcon[consult.status]} color={status.color}>{status.text}</Tag>
</span> </span>
} }
description={ description={
<div className="mt-0.5"> <div className="mt-0.5">
<div className="text-xs text-gray-600 mb-0.5">涓昏瘔锛歿consult.chief_complaint || '鏃?}</div> <div className="text-xs text-gray-600 mb-0.5">主诉:{consult.chief_complaint || '无'}</div>
<span className="text-[11px] text-gray-400"> <span className="text-[11px] text-gray-400">
{dayjs(consult.created_at).format('YYYY-MM-DD HH:mm')} {dayjs(consult.created_at).format('YYYY-MM-DD HH:mm')}
{consult.started_at && ` 路 鎺ヨ瘖 ${dayjs(consult.started_at).format('HH:mm')}`} {consult.started_at && ` · 接诊 ${dayjs(consult.started_at).format('HH:mm')}`}
{consult.ended_at && ` 路 缁撴潫 ${dayjs(consult.ended_at).format('HH:mm')}`} {consult.ended_at && ` · 结束 ${dayjs(consult.ended_at).format('HH:mm')}`}
</span> </span>
</div> </div>
} }
......
...@@ -60,7 +60,7 @@ const DoctorDetailPage: React.FC = () => { ...@@ -60,7 +60,7 @@ const DoctorDetailPage: React.FC = () => {
<div className="text-right"> <div className="text-right">
<div className="mb-2"> <div className="mb-2">
<Text strong className="text-xl! text-red-500!">¥{(doctor.price / 100).toFixed(0)}</Text> <Text strong className="text-xl! text-red-500!">¥{(doctor.price / 100).toFixed(0)}</Text>
<Text type="secondary" className="text-xs!">/?/Text> <Text type="secondary" className="text-xs!">/次</Text>
</div> </div>
<Space> <Space>
<Button type="primary" size="small" icon={<VideoCameraOutlined />} <Button type="primary" size="small" icon={<VideoCameraOutlined />}
...@@ -81,18 +81,18 @@ const DoctorDetailPage: React.FC = () => { ...@@ -81,18 +81,18 @@ const DoctorDetailPage: React.FC = () => {
</Space> </Space>
</Card> </Card>
<Card title={<span className="text-xs font-semibold">医生简?/span>} size="small"> <Card title={<span className="text-xs font-semibold">医生简介</span>} size="small">
<Paragraph className="text-xs! mb-0!">{doctor.introduction || '暂无简?}</Paragraph> <Paragraph className="text-xs! mb-0!">{doctor.introduction || '暂无简介}</Paragraph>
</Card> </Card>
<Card title={<span className="text-xs font-semibold">排班信息</span>} <Card title={<span className="text-xs font-semibold">排班信息</span>}
extra={<Button type="link" size="small" icon={<CalendarOutlined />}>更多</Button>} size="small"> extra={<Button type="link" size="small" icon={<CalendarOutlined />}>更多</Button>} size="small">
<Descriptions column={2} size="small"> <Descriptions column={2} size="small">
<Descriptions.Item label="出诊状?> <Descriptions.Item label="出诊状>
{doctor.is_online ? <Tag color="green">在线</Tag> : <Tag color="default">未排?/Tag>} {doctor.is_online ? <Tag color="green">在线</Tag> : <Tag color="default">未排班</Tag>}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="资质认证"> <Descriptions.Item label="资质认证">
<Space size={4}><SafetyCertificateOutlined className="text-green-500" /><Text className="text-xs!">已认?/Text></Space> <Space size={4}><SafetyCertificateOutlined className="text-green-500" /><Text className="text-xs!">已认证</Text></Space>
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</Card> </Card>
......
...@@ -12,7 +12,7 @@ const { Text } = Typography; ...@@ -12,7 +12,7 @@ const { Text } = Typography;
const PatientDoctorsPage: React.FC = () => { const PatientDoctorsPage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const [searchParams] = useSearchParams(); const searchParams = useSearchParams();
const [keyword, setKeyword] = useState(searchParams.get('keyword') || ''); const [keyword, setKeyword] = useState(searchParams.get('keyword') || '');
const [departmentId, setDepartmentId] = useState(searchParams.get('department') || ''); const [departmentId, setDepartmentId] = useState(searchParams.get('department') || '');
const [sortBy, setSortBy] = useState<'rating' | 'consult_count' | 'price'>('rating'); const [sortBy, setSortBy] = useState<'rating' | 'consult_count' | 'price'>('rating');
...@@ -68,9 +68,9 @@ const PatientDoctorsPage: React.FC = () => { ...@@ -68,9 +68,9 @@ const PatientDoctorsPage: React.FC = () => {
style={{ width: 110 }} style={{ width: 110 }}
size="small" size="small"
options={[ options={[
{ label: '评分最?, value: 'rating' }, { label: '评分最高, value: 'rating' },
{ label: '问诊最?, value: 'consult_count' }, { label: '问诊最高, value: 'consult_count' },
{ label: '价格最?, value: 'price' }, { label: '价格最高, value: 'price' },
]} ]}
/> />
</div> </div>
...@@ -105,7 +105,7 @@ const PatientDoctorsPage: React.FC = () => { ...@@ -105,7 +105,7 @@ const PatientDoctorsPage: React.FC = () => {
<div className="text-right"> <div className="text-right">
<div className="mb-2"> <div className="mb-2">
<span className="text-base font-bold text-red-500">¥{(doctor.price / 100).toFixed(0)}</span> <span className="text-base font-bold text-red-500">¥{(doctor.price / 100).toFixed(0)}</span>
<span className="text-[11px] text-gray-400">/?/span> <span className="text-[11px] text-gray-400">/次</span>
</div> </div>
<Space size={4}> <Space size={4}>
<Button size="small" icon={<MessageOutlined />} onClick={() => handleConsult(doctor, 'text')}> <Button size="small" icon={<MessageOutlined />} onClick={() => handleConsult(doctor, 'text')}>
......
...@@ -27,7 +27,7 @@ const HealthProfileTab: React.FC = () => { ...@@ -27,7 +27,7 @@ const HealthProfileTab: React.FC = () => {
const mutation = useMutation({ const mutation = useMutation({
mutationFn: (values: UpdateProfileParams) => healthApi.updateProfile(values), mutationFn: (values: UpdateProfileParams) => healthApi.updateProfile(values),
onSuccess: () => { message.success('健康档案已更?); queryClient.invalidateQueries({ queryKey: ['health-profile'] }); }, onSuccess: () => { message.success('健康档案已更); queryClient.invalidateQueries({ queryKey: ['health-profile'] }); },
onError: () => message.error('更新失败,请重试'), onError: () => message.error('更新失败,请重试'),
}); });
...@@ -55,15 +55,15 @@ const HealthProfileTab: React.FC = () => { ...@@ -55,15 +55,15 @@ const HealthProfileTab: React.FC = () => {
</Col> </Col>
</Row> </Row>
<Form.Item label="既往病史" name="medical_history"> <Form.Item label="既往病史" name="medical_history">
<Input.TextArea rows={3} placeholder="如高血压、糖尿病? /> <Input.TextArea rows={3} placeholder="如高血压、糖尿病史 />
</Form.Item> </Form.Item>
<Form.Item label="过敏? name="allergy_history"> <Form.Item label="过敏感 name="allergy_history">
<Input.TextArea rows={2} placeholder="过敏药物或食? /> <Input.TextArea rows={2} placeholder="过敏药物或食物 />
</Form.Item> </Form.Item>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="紧急联系人电话" name="emergency_contact"> <Form.Item label="紧急联系人电话" name="emergency_contact">
<Input placeholder="紧急联系人手机? /> <Input placeholder="紧急联系人手机构 />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
...@@ -74,7 +74,7 @@ const HealthProfileTab: React.FC = () => { ...@@ -74,7 +74,7 @@ const HealthProfileTab: React.FC = () => {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Form.Item label="医保? name="insurance_no"> <Form.Item label="医保险 name="insurance_no">
<Input placeholder="医保卡号" /> <Input placeholder="医保卡号" />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
...@@ -100,12 +100,12 @@ const LabReportsTab: React.FC = () => { ...@@ -100,12 +100,12 @@ const LabReportsTab: React.FC = () => {
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: (v: CreateReportParams) => healthApi.createReport(v), mutationFn: (v: CreateReportParams) => healthApi.createReport(v),
onSuccess: () => { message.success('报告已添?); setModalOpen(false); form.resetFields(); queryClient.invalidateQueries({ queryKey: ['lab-reports'] }); }, onSuccess: () => { message.success('报告已添); setModalOpen(false); form.resetFields(); queryClient.invalidateQueries({ queryKey: ['lab-reports'] }); },
}); });
const deleteMutation = useMutation({ const deleteMutation = useMutation({
mutationFn: (id: string) => healthApi.deleteReport(id), mutationFn: (id: string) => healthApi.deleteReport(id),
onSuccess: () => { message.success('已删?); queryClient.invalidateQueries({ queryKey: ['lab-reports'] }); }, onSuccess: () => { message.success('已删); queryClient.invalidateQueries({ queryKey: ['lab-reports'] }); },
}); });
const interpretMutation = useMutation({ const interpretMutation = useMutation({
...@@ -118,7 +118,7 @@ const LabReportsTab: React.FC = () => { ...@@ -118,7 +118,7 @@ const LabReportsTab: React.FC = () => {
{ title: '报告名称', dataIndex: 'title', key: 'title' }, { title: '报告名称', dataIndex: 'title', key: 'title' },
{ title: '类别', dataIndex: 'category', key: 'category', render: (v: string) => v ? <Tag color="blue">{v}</Tag> : '-' }, { title: '类别', dataIndex: 'category', key: 'category', render: (v: string) => v ? <Tag color="blue">{v}</Tag> : '-' },
{ title: '报告日期', dataIndex: 'report_date', key: 'report_date', render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD') : '-' }, { title: '报告日期', dataIndex: 'report_date', key: 'report_date', render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD') : '-' },
{ title: 'AI解读', dataIndex: 'ai_interpret', key: 'ai_interpret', render: (v: string) => v ? <Text type="success" style={{ fontSize: 12 }}>已解?/Text> : <Text type="secondary" style={{ fontSize: 12 }}>未解?/Text> }, { title: 'AI解读', dataIndex: 'ai_interpret', key: 'ai_interpret', render: (v: string) => v ? <Text type="success" style={{ fontSize: 12 }}>已解</Text> : <Text type="secondary" style={{ fontSize: 12 }}>未解</Text> },
{ {
title: '操作', key: 'action', render: (_: any, r: LabReport) => ( title: '操作', key: 'action', render: (_: any, r: LabReport) => (
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
...@@ -127,7 +127,7 @@ const LabReportsTab: React.FC = () => { ...@@ -127,7 +127,7 @@ const LabReportsTab: React.FC = () => {
AI解读 AI解读
</Button> </Button>
{r.ai_interpret && <Button size="small" onClick={() => { setInterpretText(r.ai_interpret); setInterpretId(r.id); }}>查看解读</Button>} {r.ai_interpret && <Button size="small" onClick={() => { setInterpretText(r.ai_interpret); setInterpretId(r.id); }}>查看解读</Button>}
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除外 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button size="small" danger icon={<DeleteOutlined />} /> <Button size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
</div> </div>
...@@ -142,16 +142,16 @@ const LabReportsTab: React.FC = () => { ...@@ -142,16 +142,16 @@ const LabReportsTab: React.FC = () => {
</div> </div>
<Table columns={columns} dataSource={reports || []} loading={isLoading} rowKey="id" pagination={{ pageSize: 10 }} /> <Table columns={columns} dataSource={reports || []} loading={isLoading} rowKey="id" pagination={{ pageSize: 10 }} />
<Modal title="添加检验报? open={modalOpen} onCancel={() => setModalOpen(false)} onOk={() => form.submit()} confirmLoading={createMutation.isPending}> <Modal title="添加检验报告 open={modalOpen} onCancel={() => setModalOpen(false)} onOk={() => form.submit()} confirmLoading={createMutation.isPending}>
<Form form={form} layout="vertical" onFinish={(v) => createMutation.mutate({ ...v, report_date: v.report_date ? v.report_date.toISOString() : null })}> <Form form={form} layout="vertical" onFinish={(v) => createMutation.mutate({ ...v, report_date: v.report_date ? v.report_date.toISOString() : null })}>
<Form.Item label="报告名称" name="title" rules={[{ required: true }]}> <Form.Item label="报告名称" name="title" rules={[{ required: true }]}>
<Input placeholder="如:血常规检验报? /> <Input placeholder="如:血常规检验报告 />
</Form.Item> </Form.Item>
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item label="类别" name="category"> <Form.Item label="类别" name="category">
<Select placeholder="请选择" allowClear> <Select placeholder="请选择" allowClear>
{['血常规', '尿常?, '生化', '影像', '其他'].map(v => <Select.Option key={v} value={v}>{v}</Select.Option>)} {['血常规', '尿常规, '生化', '影像', '其他'].map(v => <Select.Option key={v} value={v}>{v}</Select.Option>)}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
...@@ -176,11 +176,11 @@ const LabReportsTab: React.FC = () => { ...@@ -176,11 +176,11 @@ const LabReportsTab: React.FC = () => {
// ==================== 就诊历史 ==================== // ==================== 就诊历史 ====================
const statusMap: Record<string, { color: string; text: string }> = { const statusMap: Record<string, { color: string; text: string }> = {
pending: { color: 'orange', text: '待接? }, pending: { color: 'orange', text: '待接诊 },
waiting: { color: 'blue', text: '等待? }, waiting: { color: 'blue', text: '等待处 },
in_progress: { color: 'processing', text: '进行? }, in_progress: { color: 'processing', text: '进行中 },
completed: { color: 'success', text: '已完? }, completed: { color: 'success', text: '已完成 },
cancelled: { color: 'default', text: '已取? }, cancelled: { color: 'default', text: '已取消 },
}; };
const ConsultHistoryTab: React.FC = () => { const ConsultHistoryTab: React.FC = () => {
...@@ -196,7 +196,7 @@ const ConsultHistoryTab: React.FC = () => { ...@@ -196,7 +196,7 @@ const ConsultHistoryTab: React.FC = () => {
{ title: '科室', dataIndex: 'department_name', key: 'department_name', render: (v: string) => v || '-' }, { title: '科室', dataIndex: 'department_name', key: 'department_name', render: (v: string) => v || '-' },
{ title: '主诉', dataIndex: 'chief_complaint', key: 'chief_complaint', ellipsis: true }, { title: '主诉', dataIndex: 'chief_complaint', key: 'chief_complaint', ellipsis: true },
{ title: '类型', dataIndex: 'type', key: 'type', render: (v: string) => v === 'video' ? '视频问诊' : '图文问诊' }, { title: '类型', dataIndex: 'type', key: 'type', render: (v: string) => v === 'video' ? '视频问诊' : '图文问诊' },
{ title: '状?, dataIndex: 'status', key: 'status', render: (v: string) => { const s = statusMap[v] || { color: 'default', text: v }; return <Tag color={s.color}>{s.text}</Tag>; } }, { title: '状态, dataIndex: 'status', key: 'status', render: (v: string) => { const s = statusMap[v] || { color: 'default', text: v }; return <Tag color={s.color}>{s.text}</Tag>; } },
]; ];
return <Table<ConsultHistoryItem> columns={columns} dataSource={data || []} loading={isLoading} rowKey="id" pagination={{ pageSize: 10 }} />; return <Table<ConsultHistoryItem> columns={columns} dataSource={data || []} loading={isLoading} rowKey="id" pagination={{ pageSize: 10 }} />;
...@@ -219,7 +219,7 @@ const HealthTrendTab: React.FC = () => { ...@@ -219,7 +219,7 @@ const HealthTrendTab: React.FC = () => {
<> <>
<Row gutter={16} style={{ marginBottom: 24 }}> <Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={8}><Card><Statistic title="?个月问诊次数" value={total.consult} prefix={<MedicineBoxOutlined />} valueStyle={{ color: '#1677ff' }} /></Card></Col> <Col span={8}><Card><Statistic title="?个月问诊次数" value={total.consult} prefix={<MedicineBoxOutlined />} valueStyle={{ color: '#1677ff' }} /></Card></Col>
<Col span={8}><Card><Statistic title="?个月检验报? value={total.report} prefix={<FileTextOutlined />} valueStyle={{ color: '#52c41a' }} /></Card></Col> <Col span={8}><Card><Statistic title="?个月检验报告 value={total.report} prefix={<FileTextOutlined />} valueStyle={{ color: '#52c41a' }} /></Card></Col>
<Col span={8}><Card><Statistic title="健康记录月份" value={data.filter(d => d.consult_count + d.report_count > 0).length} suffix="/ 6" valueStyle={{ color: '#722ed1' }} /></Card></Col> <Col span={8}><Card><Statistic title="健康记录月份" value={data.filter(d => d.consult_count + d.report_count > 0).length} suffix="/ 6" valueStyle={{ color: '#722ed1' }} /></Card></Col>
</Row> </Row>
<Table <Table
...@@ -229,8 +229,8 @@ const HealthTrendTab: React.FC = () => { ...@@ -229,8 +229,8 @@ const HealthTrendTab: React.FC = () => {
columns={[ columns={[
{ title: '月份', dataIndex: 'month', key: 'month' }, { title: '月份', dataIndex: 'month', key: 'month' },
{ title: '问诊次数', dataIndex: 'consult_count', key: 'consult_count', render: (v: number) => <Tag color={v > 0 ? 'blue' : 'default'}>{v} ?/Tag> }, { title: '问诊次数', dataIndex: 'consult_count', key: 'consult_count', render: (v: number) => <Tag color={v > 0 ? 'blue' : 'default'}>{v} ?/Tag> },
{ title: '检验报?, dataIndex: 'report_count', key: 'report_count', render: (v: number) => <Tag color={v > 0 ? 'green' : 'default'}>{v} ?/Tag> }, { title: '检验报告, dataIndex: 'report_count', key: 'report_count', render: (v: number) => <Tag color={v > 0 ? 'green' : 'default'}>{v} ?/Tag> },
{ title: '活跃?, key: 'active', render: (_: any, r: any) => { const total = r.consult_count + r.report_count; return total > 0 ? <Tag color="success">有记?/Tag> : <Tag>无记?/Tag>; } }, { title: '活跃量, key: 'active', render: (_: any, r: any) => { const total = r.consult_count + r.report_count; return total > 0 ? <Tag color="success">有记</Tag> : <Tag>无记</Tag>; } },
]} ]}
/> />
</> </>
...@@ -252,12 +252,12 @@ const FamilyTab: React.FC = () => { ...@@ -252,12 +252,12 @@ const FamilyTab: React.FC = () => {
const saveMutation = useMutation({ const saveMutation = useMutation({
mutationFn: (v: FamilyMemberParams) => editing ? healthApi.updateFamily(editing.id, v) : healthApi.createFamily(v), mutationFn: (v: FamilyMemberParams) => editing ? healthApi.updateFamily(editing.id, v) : healthApi.createFamily(v),
onSuccess: () => { message.success(editing ? '已更? : '已添?); setModalOpen(false); setEditing(null); form.resetFields(); queryClient.invalidateQueries({ queryKey: ['family-members'] }); }, onSuccess: () => { message.success(editing ? '已更新 : '已添); setModalOpen(false); setEditing(null); form.resetFields(); queryClient.invalidateQueries({ queryKey: ['family-members'] }); },
}); });
const deleteMutation = useMutation({ const deleteMutation = useMutation({
mutationFn: (id: string) => healthApi.deleteFamily(id), mutationFn: (id: string) => healthApi.deleteFamily(id),
onSuccess: () => { message.success('已删?); queryClient.invalidateQueries({ queryKey: ['family-members'] }); }, onSuccess: () => { message.success('已删); queryClient.invalidateQueries({ queryKey: ['family-members'] }); },
}); });
const openEdit = (m: FamilyMember) => { const openEdit = (m: FamilyMember) => {
...@@ -278,7 +278,7 @@ const FamilyTab: React.FC = () => { ...@@ -278,7 +278,7 @@ const FamilyTab: React.FC = () => {
title: '操作', key: 'action', render: (_: any, r: FamilyMember) => ( title: '操作', key: 'action', render: (_: any, r: FamilyMember) => (
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<Button size="small" icon={<EditOutlined />} onClick={() => openEdit(r)}>编辑</Button> <Button size="small" icon={<EditOutlined />} onClick={() => openEdit(r)}>编辑</Button>
<Popconfirm title="确认删除? onConfirm={() => deleteMutation.mutate(r.id)}> <Popconfirm title="确认删除外 onConfirm={() => deleteMutation.mutate(r.id)}>
<Button size="small" danger icon={<DeleteOutlined />} /> <Button size="small" danger icon={<DeleteOutlined />} />
</Popconfirm> </Popconfirm>
</div> </div>
...@@ -330,7 +330,7 @@ const FamilyTab: React.FC = () => { ...@@ -330,7 +330,7 @@ const FamilyTab: React.FC = () => {
<Form.Item label="既往病史" name="medical_history"> <Form.Item label="既往病史" name="medical_history">
<Input.TextArea rows={2} /> <Input.TextArea rows={2} />
</Form.Item> </Form.Item>
<Form.Item label="过敏? name="allergy_history"> <Form.Item label="过敏感 name="allergy_history">
<Input.TextArea rows={2} /> <Input.TextArea rows={2} />
</Form.Item> </Form.Item>
</Form> </Form>
...@@ -346,7 +346,7 @@ const PatientHealthRecordsPage: React.FC = () => ( ...@@ -346,7 +346,7 @@ const PatientHealthRecordsPage: React.FC = () => (
<Card size="small"> <Card size="small">
<Tabs defaultActiveKey="profile" size="small" items={[ <Tabs defaultActiveKey="profile" size="small" items={[
{ key: 'profile', label: <span><MedicineBoxOutlined /> 健康信息</span>, children: <HealthProfileTab /> }, { key: 'profile', label: <span><MedicineBoxOutlined /> 健康信息</span>, children: <HealthProfileTab /> },
{ key: 'reports', label: <span><FileTextOutlined /> 检验报?/span>, children: <LabReportsTab /> }, { key: 'reports', label: <span><FileTextOutlined /> 检验报</span>, children: <LabReportsTab /> },
{ key: 'history', label: <span><MedicineBoxOutlined /> 就诊历史</span>, children: <ConsultHistoryTab /> }, { key: 'history', label: <span><MedicineBoxOutlined /> 就诊历史</span>, children: <ConsultHistoryTab /> },
{ key: 'trend', label: <span><BarChartOutlined /> 健康趋势</span>, children: <HealthTrendTab /> }, { key: 'trend', label: <span><BarChartOutlined /> 健康趋势</span>, children: <HealthTrendTab /> },
{ key: 'family', label: <span><TeamOutlined /> 家庭成员</span>, children: <FamilyTab /> }, { key: 'family', label: <span><TeamOutlined /> 家庭成员</span>, children: <FamilyTab /> },
......
...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation'; ...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation';
const { Text } = Typography; const { Text } = Typography;
const features = [ const features = [
{ icon: <WechatOutlined style={{ fontSize: 28, color: '#07c160' }} />, title: '寰俊/鏀粯瀹濇敮浠?, desc: '渚挎嵎鐨勫湪绾挎敮浠樻柟寮? }, { icon: <WechatOutlined style={{ fontSize: 28, color: '#07c160' }} />, title: '微信/支付宝支付', desc: '便捷的在线支付方式' },
{ icon: <PayCircleOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '鍖讳繚鎵h垂', desc: '鏀寔鍖讳繚鍦ㄧ嚎缁撶畻' }, { icon: <PayCircleOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '医保扣费', desc: '支持医保在线结算' },
{ icon: <FileTextOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '璐﹀崟鏄庣粏', desc: '璇︾粏鐨勮垂鐢ㄦ竻鍗? }, { icon: <FileTextOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '账单明细', desc: '详细的费用清单' },
{ icon: <HistoryOutlined style={{ fontSize: 28, color: '#722ed1' }} />, title: '鏀粯璁板綍', desc: '鍘嗗彶鏀粯璁板綍鏌ヨ' }, { icon: <HistoryOutlined style={{ fontSize: 28, color: '#722ed1' }} />, title: '支付记录', desc: '历史支付记录查询' },
]; ];
const PatientPaymentPage: React.FC = () => { const PatientPaymentPage: React.FC = () => {
...@@ -19,13 +19,13 @@ const PatientPaymentPage: React.FC = () => { ...@@ -19,13 +19,13 @@ const PatientPaymentPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">鍦ㄧ嚎鏀粯</h4> <h4 className="text-sm font-bold text-gray-800 m-0">在线支付</h4>
<Button type="primary" size="small" onClick={() => router.push('/patient/consult')}>鏌ョ湅闂瘖</Button> <Button type="primary" size="small" onClick={() => router.push('/patient/consult')}>查看问诊</Button>
</div> </div>
<Card size="small"> <Card size="small">
<div className="text-center py-4"> <div className="text-center py-4">
<PayCircleOutlined className="text-4xl text-yellow-400 mb-2" /> <PayCircleOutlined className="text-4xl text-yellow-400 mb-2" />
<div className="text-xs text-gray-400">鍔熻兘寮€鍙戜腑锛屾暚璇锋湡寰?/div> <div className="text-xs text-gray-400">支付方式说明</div>
</div> </div>
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{features.map((f) => ( {features.map((f) => (
...@@ -48,4 +48,4 @@ const PatientPaymentPage: React.FC = () => { ...@@ -48,4 +48,4 @@ const PatientPaymentPage: React.FC = () => {
}; };
export default PatientPaymentPage; export default PatientPaymentPage;
...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation'; ...@@ -8,10 +8,10 @@ import { useRouter } from 'next/navigation';
const { Text } = Typography; const { Text } = Typography;
const features = [ const features = [
{ icon: <EnvironmentOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '閰嶉€佽繘搴﹁窡韪?, desc: '瀹炴椂鏌ョ湅鑽搧閰嶉€佺姸鎬? }, { icon: <EnvironmentOutlined style={{ fontSize: 28, color: '#1677ff' }} />, title: '配送进度跟踪', desc: '实时查看药品配送状态' },
{ icon: <ShoppingCartOutlined style={{ fontSize: 28, color: '#eb2f96' }} />, title: '鐗╂祦鍦板浘', desc: '鍙鍖栭厤閫佽矾绾? }, { icon: <ShoppingCartOutlined style={{ fontSize: 28, color: '#eb2f96' }} />, title: '物流地图', desc: '可视化配送路线' },
{ icon: <CheckCircleOutlined style={{ fontSize: 28, color: '#52c41a' }} />, title: '绛炬敹纭', desc: '鍦ㄧ嚎纭鏀惰揣' }, { icon: <CheckCircleOutlined style={{ fontSize: 28, color: '#52c41a' }} />, title: '签收确认', desc: '在线确认收货' },
{ icon: <HistoryOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '璁㈠崟鍘嗗彶', desc: '鏌ョ湅鍘嗗彶鑽搧璁㈠崟' }, { icon: <HistoryOutlined style={{ fontSize: 28, color: '#fa8c16' }} />, title: '订单历史', desc: '查看历史药品订单' },
]; ];
const PatientPharmacyOrderPage: React.FC = () => { const PatientPharmacyOrderPage: React.FC = () => {
...@@ -19,13 +19,13 @@ const PatientPharmacyOrderPage: React.FC = () => { ...@@ -19,13 +19,13 @@ const PatientPharmacyOrderPage: React.FC = () => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-gray-800 m-0">鑽搧閰嶉€?/h4> <h4 className="text-sm font-bold text-gray-800 m-0">药品配送</h4>
<Button type="primary" size="small" onClick={() => router.push('/patient/prescription')}>鏌ョ湅澶勬柟</Button> <Button type="primary" size="small" onClick={() => router.push('/patient/prescription')}>查看处方</Button>
</div> </div>
<Card size="small"> <Card size="small">
<div className="text-center py-4"> <div className="text-center py-4">
<ShoppingCartOutlined className="text-4xl text-pink-500 mb-2" /> <ShoppingCartOutlined className="text-4xl text-pink-500 mb-2" />
<div className="text-xs text-gray-400">鍔熻兘寮€鍙戜腑锛屾暚璇锋湡寰?/div> <div className="text-xs text-gray-400">功能开发中,敬请期待</div>
</div> </div>
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{features.map((f) => ( {features.map((f) => (
...@@ -48,4 +48,4 @@ const PatientPharmacyOrderPage: React.FC = () => { ...@@ -48,4 +48,4 @@ const PatientPharmacyOrderPage: React.FC = () => {
}; };
export default PatientPharmacyOrderPage; export default PatientPharmacyOrderPage;
...@@ -40,7 +40,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -40,7 +40,8 @@ const PatientPreConsultPage: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { user } = useUserStore(); const { user } = useUserStore();
// 对话状? const [messages, setMessages] = useState<DisplayMessage[]>([]); //
const [messages, setMessages] = useState<DisplayMessage[]>([]);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [sessionId, setSessionId] = useState<string | null>(null); const [sessionId, setSessionId] = useState<string | null>(null);
...@@ -48,21 +49,24 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -48,21 +49,24 @@ const PatientPreConsultPage: React.FC = () => {
const [turnCount, setTurnCount] = useState(0); const [turnCount, setTurnCount] = useState(0);
const [initialLoading, setInitialLoading] = useState(true); const [initialLoading, setInitialLoading] = useState(true);
// 分析报告状? const [reportContent, setReportContent] = useState(''); //
const [reportContent, setReportContent] = useState('');
const [generating, setGenerating] = useState(false); const [generating, setGenerating] = useState(false);
const [reportDone, setReportDone] = useState(false); const [reportDone, setReportDone] = useState(false);
const [doneInfo, setDoneInfo] = useState<ChatDoneInfo | null>(null); const [doneInfo, setDoneInfo] = useState<ChatDoneInfo | null>(null);
// 创建问诊弹窗状? const [consultModalOpen, setConsultModalOpen] = useState(false); //
const [consultModalOpen, setConsultModalOpen] = useState(false);
const [selectedDoctor, setSelectedDoctor] = useState<DoctorBrief | null>(null); const [selectedDoctor, setSelectedDoctor] = useState<DoctorBrief | null>(null);
// 历史对话抽屉状? const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false); //
const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false);
const [historyList, setHistoryList] = useState<PreConsultResponse[]>([]); const [historyList, setHistoryList] = useState<PreConsultResponse[]>([]);
const [historyLoading, setHistoryLoading] = useState(false); const [historyLoading, setHistoryLoading] = useState(false);
// 患者信息(从用户信息自动获取) // 患者信息(从用户信息自动获取)
const patientInfo = { const patientInfo = {
name: user?.real_name || '患?, name: user?.real_name || '患者,
gender: user?.gender || '未知', gender: user?.gender || '未知',
age: user?.age || 0, age: user?.age || 0,
}; };
...@@ -72,7 +76,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -72,7 +76,8 @@ const PatientPreConsultPage: React.FC = () => {
const abortRef = useRef<AbortController | null>(null); const abortRef = useRef<AbortController | null>(null);
const inputRef = useRef<any>(null); const inputRef = useRef<any>(null);
// 自动滚动到底? const scrollToBottom = useCallback(() => { //
const scrollToBottom = useCallback(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, []); }, []);
...@@ -114,7 +119,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -114,7 +119,8 @@ const PatientPreConsultPage: React.FC = () => {
setMessages(chatMessagesToDisplay(chatMsgs)); setMessages(chatMessagesToDisplay(chatMsgs));
setTurnCount(Math.floor(chatMsgs.length / 2)); setTurnCount(Math.floor(chatMsgs.length / 2));
} else { } else {
// 没有对话历史,清空消? setMessages([]); //
setMessages([]);
setTurnCount(0); setTurnCount(0);
} }
...@@ -145,13 +151,15 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -145,13 +151,15 @@ const PatientPreConsultPage: React.FC = () => {
useEffect(() => { useEffect(() => {
const initSession = async () => { const initSession = async () => {
try { try {
// 先尝试获取进行中的会? const latestRes = await preConsultApi.getLatest(); //
const latestRes = await preConsultApi.getLatest();
if (latestRes.data) { if (latestRes.data) {
restoreSession(latestRes.data); restoreSession(latestRes.data);
return; return;
} }
// 没有进行中的,获取历史列表,恢复最新的已完成会? const listRes = await preConsultApi.getList(); //
const listRes = await preConsultApi.getList();
if (listRes.data && listRes.data.length > 0) { if (listRes.data && listRes.data.length > 0) {
const latestCompleted = listRes.data.find(s => s.status === 'completed'); const latestCompleted = listRes.data.find(s => s.status === 'completed');
if (latestCompleted) { if (latestCompleted) {
...@@ -178,7 +186,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -178,7 +186,8 @@ const PatientPreConsultPage: React.FC = () => {
initSession().finally(() => setInitialLoading(false)); initSession().finally(() => setInitialLoading(false));
}, []); }, []);
// 发送用户消? const handleSend = () => { //
const handleSend = () => {
const text = inputValue.trim(); const text = inputValue.trim();
if (!text || sending || generating) return; if (!text || sending || generating) return;
...@@ -253,7 +262,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -253,7 +262,8 @@ const PatientPreConsultPage: React.FC = () => {
}); });
}; };
// 结束对话,生成分析报? const handleFinishChat = () => { //
const handleFinishChat = () => {
if (!sessionId || generating) return; if (!sessionId || generating) return;
setGenerating(true); setGenerating(true);
...@@ -292,7 +302,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -292,7 +302,8 @@ const PatientPreConsultPage: React.FC = () => {
setConsultModalOpen(true); setConsultModalOpen(true);
}; };
// 键盘快捷? const handleKeyDown = (e: React.KeyboardEvent) => { //
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleSend(); handleSend();
...@@ -387,7 +398,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -387,7 +398,7 @@ const PatientPreConsultPage: React.FC = () => {
title={ title={
<Space> <Space>
<FileTextOutlined style={{ color: '#722ed1' }} /> <FileTextOutlined style={{ color: '#722ed1' }} />
<span>AI 预问诊分析报?/span> <span>AI 预问诊分析报</span>
{generating && <LoadingOutlined style={{ color: '#722ed1' }} />} {generating && <LoadingOutlined style={{ color: '#722ed1' }} />}
{reportDone && <Tag color="success">生成完成</Tag>} {reportDone && <Tag color="success">生成完成</Tag>}
</Space> </Space>
...@@ -452,7 +463,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -452,7 +463,7 @@ const PatientPreConsultPage: React.FC = () => {
<Space split={<Divider type="vertical" />}> <Space split={<Divider type="vertical" />}>
<span>评分 {doctor.rating}</span> <span>评分 {doctor.rating}</span>
<span>问诊 {doctor.consult_count} ?/span> <span>问诊 {doctor.consult_count} ?/span>
<span style={{ color: '#fa8c16' }}>¥{(doctor.price / 100).toFixed(0)}/?/span> <span style={{ color: '#fa8c16' }}>¥{(doctor.price / 100).toFixed(0)}/次</span>
</Space> </Space>
} }
/> />
...@@ -480,9 +491,9 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -480,9 +491,9 @@ const PatientPreConsultPage: React.FC = () => {
<Space> <Space>
<RobotOutlined style={{ fontSize: 24 }} /> <RobotOutlined style={{ fontSize: 24 }} />
<div> <div>
<div style={{ fontSize: 16, fontWeight: 600 }}>AI 智能预问?/div> <div style={{ fontSize: 16, fontWeight: 600 }}>AI 智能预问诊</div>
<div style={{ fontSize: 12, opacity: 0.85 }}> <div style={{ fontSize: 12, opacity: 0.85 }}>
{sessionStatus === 'completed' ? '问诊已完? : {sessionStatus === 'completed' ? '问诊已完成 :
sessionStatus === 'in_progress' ? `对话?· ?{turnCount}轮` : sessionStatus === 'in_progress' ? `对话?· ?{turnCount}轮` :
'请先填写基本信息'} '请先填写基本信息'}
</div> </div>
...@@ -560,7 +571,8 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -560,7 +571,8 @@ const PatientPreConsultPage: React.FC = () => {
<Button <Button
size="large" size="large"
onClick={() => { onClick={() => {
// 重置所有状? setSessionId(null); //
setSessionId(null);
setSessionStatus('new'); setSessionStatus('new');
setTurnCount(0); setTurnCount(0);
setReportContent(''); setReportContent('');
...@@ -578,7 +590,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -578,7 +590,7 @@ const PatientPreConsultPage: React.FC = () => {
}} }}
style={{ borderColor: '#722ed1', color: '#722ed1' }} style={{ borderColor: '#722ed1', color: '#722ed1' }}
> >
重新预问? </Button> 重新预问诊 </Button>
</Space> </Space>
</div> </div>
)} )}
...@@ -598,7 +610,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -598,7 +610,7 @@ const PatientPreConsultPage: React.FC = () => {
> >
{turnCount >= 2 && !generating && !reportDone && ( {turnCount >= 2 && !generating && !reportDone && (
<Alert <Alert
message="对话信息已足够,您可以点击右上角「结束对?· 生成报告」获取分析结果,或继续补充信息? message="对话信息已足够,您可以点击右上角「结束对?· 生成报告」获取分析结果,或继续补充信息。
type="info" type="info"
showIcon showIcon
style={{ marginBottom: 12, borderRadius: 8 }} style={{ marginBottom: 12, borderRadius: 8 }}
...@@ -611,7 +623,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -611,7 +623,7 @@ const PatientPreConsultPage: React.FC = () => {
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={sending ? 'AI 正在回复?..' : '请描述您的症状或回答AI的问?.. (Enter发送,Shift+Enter换行)'} placeholder={sending ? 'AI 正在回复..' : '请描述您的症状或回答AI的问.. (Enter发送,Shift+Enter换行)'}
autoSize={{ minRows: 1, maxRows: 4 }} autoSize={{ minRows: 1, maxRows: 4 }}
disabled={sending || generating} disabled={sending || generating}
style={{ style={{
...@@ -666,7 +678,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -666,7 +678,7 @@ const PatientPreConsultPage: React.FC = () => {
> >
{historyLoading ? ( {historyLoading ? (
<div style={{ textAlign: 'center', padding: 60 }}> <div style={{ textAlign: 'center', padding: 60 }}>
<Spin tip="加载?.." /> <Spin tip="加载中.." />
</div> </div>
) : historyList.length === 0 ? ( ) : historyList.length === 0 ? (
<Empty description="暂无历史对话" /> <Empty description="暂无历史对话" />
...@@ -688,7 +700,7 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -688,7 +700,7 @@ const PatientPreConsultPage: React.FC = () => {
title={ title={
<Space> <Space>
<Text strong ellipsis style={{ maxWidth: 200 }}> <Text strong ellipsis style={{ maxWidth: 200 }}>
{item.chief_complaint || '预问诊对?} {item.chief_complaint || '预问诊对}
</Text> </Text>
{item.id === sessionId && <Tag color="blue">当前</Tag>} {item.id === sessionId && <Tag color="blue">当前</Tag>}
</Space> </Space>
...@@ -698,15 +710,15 @@ const PatientPreConsultPage: React.FC = () => { ...@@ -698,15 +710,15 @@ const PatientPreConsultPage: React.FC = () => {
<div style={{ fontSize: 12, color: '#666', marginBottom: 4 }}> <div style={{ fontSize: 12, color: '#666', marginBottom: 4 }}>
{item.status === 'completed' ? ( {item.status === 'completed' ? (
<> <>
<Tag color="green" style={{ marginRight: 4 }}>已完?/Tag> <Tag color="green" style={{ marginRight: 4 }}>已完成</Tag>
{item.ai_department && <Tag>{item.ai_department}</Tag>} {item.ai_department && <Tag>{item.ai_department}</Tag>}
</> </>
) : ( ) : (
<Tag color="orange">进行?/Tag> <Tag color="orange">进行中</Tag>
)} )}
</div> </div>
<div style={{ fontSize: 12, color: '#999' }}> <div style={{ fontSize: 12, color: '#999' }}>
{item.chat_messages ? `${Math.floor(item.chat_messages.length / 2)}轮对话` : '无对?} · {new Date(item.created_at).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} {item.chat_messages ? `${Math.floor(item.chat_messages.length / 2)}轮对话` : '无对} · {new Date(item.created_at).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
</div> </div>
</div> </div>
} }
......
...@@ -51,12 +51,12 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -51,12 +51,12 @@ const PatientPrescriptionPage: React.FC = () => {
const getStatusTag = (status: string) => { const getStatusTag = (status: string) => {
const map: Record<string, { color: string; label: string }> = { const map: Record<string, { color: string; label: string }> = {
pending: { color: 'orange', label: '待签? }, pending: { color: 'orange', label: '待签名 },
signed: { color: 'blue', label: '已签? }, signed: { color: 'blue', label: '已签名 },
approved: { color: 'green', label: '已审? }, approved: { color: 'green', label: '已审核 },
rejected: { color: 'red', label: '已驳? }, rejected: { color: 'red', label: '已驳 },
dispensed: { color: 'cyan', label: '已发? }, dispensed: { color: 'cyan', label: '已发送 },
completed: { color: 'default', label: '已完? }, completed: { color: 'default', label: '已完成 },
}; };
const s = map[status] || { color: 'default', label: status }; const s = map[status] || { color: 'default', label: status };
return <Tag color={s.color}>{s.label}</Tag>; return <Tag color={s.color}>{s.label}</Tag>;
...@@ -79,9 +79,9 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -79,9 +79,9 @@ const PatientPrescriptionPage: React.FC = () => {
title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100, title: '金额', dataIndex: 'total_amount', key: 'total_amount', width: 100,
render: (v: number) => <Text strong style={{ color: '#f5222d' }}>¥{(v / 100).toFixed(2)}</Text>, render: (v: number) => <Text strong style={{ color: '#f5222d' }}>¥{(v / 100).toFixed(2)}</Text>,
}, },
{ title: '状?, dataIndex: 'status', key: 'status', width: 90, render: (v: string) => getStatusTag(v) }, { title: '状态, dataIndex: 'status', key: 'status', width: 90, render: (v: string) => getStatusTag(v) },
{ {
title: '开方时?, dataIndex: 'created_at', key: 'created_at', width: 170, title: '开方时, dataIndex: 'created_at', key: 'created_at', width: 170,
render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-', render: (v: string) => v ? new Date(v).toLocaleString('zh-CN') : '-',
}, },
{ {
...@@ -98,9 +98,9 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -98,9 +98,9 @@ const PatientPrescriptionPage: React.FC = () => {
<Card size="small" className="text-center py-6"> <Card size="small" className="text-center py-6">
<FileTextOutlined className="text-4xl text-[#1890ff] mb-2" /> <FileTextOutlined className="text-4xl text-[#1890ff] mb-2" />
<div className="text-sm font-bold mb-1">电子处方</div> <div className="text-sm font-bold mb-1">电子处方</div>
<Text type="secondary" className="text-xs!">暂无处方记录,完成问诊后医生会为您开具处?/Text> <Text type="secondary" className="text-xs!">暂无处方记录,完成问诊后医生会为您开具处</Text>
<div className="mt-4"> <div className="mt-4">
<Button type="primary" size="small" onClick={() => router.push('/patient/doctors')}>去问?/Button> <Button type="primary" size="small" onClick={() => router.push('/patient/doctors')}>去问</Button>
</div> </div>
</Card> </Card>
</div> </div>
...@@ -123,26 +123,26 @@ const PatientPrescriptionPage: React.FC = () => { ...@@ -123,26 +123,26 @@ const PatientPrescriptionPage: React.FC = () => {
footer={ footer={
currentRx && ['approved', 'signed'].includes(currentRx.status) ? [ currentRx && ['approved', 'signed'].includes(currentRx.status) ? [
<Button key="buy" type="primary" icon={<ShoppingCartOutlined />} <Button key="buy" type="primary" icon={<ShoppingCartOutlined />}
onClick={() => { message.info('购药功能即将上线'); }}>一键购?/Button>, onClick={() => { message.info('购药功能即将上线'); }}>一键购</Button>,
] : null ] : null
}> }>
{currentRx && ( {currentRx && (
<div> <div>
<div style={{ textAlign: 'center', marginBottom: 16 }}> <div style={{ textAlign: 'center', marginBottom: 16 }}>
<SafetyCertificateOutlined style={{ fontSize: 32, color: '#52c41a' }} /> <SafetyCertificateOutlined style={{ fontSize: 32, color: '#52c41a' }} />
<Title level={5} style={{ margin: '8px 0 0' }}>电子处方?/Title> <Title level={5} style={{ margin: '8px 0 0' }}>电子处方</Title>
<Text type="secondary">{currentRx.prescription_no}</Text> <Text type="secondary">{currentRx.prescription_no}</Text>
</div> </div>
<Descriptions bordered size="small" column={2}> <Descriptions bordered size="small" column={2}>
<Descriptions.Item label="患者姓?>{currentRx.patient_name}</Descriptions.Item> <Descriptions.Item label="患者姓>{currentRx.patient_name}</Descriptions.Item>
<Descriptions.Item label="性别/年龄"> <Descriptions.Item label="性别/年龄">
{currentRx.patient_gender || '-'} / {currentRx.patient_age ? `${currentRx.patient_age}岁` : '-'} {currentRx.patient_gender || '-'} / {currentRx.patient_age ? `${currentRx.patient_age}岁` : '-'}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="开方医?>{currentRx.doctor_name}</Descriptions.Item> <Descriptions.Item label="开方医>{currentRx.doctor_name}</Descriptions.Item>
<Descriptions.Item label="状?>{getStatusTag(currentRx.status)}</Descriptions.Item> <Descriptions.Item label="状>{getStatusTag(currentRx.status)}</Descriptions.Item>
<Descriptions.Item label="临床诊断" span={2}>{currentRx.diagnosis}</Descriptions.Item> <Descriptions.Item label="临床诊断" span={2}>{currentRx.diagnosis}</Descriptions.Item>
{currentRx.allergy_history && ( {currentRx.allergy_history && (
<Descriptions.Item label="过敏? span={2}> <Descriptions.Item label="过敏感 span={2}>
<Text type="danger">{currentRx.allergy_history}</Text> <Text type="danger">{currentRx.allergy_history}</Text>
</Descriptions.Item> </Descriptions.Item>
)} )}
......
...@@ -27,15 +27,15 @@ const PatientProfilePage: React.FC = () => { ...@@ -27,15 +27,15 @@ const PatientProfilePage: React.FC = () => {
<Avatar size={56} src={user?.avatar} icon={<UserOutlined />} /> <Avatar size={56} src={user?.avatar} icon={<UserOutlined />} />
</Col> </Col>
<Col flex="auto"> <Col flex="auto">
<h4 className="text-sm font-bold m-0 mb-0.5">{user?.real_name || '未设置姓?}</h4> <h4 className="text-sm font-bold m-0 mb-0.5">{user?.real_name || '未设置姓}</h4>
<span className="text-xs text-gray-400"><PhoneOutlined className="mr-1" />{user?.phone || '未绑定手?}</span> <span className="text-xs text-gray-400"><PhoneOutlined className="mr-1" />{user?.phone || '未绑定手}</span>
<div className="mt-1"> <div className="mt-1">
{user?.is_verified ? ( {user?.is_verified ? (
<Tag icon={<SafetyCertificateOutlined />} color="success">已实?/Tag> <Tag icon={<SafetyCertificateOutlined />} color="success">已实</Tag>
) : ( ) : (
<Tag color="warning">未认?/Tag> <Tag color="warning">未认证</Tag>
)} )}
<Tag color="blue">患?/Tag> <Tag color="blue">患</Tag>
</div> </div>
</Col> </Col>
<Col> <Col>
...@@ -48,7 +48,7 @@ const PatientProfilePage: React.FC = () => { ...@@ -48,7 +48,7 @@ const PatientProfilePage: React.FC = () => {
<Card title={<span className="text-xs font-semibold">基本信息</span>} size="small"> <Card title={<span className="text-xs font-semibold">基本信息</span>} size="small">
<Descriptions column={2} size="small"> <Descriptions column={2} size="small">
<Descriptions.Item label="用户ID">{user?.id || '-'}</Descriptions.Item> <Descriptions.Item label="用户ID">{user?.id || '-'}</Descriptions.Item>
<Descriptions.Item label="手机?>{user?.phone || '-'}</Descriptions.Item> <Descriptions.Item label="手机>{user?.phone || '-'}</Descriptions.Item>
<Descriptions.Item label="真实姓名">{user?.real_name || '-'}</Descriptions.Item> <Descriptions.Item label="真实姓名">{user?.real_name || '-'}</Descriptions.Item>
<Descriptions.Item label="注册时间">{user?.created_at || '-'}</Descriptions.Item> <Descriptions.Item label="注册时间">{user?.created_at || '-'}</Descriptions.Item>
</Descriptions> </Descriptions>
...@@ -61,15 +61,15 @@ const PatientProfilePage: React.FC = () => { ...@@ -61,15 +61,15 @@ const PatientProfilePage: React.FC = () => {
<div className="w-10 h-10 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-2"> <div className="w-10 h-10 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-2">
<SafetyCertificateOutlined className="text-xl text-green-500" /> <SafetyCertificateOutlined className="text-xl text-green-500" />
</div> </div>
<Text strong className="text-xs!">已完成实名认?/Text> <Text strong className="text-xs!">已完成实名认</Text>
</div> </div>
) : ( ) : (
<div className="text-center py-4"> <div className="text-center py-4">
<div className="w-10 h-10 rounded-full bg-yellow-50 flex items-center justify-center mx-auto mb-2"> <div className="w-10 h-10 rounded-full bg-yellow-50 flex items-center justify-center mx-auto mb-2">
<IdcardOutlined className="text-xl text-yellow-500" /> <IdcardOutlined className="text-xl text-yellow-500" />
</div> </div>
<div className="text-xs text-gray-500 mb-2">完成实名认证后可享受完整的线上问诊服?/div> <div className="text-xs text-gray-500 mb-2">完成实名认证后可享受完整的线上问诊服</div>
<Button type="primary" size="small">去认?/Button> <Button type="primary" size="small">去认</Button>
</div> </div>
)} )}
</Card> </Card>
...@@ -81,8 +81,8 @@ const PatientProfilePage: React.FC = () => { ...@@ -81,8 +81,8 @@ const PatientProfilePage: React.FC = () => {
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/prescription')}>电子处方</Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/prescription')}>电子处方</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/health-records')}>健康档案</Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/health-records')}>健康档案</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/chronic')}>慢病管理</Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/chronic')}>慢病管理</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/pharmacy/order')}>药品配?/Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/pharmacy/order')}>药品配送</Button></Col>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/pre-consult')}>AI预问?/Button></Col> <Col span={8}><Button block size="small" onClick={() => router.push('/patient/pre-consult')}>AI预问诊</Button></Col>
</Row> </Row>
</Card> </Card>
</div> </div>
......
...@@ -17,10 +17,10 @@ const { TextArea } = Input; ...@@ -17,10 +17,10 @@ const { TextArea } = Input;
const statusMap: Record<string, { text: string; color: string }> = { const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '等待接诊', color: 'orange' }, pending: { text: '等待接诊', color: 'orange' },
waiting: { text: '等待?, color: 'orange' }, waiting: { text: '等待处, color: 'orange' },
in_progress: { text: '问诊?, color: 'green' }, in_progress: { text: '问诊量, color: 'green' },
completed: { text: '已完?, color: 'default' }, completed: { text: '已完成, color: 'default' },
cancelled: { text: '已取?, color: 'red' }, cancelled: { text: '已取消, color: 'red' },
}; };
const PatientTextConsultPage: React.FC = () => { const PatientTextConsultPage: React.FC = () => {
...@@ -52,7 +52,7 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -52,7 +52,7 @@ const PatientTextConsultPage: React.FC = () => {
setInputValue(''); setInputValue('');
}, },
onError: () => { onError: () => {
message.error('发送失?); message.error('发送失败);
}, },
}); });
...@@ -164,7 +164,7 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -164,7 +164,7 @@ const PatientTextConsultPage: React.FC = () => {
</div> </div>
<Divider className="my-1!" /> <Divider className="my-1!" />
<div className="text-[11px] text-gray-400 mb-1">主诉</div> <div className="text-[11px] text-gray-400 mb-1">主诉</div>
<div className="text-xs bg-gray-50 rounded p-2">{consult?.chief_complaint || '未填?}</div> <div className="text-xs bg-gray-50 rounded p-2">{consult?.chief_complaint || '未填写}</div>
</Card> </Card>
</Col> </Col>
...@@ -179,7 +179,7 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -179,7 +179,7 @@ const PatientTextConsultPage: React.FC = () => {
<div style={{ flex: 1, overflow: 'auto', padding: 12 }}> <div style={{ flex: 1, overflow: 'auto', padding: 12 }}>
{(!messagesData?.data || messagesData.data.length === 0) ? ( {(!messagesData?.data || messagesData.data.length === 0) ? (
<div className="text-center py-10 text-xs text-gray-400"> <div className="text-center py-10 text-xs text-gray-400">
{consult?.status === 'pending' || consult?.status === 'waiting' ? '等待医生接诊?..' : '暂无消息'} {consult?.status === 'pending' || consult?.status === 'waiting' ? '等待医生接诊中..' : '暂无消息'}
</div> </div>
) : ( ) : (
<List dataSource={messagesData.data} renderItem={renderMessage} split={false} /> <List dataSource={messagesData.data} renderItem={renderMessage} split={false} />
...@@ -193,11 +193,11 @@ const PatientTextConsultPage: React.FC = () => { ...@@ -193,11 +193,11 @@ const PatientTextConsultPage: React.FC = () => {
placeholder="输入消息..." autoSize={{ minRows: 1, maxRows: 3 }} size="small" placeholder="输入消息..." autoSize={{ minRows: 1, maxRows: 3 }} size="small"
onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} /> onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} style={{ flex: 1 }} />
<Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend} <Button type="primary" size="small" icon={<SendOutlined />} onClick={handleSend}
loading={sendMutation.isPending}>发?/Button> loading={sendMutation.isPending}>发送</Button>
</Space.Compact> </Space.Compact>
) : ( ) : (
<div className="text-center text-xs text-gray-400 py-1"> <div className="text-center text-xs text-gray-400 py-1">
{consult?.status === 'completed' ? '问诊已结? : '等待医生接诊后可发送消?} {consult?.status === 'completed' ? '问诊已结算 : '等待医生接诊后可发送消息}
</div> </div>
)} )}
</div> </div>
......
...@@ -33,7 +33,7 @@ const PatientVideoConsultPage: React.FC = () => { ...@@ -33,7 +33,7 @@ const PatientVideoConsultPage: React.FC = () => {
} = useVideoCall({ } = useVideoCall({
consultId: id || '', consultId: id || '',
onCallEnded: () => { onCallEnded: () => {
message.info('通话已结?); message.info('通话已结束);
router.push('/patient/consult'); router.push('/patient/consult');
}, },
}); });
...@@ -46,7 +46,7 @@ const PatientVideoConsultPage: React.FC = () => { ...@@ -46,7 +46,7 @@ const PatientVideoConsultPage: React.FC = () => {
useEffect(() => { useEffect(() => {
joinRoom().catch((error) => { joinRoom().catch((error) => {
message.error('加入房间失败? + error.message); message.error('加入房间失败了 + error.message);
}); });
return () => { return () => {
......
B'use client'; B'use client';
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment