Commit e26e5d42 authored by yuguo's avatar yuguo

fix

parent 45960530
......@@ -122,7 +122,7 @@ func (s *Service) Register(ctx context.Context, req *RegisterRequest) (*LoginRes
return nil, err
}
// 如果是患者,创建患者扩展信息
// 根据角色创建扩展信息
if role == "patient" {
patientProfile := &model.PatientProfile{
ID: uuid.New().String(),
......@@ -131,6 +131,16 @@ func (s *Service) Register(ctx context.Context, req *RegisterRequest) (*LoginRes
if err := s.db.Create(patientProfile).Error; err != nil {
fmt.Printf("创建患者扩展信息失败: %v\n", err)
}
} else if role == "doctor" {
doctor := &model.Doctor{
ID: uuid.New().String(),
UserID: user.ID,
Name: req.RealName,
Status: "pending", // 注册后需审核
}
if err := s.db.Create(doctor).Error; err != nil {
fmt.Printf("创建医生档案失败: %v\n", err)
}
}
tokenPair, err := utils.GenerateTokenPair(user.ID, user.Role)
......
This diff is collapsed.
......@@ -362,6 +362,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
}}>
{/* 标题 */}
<div style={{
......@@ -371,9 +372,17 @@ const AIPanel: React.FC<AIPanelProps> = ({
alignItems: 'center',
gap: 8,
flexShrink: 0,
background: 'linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%)',
}}>
<RobotOutlined style={{ color: '#52c41a', fontSize: 16 }} />
<span style={{ fontWeight: 600, fontSize: 14 }}>AI 辅助</span>
<div style={{
width: 28, height: 28, borderRadius: 8,
background: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: '0 2px 6px rgba(82, 196, 26, 0.3)',
}}>
<RobotOutlined style={{ color: '#fff', fontSize: 14 }} />
</div>
<span style={{ fontWeight: 600, fontSize: 14, color: '#1d2129' }}>AI 辅助</span>
{hasActiveConsult && (
<Badge status="processing" style={{ marginLeft: 2 }} />
)}
......@@ -470,9 +479,19 @@ const AIPanel: React.FC<AIPanelProps> = ({
]}
/>
) : (
<div style={{ textAlign: 'center', paddingTop: 60 }}>
<RobotOutlined style={{ fontSize: 40, color: '#d9d9d9', display: 'block', marginBottom: 14 }} />
<Text type="secondary" style={{ fontSize: 13 }}>接诊后自动开始AI辅助</Text>
<div style={{ textAlign: 'center', paddingTop: 60, padding: '60px 24px' }}>
<div style={{
width: 64, height: 64, borderRadius: 16, margin: '0 auto 16px',
background: 'linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<RobotOutlined style={{ fontSize: 28, color: '#b7eb8f' }} />
</div>
<Text strong style={{ fontSize: 14, display: 'block', marginBottom: 6, color: '#8c8c8c' }}>AI 辅助诊疗</Text>
<Text type="secondary" style={{ fontSize: 12, lineHeight: 1.6 }}>
接诊后将自动启动 AI 辅助分析,<br />
为您提供鉴别诊断、用药建议等支持
</Text>
</div>
)}
</div>
......
......@@ -382,6 +382,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
border: '1px solid #E5E8EF',
background: '#fff',
overflow: 'hidden',
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
}}>
{/* 顶部患者信息栏 */}
<div style={{
......@@ -391,7 +392,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
alignItems: 'center',
gap: 10,
flexShrink: 0,
background: '#fff',
background: 'linear-gradient(135deg, #ffffff 0%, #f9fafb 100%)',
}}>
{activeConsult ? (
<>
......@@ -429,8 +430,9 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
{activeConsult.started_at && activeConsult.status === 'in_progress' && (
<div style={{
display: 'flex', alignItems: 'center', gap: 4,
background: '#f6ffed', border: '1px solid #b7eb8f', borderRadius: 6,
padding: '2px 10px', fontSize: 13, fontWeight: 500, color: '#52c41a',
background: 'linear-gradient(135deg, #f6ffed 0%, #e8ffd6 100%)',
border: '1px solid #b7eb8f', borderRadius: 8,
padding: '3px 12px', fontSize: 13, fontWeight: 600, color: '#389e0d',
fontVariantNumeric: 'tabular-nums',
}}>
<ClockCircleOutlined style={{ fontSize: 12 }} />
......@@ -488,7 +490,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
</div>
{/* 消息区域 */}
<div style={{ flex: 1, overflow: 'auto', padding: 16, background: '#f5f7fb' }}>
<div style={{ flex: 1, overflow: 'auto', padding: 16, background: 'linear-gradient(180deg, #f5f7fb 0%, #f0f2f5 100%)' }}>
{activeConsult ? (
<>
{messages.length === 0 && (
......@@ -617,13 +619,16 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
onClick={handleSend}
loading={sending}
style={{
background: '#52c41a',
background: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)',
borderColor: '#52c41a',
borderRadius: 8,
borderRadius: 10,
height: 'auto',
alignSelf: 'flex-end',
paddingBottom: 8,
paddingTop: 8,
paddingBottom: 10,
paddingTop: 10,
paddingLeft: 16,
paddingRight: 16,
boxShadow: '0 2px 6px rgba(82, 196, 26, 0.3)',
}}
>
发送
......
......@@ -287,74 +287,92 @@ const ConsultPage: React.FC = () => {
{ label: 'AI使用率', value: workbenchStats.ai_usage_rate.toFixed(0), unit: '%', icon: <RobotOutlined />, color: '#0891B2' },
] : [];
const statBgMap: Record<string, string> = {
'候诊中': 'linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%)',
'接诊中': 'linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%)',
'今日完成': 'linear-gradient(135deg, #E6FFFB 0%, #B5F5EC 100%)',
'今日收入': 'linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%)',
};
return (
<div style={{ height: 'calc(100vh - 72px)', display: 'flex', flexDirection: 'column', gap: 16, padding: '12px 16px' }}>
{/* 顶部标题 + 统计卡片 */}
<div style={{ height: 'calc(100vh - 72px)', display: 'flex', flexDirection: 'column', gap: 12, padding: '12px 16px' }}>
{/* 顶部统计栏 */}
<div style={{ flexShrink: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<div style={{ marginLeft: 'auto', display: 'flex', gap: 10, alignItems: 'center' }}>
{statItems.map(({ label, value, color, icon, unit }) => (
<div
key={label}
style={{
background: '#fff',
borderRadius: 10,
padding: '8px 18px',
border: '1px solid #E5E8EF',
minWidth: 100,
boxShadow: '0 1px 4px rgba(0,0,0,0.04)',
}}
>
<div style={{ fontSize: 11, color, display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2 }}>
{icon}
{label}
</div>
<div style={{ fontSize: 22, fontWeight: 700, color, lineHeight: 1.2 }}>
{value}
<span style={{ fontSize: 11, fontWeight: 400, marginLeft: 2 }}>{unit}</span>
</div>
</div>
))}
<div style={{ display: 'flex', alignItems: 'stretch', gap: 10 }}>
{statItems.map(({ label, value, color, icon, unit }) => (
<div
onClick={() => setShowEfficiency(!showEfficiency)}
key={label}
style={{
cursor: 'pointer', padding: '6px 10px', borderRadius: 8,
background: showEfficiency ? '#F0FFF5' : '#f5f5f5',
border: `1px solid ${showEfficiency ? '#91d5ff' : '#e8e8e8'}`,
display: 'flex', alignItems: 'center', gap: 4,
fontSize: 12, color: showEfficiency ? '#2B9E6E' : '#8c8c8c',
transition: 'all 0.2s',
flex: 1,
background: statBgMap[label] || '#fff',
borderRadius: 12,
padding: '12px 16px',
border: '1px solid rgba(0,0,0,0.04)',
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
transition: 'transform 0.2s, box-shadow 0.2s',
cursor: 'default',
}}
onMouseEnter={e => {
(e.currentTarget as HTMLDivElement).style.transform = 'translateY(-1px)';
(e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.08)';
}}
onMouseLeave={e => {
(e.currentTarget as HTMLDivElement).style.transform = 'translateY(0)';
(e.currentTarget as HTMLDivElement).style.boxShadow = '0 1px 3px rgba(0,0,0,0.04)';
}}
>
<DashboardOutlined />
效率
{showEfficiency ? <UpOutlined style={{ fontSize: 10 }} /> : <DownOutlined style={{ fontSize: 10 }} />}
<div style={{ fontSize: 12, color: '#666', display: 'flex', alignItems: 'center', gap: 5, marginBottom: 6 }}>
{React.cloneElement(icon as React.ReactElement, { style: { color, fontSize: 14 } })}
{label}
</div>
<div style={{ fontSize: 26, fontWeight: 700, color, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>
{value}
<span style={{ fontSize: 12, fontWeight: 400, marginLeft: 3, color: '#999' }}>{unit}</span>
</div>
</div>
))}
<div
onClick={() => setShowEfficiency(!showEfficiency)}
style={{
cursor: 'pointer', padding: '12px 14px', borderRadius: 12,
background: showEfficiency ? 'linear-gradient(135deg, #F0FFF5 0%, #D9F7E0 100%)' : '#f8f9fa',
border: `1px solid ${showEfficiency ? '#b7eb8f' : '#e8e8e8'}`,
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4,
fontSize: 12, color: showEfficiency ? '#2B9E6E' : '#8c8c8c',
transition: 'all 0.2s',
minWidth: 56,
}}
>
<DashboardOutlined style={{ fontSize: 16 }} />
<span style={{ fontSize: 11 }}>效率</span>
{showEfficiency ? <UpOutlined style={{ fontSize: 9 }} /> : <DownOutlined style={{ fontSize: 9 }} />}
</div>
</div>
{/* 可折叠效率指标行 */}
{showEfficiency && efficiencyItems.length > 0 && (
<div style={{
display: 'flex', gap: 8, marginTop: 8, padding: '8px 12px',
background: '#fafafa', borderRadius: 10, border: '1px solid #f0f0f0',
overflow: 'auto',
display: 'flex', gap: 8, marginTop: 8, padding: '10px 16px',
background: 'linear-gradient(135deg, #fafbfc 0%, #f0f2f5 100%)',
borderRadius: 12, border: '1px solid #eee',
}}>
{efficiencyItems.map(({ label, value, unit, icon, color }) => (
<div
key={label}
style={{
flex: 1, minWidth: 90, textAlign: 'center',
padding: '4px 8px',
flex: 1, minWidth: 80, textAlign: 'center',
padding: '6px 8px',
borderRadius: 8,
background: 'rgba(255,255,255,0.7)',
}}
>
<div style={{ fontSize: 11, color: '#8c8c8c', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3, marginBottom: 2 }}>
{React.cloneElement(icon as React.ReactElement, { style: { color, fontSize: 11 } })}
<div style={{ fontSize: 11, color: '#8c8c8c', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3, marginBottom: 4 }}>
{React.cloneElement(icon as React.ReactElement, { style: { color, fontSize: 12 } })}
{label}
</div>
<div style={{ fontSize: 18, fontWeight: 600, color, lineHeight: 1.3 }}>
<div style={{ fontSize: 20, fontWeight: 600, color, lineHeight: 1.2, fontVariantNumeric: 'tabular-nums' }}>
{value}
<span style={{ fontSize: 10, fontWeight: 400, marginLeft: 1 }}>{unit}</span>
<span style={{ fontSize: 10, fontWeight: 400, marginLeft: 2, color: '#999' }}>{unit}</span>
</div>
</div>
))}
......@@ -363,9 +381,9 @@ const ConsultPage: React.FC = () => {
</div>
{/* 主体三栏 */}
<div style={{ flex: 1, display: 'flex', gap: 12, overflow: 'hidden', minHeight: 0 }}>
<div style={{ flex: 1, display: 'flex', gap: 10, overflow: 'hidden', minHeight: 0 }}>
{/* 左:患者列表 */}
<div style={{ width: 272, flexShrink: 0 }}>
<div style={{ width: 280, flexShrink: 0 }}>
<PatientList
patients={patientList}
onSelectPatient={handleSelectPatient}
......
'use client';
import React, { useState, useEffect } from 'react';
import { Card, Calendar, Button, Modal, Form, TimePicker, InputNumber, Tag, Typography, Space, Row, Col, Badge, List, message, Spin } from 'antd';
import { Card, Calendar, Button, Modal, Form, TimePicker, InputNumber, Tag, Typography, Space, Row, Col, Badge, List, Spin, App } from 'antd';
import { DrawerForm } from '@ant-design/pro-components';
import { PlusOutlined, DeleteOutlined, CalendarOutlined } from '@ant-design/icons';
import dayjs, { Dayjs } from 'dayjs';
......@@ -10,6 +10,7 @@ import { doctorPortalApi, type ScheduleSlot } from '@/api/doctorPortal';
const { Text } = Typography;
const DoctorSchedulePage: React.FC = () => {
const { message, modal } = App.useApp();
const [selectedDate, setSelectedDate] = useState<Dayjs>(dayjs());
const [isModalOpen, setIsModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
......@@ -59,7 +60,7 @@ const DoctorSchedulePage: React.FC = () => {
setIsModalOpen(true);
};
const handleSaveSchedule = async (values: any) => {
const handleSaveSchedule = async (values: any): Promise<boolean> => {
try {
await doctorPortalApi.createSchedule([{
date: selectedDate.format('YYYY-MM-DD'),
......@@ -68,15 +69,16 @@ const DoctorSchedulePage: React.FC = () => {
max_count: values.max_count,
}]);
message.success('排班添加成功');
setIsModalOpen(false);
fetchSchedule();
return true;
} catch {
message.error('添加排班失败');
return false;
}
};
const handleDeleteSlot = (slotId: string) => {
Modal.confirm({
modal.confirm({
title: '确认删除',
content: '确定要删除该排班吗?',
onOk: async () => {
......@@ -159,10 +161,7 @@ const DoctorSchedulePage: React.FC = () => {
title={`添加排班 - ${selectedDate.format('YYYY-MM-DD')}`}
open={isModalOpen}
onOpenChange={setIsModalOpen}
onFinish={async (values: any) => {
await handleSaveSchedule(values);
return true;
}}
onFinish={handleSaveSchedule}
drawerProps={{ placement: 'right', destroyOnClose: true }}
width={480}
>
......
......@@ -2,7 +2,7 @@
import React, { useState } from 'react';
import {
Card, Tabs, Button, Tag, Space, Form, Input, Select, App,
Card, Button, Tag, Space, Form, Input, Select, App,
DatePicker, Switch, Popconfirm, Typography, Row, Col,
TimePicker, Statistic, Empty,
} from 'antd';
......@@ -566,17 +566,68 @@ const MetricsTab: React.FC = () => {
// ========== 主页面 ==========
const PatientChronicPage: React.FC = () => {
const tabItems = [
{ key: 'records', label: <><HeartOutlined /> 慢病档案</>, children: <ChronicRecordsTab /> },
{ key: 'renewals', label: <><FileAddOutlined /> 续方申请</>, children: <RenewalsTab /> },
{ key: 'reminders', label: <><BellOutlined /> 用药提醒</>, children: <RemindersTab /> },
{ key: 'metrics', label: <><LineChartOutlined /> 健康指标</>, children: <MetricsTab /> },
const [activeKey, setActiveKey] = useState('records');
const menuItems = [
{ key: 'records', icon: <HeartOutlined />, label: '慢病档案' },
{ key: 'renewals', icon: <FileAddOutlined />, label: '续方申请' },
{ key: 'reminders', icon: <BellOutlined />, label: '用药提醒' },
{ key: 'metrics', icon: <LineChartOutlined />, label: '健康指标' },
];
const contentMap: Record<string, React.ReactNode> = {
records: <ChronicRecordsTab />,
renewals: <RenewalsTab />,
reminders: <RemindersTab />,
metrics: <MetricsTab />,
};
const currentMenu = menuItems.find(m => m.key === activeKey);
return (
<div style={{ padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 16 }}>
<Card style={{ borderRadius: 12, border: '1px solid #E5E8EF' }}>
<Tabs items={tabItems} />
<div style={{ padding: '12px 16px', display: 'flex', gap: 16, minHeight: 'calc(100vh - 120px)' }}>
{/* 左侧导航 */}
<Card
style={{ width: 200, flexShrink: 0, borderRadius: 12, border: '1px solid #E5E8EF' }}
styles={{ body: { padding: '12px 8px' } }}
>
<div style={{ padding: '4px 12px 16px', marginBottom: 8, borderBottom: '1px solid #f0f0f0' }}>
<Space>
<MedicineBoxOutlined style={{ color: '#2B6DE8', fontSize: 16 }} />
<Text strong style={{ fontSize: 15 }}>慢病管理</Text>
</Space>
</div>
{menuItems.map(item => (
<div
key={item.key}
onClick={() => setActiveKey(item.key)}
style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '10px 16px', borderRadius: 8, cursor: 'pointer',
marginBottom: 4, transition: 'all 0.2s',
background: activeKey === item.key ? '#EBF1FF' : 'transparent',
color: activeKey === item.key ? '#2B6DE8' : '#595959',
fontWeight: activeKey === item.key ? 600 : 400,
}}
>
<span style={{ fontSize: 16 }}>{item.icon}</span>
<span style={{ fontSize: 14 }}>{item.label}</span>
</div>
))}
</Card>
{/* 右侧内容 */}
<Card
title={currentMenu && (
<Space>
<span style={{ color: '#2B6DE8' }}>{currentMenu.icon}</span>
<span>{currentMenu.label}</span>
</Space>
)}
style={{ flex: 1, borderRadius: 12, border: '1px solid #E5E8EF' }}
styles={{ body: { padding: '16px 20px' } }}
>
{contentMap[activeKey]}
</Card>
</div>
);
......
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