Commit f1c4920f authored by yuguo's avatar yuguo

fix

parent 4ae56601
...@@ -14,7 +14,11 @@ ...@@ -14,7 +14,11 @@
"Bash(npx tailwindcss:*)", "Bash(npx tailwindcss:*)",
"Bash(npx next:*)", "Bash(npx next:*)",
"Bash(PGPASSWORD=T5sSfTZ6XYTD9bfC psql:*)", "Bash(PGPASSWORD=T5sSfTZ6XYTD9bfC psql:*)",
"Bash(npm install:*)" "Bash(npm install:*)",
"Bash(kill:*)",
"Bash(go run:*)",
"Bash(npm run:*)",
"Bash(lsof:*)"
] ]
} }
} }
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"internet-hospital/internal/model" "internet-hospital/internal/model"
) )
...@@ -46,7 +47,9 @@ func (s *Service) CreatePatient(ctx context.Context, req *CreatePatientRequest) ...@@ -46,7 +47,9 @@ func (s *Service) CreatePatient(ctx context.Context, req *CreatePatientRequest)
return fmt.Errorf("密码加密失败: %w", err) return fmt.Errorf("密码加密失败: %w", err)
} }
userID := uuid.New().String()
user := &model.User{ user := &model.User{
ID: userID,
Phone: req.Phone, Phone: req.Phone,
Password: string(hashedPassword), Password: string(hashedPassword),
RealName: req.RealName, RealName: req.RealName,
...@@ -56,7 +59,22 @@ func (s *Service) CreatePatient(ctx context.Context, req *CreatePatientRequest) ...@@ -56,7 +59,22 @@ func (s *Service) CreatePatient(ctx context.Context, req *CreatePatientRequest)
Age: req.Age, Age: req.Age,
} }
return s.db.Create(user).Error tx := s.db.Begin()
if err := tx.Create(user).Error; err != nil {
tx.Rollback()
return err
}
profile := &model.PatientProfile{
ID: uuid.New().String(),
UserID: userID,
}
if err := tx.Create(profile).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
} }
// UpdatePatient 更新患者信息 // UpdatePatient 更新患者信息
......
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
HomeOutlined, UserOutlined, MedicineBoxOutlined, FileTextOutlined, HomeOutlined, UserOutlined, MedicineBoxOutlined, FileTextOutlined,
HeartOutlined, SettingOutlined, LogoutOutlined, BellOutlined, HeartOutlined, SettingOutlined, LogoutOutlined, BellOutlined,
RobotOutlined, VideoCameraOutlined, PayCircleOutlined, RobotOutlined, VideoCameraOutlined, PayCircleOutlined,
ShoppingCartOutlined, FolderOpenOutlined, ShoppingCartOutlined, FolderOpenOutlined, SearchOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useUserStore } from '@/store/userStore'; import { useUserStore } from '@/store/userStore';
...@@ -16,17 +16,39 @@ const { Text } = Typography; ...@@ -16,17 +16,39 @@ const { Text } = Typography;
const menuItems = [ const menuItems = [
{ key: '/patient/home', icon: <HomeOutlined />, label: '首页' }, { key: '/patient/home', icon: <HomeOutlined />, label: '首页' },
{ key: '/patient/doctors', icon: <MedicineBoxOutlined />, label: '医生列表' }, {
key: 'consult-services', icon: <VideoCameraOutlined />, label: '问诊服务',
children: [
{ key: '/patient/doctors', icon: <SearchOutlined />, label: '找医生' },
{ key: '/patient/pre-consult', icon: <RobotOutlined />, label: 'AI预问诊' }, { key: '/patient/pre-consult', icon: <RobotOutlined />, label: 'AI预问诊' },
{ key: '/patient/consult', icon: <VideoCameraOutlined />, label: '问诊' }, { key: '/patient/consult', icon: <VideoCameraOutlined />, label: '我的问诊' },
{ key: '/patient/prescription', icon: <FileTextOutlined />, label: '处方' }, ],
{ key: '/patient/payment', icon: <PayCircleOutlined />, label: '支付' }, },
{
key: 'rx-pharmacy', icon: <MedicineBoxOutlined />, label: '处方购药',
children: [
{ key: '/patient/prescription', icon: <FileTextOutlined />, label: '电子处方' },
{ key: '/patient/pharmacy/order', icon: <ShoppingCartOutlined />, label: '药品配送' }, { key: '/patient/pharmacy/order', icon: <ShoppingCartOutlined />, label: '药品配送' },
{ key: '/patient/payment', icon: <PayCircleOutlined />, label: '支付管理' },
],
},
{
key: 'health-mgmt', icon: <HeartOutlined />, label: '健康管理',
children: [
{ key: '/patient/chronic', icon: <HeartOutlined />, label: '慢病管理' }, { key: '/patient/chronic', icon: <HeartOutlined />, label: '慢病管理' },
{ key: '/patient/health-records', icon: <FolderOpenOutlined />, label: '健康档案' }, { key: '/patient/health-records', icon: <FolderOpenOutlined />, label: '健康档案' },
],
},
{ key: '/patient/profile', icon: <UserOutlined />, label: '个人中心' }, { key: '/patient/profile', icon: <UserOutlined />, label: '个人中心' },
]; ];
// 所有可路由的 key 列表(用于选中匹配)
const allRouteKeys = [
'/patient/home', '/patient/doctors', '/patient/pre-consult', '/patient/consult',
'/patient/prescription', '/patient/pharmacy/order', '/patient/payment',
'/patient/chronic', '/patient/health-records', '/patient/profile',
];
export default function PatientLayout({ children }: { children: React.ReactNode }) { export default function PatientLayout({ children }: { children: React.ReactNode }) {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
...@@ -52,8 +74,8 @@ export default function PatientLayout({ children }: { children: React.ReactNode ...@@ -52,8 +74,8 @@ export default function PatientLayout({ children }: { children: React.ReactNode
}; };
const getSelectedKeys = () => { const getSelectedKeys = () => {
const match = menuItems.find(item => currentPath.startsWith(item.key)); const match = allRouteKeys.find(k => currentPath.startsWith(k));
return match ? [match.key] : []; return match ? [match] : [];
}; };
return ( return (
......
This diff is collapsed.
...@@ -8,28 +8,47 @@ import { ...@@ -8,28 +8,47 @@ import {
SafetyCertificateOutlined, SafetyCertificateOutlined,
EditOutlined, EditOutlined,
IdcardOutlined, IdcardOutlined,
VideoCameraOutlined,
FileTextOutlined,
FolderOpenOutlined,
HeartOutlined,
ShoppingCartOutlined,
RobotOutlined,
RightOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useUserStore } from '../../../store/userStore'; import { useUserStore } from '../../../store/userStore';
const { Title, Text } = Typography; const { Text } = Typography;
const PatientProfilePage: React.FC = () => { const PatientProfilePage: React.FC = () => {
const { user } = useUserStore(); const { user } = useUserStore();
const router = useRouter(); const router = useRouter();
const serviceLinks = [
{ icon: <VideoCameraOutlined />, title: '就诊记录', path: '/patient/consult', color: '#1890ff' },
{ icon: <FileTextOutlined />, title: '电子处方', path: '/patient/prescription', color: '#13c2c2' },
{ icon: <FolderOpenOutlined />, title: '健康档案', path: '/patient/health-records', color: '#52c41a' },
{ icon: <HeartOutlined />, title: '慢病管理', path: '/patient/chronic', color: '#eb2f96' },
{ icon: <ShoppingCartOutlined />, title: '药品配送', path: '/patient/pharmacy/order', color: '#fa8c16' },
{ icon: <RobotOutlined />, title: 'AI预问诊', path: '/patient/pre-consult', color: '#722ed1' },
];
return ( return (
<div className="max-w-[1200px] mx-auto space-y-2"> <div style={{ maxWidth: 1200, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 8 }}>
{/* 个人信息卡片 */} {/* 个人信息卡片 */}
<Card size="small"> <Card size="small">
<Row gutter={12} align="middle"> <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<Col> <Avatar size={56} src={user?.avatar} icon={<UserOutlined />}
<Avatar size={56} src={user?.avatar} icon={<UserOutlined />} /> style={{ backgroundColor: '#1890ff', flexShrink: 0 }} />
</Col> <div style={{ flex: 1 }}>
<Col flex="auto"> <h4 style={{ fontSize: 15, fontWeight: 700, margin: '0 0 4px', color: '#1d2129' }}>
<h4 className="text-sm font-bold m-0 mb-0.5">{user?.real_name || '未设置姓名'}</h4> {user?.real_name || '未设置姓名'}
<span className="text-xs text-gray-400"><PhoneOutlined className="mr-1" />{user?.phone || '未绑定手机'}</span> </h4>
<div className="mt-1"> <div style={{ fontSize: 12, color: '#8c8c8c', marginBottom: 6 }}>
<PhoneOutlined style={{ marginRight: 4 }} />{user?.phone || '未绑定手机'}
</div>
<div style={{ display: 'flex', gap: 4 }}>
{user?.is_verified ? ( {user?.is_verified ? (
<Tag icon={<SafetyCertificateOutlined />} color="success">已实名</Tag> <Tag icon={<SafetyCertificateOutlined />} color="success">已实名</Tag>
) : ( ) : (
...@@ -37,52 +56,91 @@ const PatientProfilePage: React.FC = () => { ...@@ -37,52 +56,91 @@ const PatientProfilePage: React.FC = () => {
)} )}
<Tag color="blue">患者</Tag> <Tag color="blue">患者</Tag>
</div> </div>
</Col> </div>
<Col>
<Button size="small" type="primary" icon={<EditOutlined />}>编辑资料</Button> <Button size="small" type="primary" icon={<EditOutlined />}>编辑资料</Button>
</Col> </div>
</Row>
</Card> </Card>
<Row gutter={[8, 8]}>
{/* 基本信息 */} {/* 基本信息 */}
<Card title={<span className="text-xs font-semibold">基本信息</span>} size="small"> <Col xs={24} sm={12}>
<Descriptions column={2} size="small"> <Card title={<span style={{ fontSize: 13, fontWeight: 600 }}>基本信息</span>} size="small" style={{ height: '100%' }}>
<Descriptions.Item label="用户ID">{user?.id || '-'}</Descriptions.Item> <Descriptions column={1} size="small">
<Descriptions.Item label="手机">{user?.phone || '-'}</Descriptions.Item> <Descriptions.Item label="用户ID">
<Text copyable style={{ fontSize: 12 }}>{user?.id || '-'}</Text>
</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 ? new Date(user.created_at).toLocaleDateString('zh-CN') : '-'}
</Descriptions.Item>
</Descriptions> </Descriptions>
</Card> </Card>
</Col>
{/* 实名认证 */} {/* 实名认证 */}
<Card title={<span className="text-xs font-semibold">实名认证</span>} size="small"> <Col xs={24} sm={12}>
<Card title={<span style={{ fontSize: 13, fontWeight: 600 }}>实名认证</span>} size="small" style={{ height: '100%' }}>
{user?.is_verified ? ( {user?.is_verified ? (
<div className="text-center py-4"> <div style={{ textAlign: 'center', padding: '16px 0' }}>
<div className="w-10 h-10 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-2"> <div style={{
<SafetyCertificateOutlined className="text-xl text-green-500" /> width: 48, height: 48, borderRadius: '50%', backgroundColor: '#f6ffed',
display: 'flex', alignItems: 'center', justifyContent: 'center',
margin: '0 auto 8px', fontSize: 24, color: '#52c41a',
}}>
<SafetyCertificateOutlined />
</div> </div>
<Text strong className="text-xs!">已完成实名认证</Text> <div style={{ fontSize: 13, fontWeight: 600, color: '#1d2129' }}>已完成实名认证</div>
<div style={{ fontSize: 11, color: '#8c8c8c', marginTop: 4 }}>可使用全部在线问诊服务</div>
</div> </div>
) : ( ) : (
<div className="text-center py-4"> <div style={{ textAlign: 'center', padding: '16px 0' }}>
<div className="w-10 h-10 rounded-full bg-yellow-50 flex items-center justify-center mx-auto mb-2"> <div style={{
<IdcardOutlined className="text-xl text-yellow-500" /> width: 48, height: 48, borderRadius: '50%', backgroundColor: '#fffbe6',
display: 'flex', alignItems: 'center', justifyContent: 'center',
margin: '0 auto 8px', fontSize: 24, color: '#faad14',
}}>
<IdcardOutlined />
</div>
<div style={{ fontSize: 12, color: '#8c8c8c', marginBottom: 8 }}>
完成实名认证后可享受完整的线上问诊服务
</div> </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>
</Col>
</Row>
{/* 快捷操作 */} {/* 更多服务 */}
<Card title={<span className="text-xs font-semibold">更多服务</span>} size="small"> <Card title={<span style={{ fontSize: 13, fontWeight: 600 }}>更多服务</span>} size="small">
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/consult')}>就诊记录</Button></Col> {serviceLinks.map((link) => (
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/prescription')}>电子处方</Button></Col> <Col xs={12} sm={8} md={4} key={link.title}>
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/health-records')}>健康档案</Button></Col> <div
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/chronic')}>慢病管理</Button></Col> style={{
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/pharmacy/order')}>药品配送</Button></Col> display: 'flex', alignItems: 'center', justifyContent: 'space-between',
<Col span={8}><Button block size="small" onClick={() => router.push('/patient/pre-consult')}>AI预问诊</Button></Col> padding: '10px 12px', borderRadius: 8, cursor: 'pointer',
border: '1px solid #e6f0fa', transition: 'all 0.2s',
}}
onClick={() => router.push(link.path)}
onMouseEnter={(e) => { e.currentTarget.style.background = '#f0f5ff'; e.currentTarget.style.borderColor = '#bbd4f0'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.borderColor = '#e6f0fa'; }}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{
width: 28, height: 28, borderRadius: 6, display: 'flex',
alignItems: 'center', justifyContent: 'center',
color: link.color, background: `${link.color}15`, fontSize: 14,
}}>
{link.icon}
</div>
<span style={{ fontSize: 12, fontWeight: 500, color: '#1d2129' }}>{link.title}</span>
</div>
<RightOutlined style={{ fontSize: 10, color: '#bfbfbf' }} />
</div>
</Col>
))}
</Row> </Row>
</Card> </Card>
</div> </div>
...@@ -90,4 +148,3 @@ const PatientProfilePage: React.FC = () => { ...@@ -90,4 +148,3 @@ const PatientProfilePage: React.FC = () => {
}; };
export default PatientProfilePage; export default PatientProfilePage;
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