Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
Internet-hospital
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
yuguo
Internet-hospital
Commits
f1c4920f
Commit
f1c4920f
authored
Feb 28, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
4ae56601
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
296 additions
and
181 deletions
+296
-181
.claude/settings.local.json
.claude/settings.local.json
+5
-1
server/internal/service/admin/user_crud_service.go
server/internal/service/admin/user_crud_service.go
+19
-1
web/src/app/(main)/patient/layout.tsx
web/src/app/(main)/patient/layout.tsx
+33
-11
web/src/pages/patient/Home/index.tsx
web/src/pages/patient/Home/index.tsx
+130
-116
web/src/pages/patient/Profile/index.tsx
web/src/pages/patient/Profile/index.tsx
+109
-52
No files found.
.claude/settings.local.json
View file @
f1c4920f
...
...
@@ -14,7 +14,11 @@
"Bash(npx tailwindcss:*)"
,
"Bash(npx next:*)"
,
"Bash(PGPASSWORD=T5sSfTZ6XYTD9bfC psql:*)"
,
"Bash(npm install:*)"
"Bash(npm install:*)"
,
"Bash(kill:*)"
,
"Bash(go run:*)"
,
"Bash(npm run:*)"
,
"Bash(lsof:*)"
]
}
}
server/internal/service/admin/user_crud_service.go
View file @
f1c4920f
...
...
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"internet-hospital/internal/model"
)
...
...
@@ -46,7 +47,9 @@ func (s *Service) CreatePatient(ctx context.Context, req *CreatePatientRequest)
return
fmt
.
Errorf
(
"密码加密失败: %w"
,
err
)
}
userID
:=
uuid
.
New
()
.
String
()
user
:=
&
model
.
User
{
ID
:
userID
,
Phone
:
req
.
Phone
,
Password
:
string
(
hashedPassword
),
RealName
:
req
.
RealName
,
...
...
@@ -56,7 +59,22 @@ func (s *Service) CreatePatient(ctx context.Context, req *CreatePatientRequest)
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 更新患者信息
...
...
web/src/app/(main)/patient/layout.tsx
View file @
f1c4920f
...
...
@@ -7,7 +7,7 @@ import {
HomeOutlined
,
UserOutlined
,
MedicineBoxOutlined
,
FileTextOutlined
,
HeartOutlined
,
SettingOutlined
,
LogoutOutlined
,
BellOutlined
,
RobotOutlined
,
VideoCameraOutlined
,
PayCircleOutlined
,
ShoppingCartOutlined
,
FolderOpenOutlined
,
ShoppingCartOutlined
,
FolderOpenOutlined
,
SearchOutlined
,
}
from
'
@ant-design/icons
'
;
import
{
useUserStore
}
from
'
@/store/userStore
'
;
...
...
@@ -16,17 +16,39 @@ const { Text } = Typography;
const
menuItems
=
[
{
key
:
'
/patient/home
'
,
icon
:
<
HomeOutlined
/>,
label
:
'
首页
'
},
{
key
:
'
/patient/doctors
'
,
icon
:
<
MedicineBoxOutlined
/>,
label
:
'
医生列表
'
},
{
key
:
'
/patient/pre-consult
'
,
icon
:
<
RobotOutlined
/>,
label
:
'
AI预问诊
'
},
{
key
:
'
/patient/consult
'
,
icon
:
<
VideoCameraOutlined
/>,
label
:
'
问诊
'
},
{
key
:
'
/patient/prescription
'
,
icon
:
<
FileTextOutlined
/>,
label
:
'
处方
'
},
{
key
:
'
/patient/payment
'
,
icon
:
<
PayCircleOutlined
/>,
label
:
'
支付
'
},
{
key
:
'
/patient/pharmacy/order
'
,
icon
:
<
ShoppingCartOutlined
/>,
label
:
'
药品配送
'
},
{
key
:
'
/patient/chronic
'
,
icon
:
<
HeartOutlined
/>,
label
:
'
慢病管理
'
},
{
key
:
'
/patient/health-records
'
,
icon
:
<
FolderOpenOutlined
/>,
label
:
'
健康档案
'
},
{
key
:
'
consult-services
'
,
icon
:
<
VideoCameraOutlined
/>,
label
:
'
问诊服务
'
,
children
:
[
{
key
:
'
/patient/doctors
'
,
icon
:
<
SearchOutlined
/>,
label
:
'
找医生
'
},
{
key
:
'
/patient/pre-consult
'
,
icon
:
<
RobotOutlined
/>,
label
:
'
AI预问诊
'
},
{
key
:
'
/patient/consult
'
,
icon
:
<
VideoCameraOutlined
/>,
label
:
'
我的问诊
'
},
],
},
{
key
:
'
rx-pharmacy
'
,
icon
:
<
MedicineBoxOutlined
/>,
label
:
'
处方购药
'
,
children
:
[
{
key
:
'
/patient/prescription
'
,
icon
:
<
FileTextOutlined
/>,
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/health-records
'
,
icon
:
<
FolderOpenOutlined
/>,
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
})
{
const
router
=
useRouter
();
const
pathname
=
usePathname
();
...
...
@@ -52,8 +74,8 @@ export default function PatientLayout({ children }: { children: React.ReactNode
};
const
getSelectedKeys
=
()
=>
{
const
match
=
menuItems
.
find
(
item
=>
currentPath
.
startsWith
(
item
.
key
));
return
match
?
[
match
.
key
]
:
[];
const
match
=
allRouteKeys
.
find
(
k
=>
currentPath
.
startsWith
(
k
));
return
match
?
[
match
]
:
[];
};
return
(
...
...
web/src/pages/patient/Home/index.tsx
View file @
f1c4920f
...
...
@@ -2,7 +2,7 @@
import
React
from
'
react
'
;
import
{
useRouter
}
from
'
next/navigation
'
;
import
{
Card
,
Row
,
Col
,
Input
,
Button
,
Typography
,
Space
,
Statistic
}
from
'
antd
'
;
import
{
Card
,
Row
,
Col
,
Input
,
Button
,
Typography
,
Tag
}
from
'
antd
'
;
import
{
SearchOutlined
,
VideoCameraOutlined
,
...
...
@@ -14,144 +14,158 @@ import {
TeamOutlined
,
SafetyCertificateOutlined
,
ClockCircleOutlined
,
RightOutlined
,
ShoppingCartOutlined
,
FolderOpenOutlined
,
}
from
'
@ant-design/icons
'
;
const
{
T
itle
,
Text
,
Paragraph
}
=
Typography
;
const
{
T
ext
}
=
Typography
;
const
PatientHomePage
:
React
.
FC
=
()
=>
{
const
router
=
useRouter
();
const
services
=
[
{
icon
:
<
VideoCameraOutlined
/>,
title
:
'
视频问诊
'
,
desc
:
'
面对面视频交流
'
,
path
:
'
/patient/doctors?type=video
'
,
color
:
'
#1890ff
'
,
bg
:
'
#e6f7ff
'
},
{
icon
:
<
MessageOutlined
/>,
title
:
'
图文问诊
'
,
desc
:
'
随时在线咨询
'
,
path
:
'
/patient/doctors?type=text
'
,
color
:
'
#52c41a
'
,
bg
:
'
#f6ffed
'
},
{
icon
:
<
RobotOutlined
/>,
title
:
'
AI智能问诊
'
,
desc
:
'
AI辅助症状分析
'
,
path
:
'
/patient/pre-consult
'
,
color
:
'
#722ed1
'
,
bg
:
'
#f9f0ff
'
},
{
icon
:
<
MedicineBoxOutlined
/>,
title
:
'
找医生
'
,
desc
:
'
按科室找专家
'
,
path
:
'
/patient/doctors
'
,
color
:
'
#fa8c16
'
,
bg
:
'
#fff7e6
'
},
{
icon
:
<
FileTextOutlined
/>,
title
:
'
电子处方
'
,
desc
:
'
查看历史处方
'
,
path
:
'
/patient/prescription
'
,
color
:
'
#13c2c2
'
,
bg
:
'
#e6fffb
'
},
{
icon
:
<
HeartOutlined
/>,
title
:
'
慢病管理
'
,
desc
:
'
慢性病续方
'
,
path
:
'
/patient/chronic
'
,
color
:
'
#eb2f96
'
,
bg
:
'
#fff0f6
'
},
];
const
quickLinks
=
[
{
icon
:
<
VideoCameraOutlined
/>,
title
:
'
我的问诊
'
,
path
:
'
/patient/consult
'
,
color
:
'
#1890ff
'
},
{
icon
:
<
ShoppingCartOutlined
/>,
title
:
'
药品配送
'
,
path
:
'
/patient/pharmacy/order
'
,
color
:
'
#fa8c16
'
},
{
icon
:
<
FolderOpenOutlined
/>,
title
:
'
健康档案
'
,
path
:
'
/patient/health-records
'
,
color
:
'
#52c41a
'
},
];
const
services
=
[
{
icon
:
<
VideoCameraOutlined
style=
{
{
fontSize
:
36
,
color
:
'
#1890ff
'
}
}
/>,
title
:
'
视频问诊
'
,
desc
:
'
与医生面对面视频交流
'
,
path
:
'
/patient/doctors?type=video
'
,
color
:
'
#e6f7ff
'
,
},
{
icon
:
<
MessageOutlined
style=
{
{
fontSize
:
36
,
color
:
'
#52c41a
'
}
}
/>,
title
:
'
图文问诊
'
,
desc
:
'
随时随地在线咨询
'
,
path
:
'
/patient/doctors?type=text
'
,
color
:
'
#f6ffed
'
,
},
{
icon
:
<
RobotOutlined
style=
{
{
fontSize
:
36
,
color
:
'
#722ed1
'
}
}
/>,
title
:
'
AI 智能问诊
'
,
desc
:
'
AI 辅助症状分析
'
,
path
:
'
/patient/pre-consult
'
,
color
:
'
#f9f0ff
'
,
},
{
icon
:
<
MedicineBoxOutlined
style=
{
{
fontSize
:
36
,
color
:
'
#fa8c16
'
}
}
/>,
title
:
'
找医生
'
,
desc
:
'
按科室找专家
'
,
path
:
'
/patient/doctors
'
,
color
:
'
#fff7e6
'
,
},
{
icon
:
<
FileTextOutlined
style=
{
{
fontSize
:
36
,
color
:
'
#13c2c2
'
}
}
/>,
title
:
'
电子处方
'
,
desc
:
'
查看历史处方
'
,
path
:
'
/patient/prescription
'
,
color
:
'
#e6fffb
'
,
},
{
icon
:
<
HeartOutlined
style=
{
{
fontSize
:
36
,
color
:
'
#eb2f96
'
}
}
/>,
title
:
'
慢病管理
'
,
desc
:
'
慢性病续方服务
'
,
path
:
'
/patient/chronic
'
,
color
:
'
#fff0f6
'
,
},
];
const
departments
=
[
'
内科
'
,
'
外科
'
,
'
妇科
'
,
'
儿科
'
,
'
皮肤科
'
,
'
眼科
'
,
'
耳鼻喉科
'
,
'
口腔科
'
,
'
骨科
'
,
'
神经内科
'
,
'
心血管内科
'
,
'
消化内科
'
,
];
const
departments
=
[
'
内科
'
,
'
外科
'
,
'
妇科
'
,
'
儿科
'
,
'
皮肤科
'
,
'
眼科
'
,
'
耳鼻喉科
'
,
'
口腔科
'
,
'
骨科
'
,
'
神经内科
'
,
'
心血管内科
'
,
'
消化内科
'
,
];
const
PatientHomePage
:
React
.
FC
=
()
=>
{
const
router
=
useRouter
();
return
(
<
div
className=
"max-w-[1200px] mx-auto space-y-3"
>
{
/* 顶部 Banner */
}
<
div
className=
"rounded-xl overflow-hidden"
style=
{
{
background
:
'
linear-gradient(135deg, #001529 0%, #003a8c 40%, #0050b3 70%, #1890ff 100%)
'
}
}
>
<
div
className=
"text-center px-6 py-8 relative"
>
<
div
className=
"absolute inset-0 overflow-hidden pointer-events-none"
>
<
div
className=
"absolute -top-10 -right-10 w-40 h-40 bg-white/5 rounded-full blur-2xl"
/>
<
div
className=
"absolute -bottom-10 -left-10 w-48 h-48 bg-blue-400/8 rounded-full blur-2xl"
/>
<
div
style=
{
{
maxWidth
:
1200
,
margin
:
'
0 auto
'
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
12
}
}
>
{
/* 顶部 Banner */
}
<
div
style=
{
{
borderRadius
:
12
,
overflow
:
'
hidden
'
,
position
:
'
relative
'
,
background
:
'
linear-gradient(135deg, #001529 0%, #003a8c 40%, #0050b3 70%, #1890ff 100%)
'
,
}
}
>
{
/* 装饰 */
}
<
div
style=
{
{
position
:
'
absolute
'
,
inset
:
0
,
overflow
:
'
hidden
'
,
pointerEvents
:
'
none
'
}
}
>
<
div
style=
{
{
position
:
'
absolute
'
,
top
:
-
40
,
right
:
-
40
,
width
:
160
,
height
:
160
,
background
:
'
rgba(255,255,255,0.05)
'
,
borderRadius
:
'
50%
'
,
filter
:
'
blur(40px)
'
}
}
/>
<
div
style=
{
{
position
:
'
absolute
'
,
bottom
:
-
40
,
left
:
-
40
,
width
:
192
,
height
:
192
,
background
:
'
rgba(96,165,250,0.08)
'
,
borderRadius
:
'
50%
'
,
filter
:
'
blur(40px)
'
}
}
/>
</
div
>
<
div
className=
"relative z-10"
>
<
h2
className=
"text-xl font-bold text-white mb-1"
>
互联网医院 · 在线问诊平台
</
h2
>
<
p
className=
"text-blue-200/80 text-sm mb-5"
>
专业医生在线服务,足不出户享受优质医疗
</
p
>
<
div
className=
"max-w-[440px] mx-auto"
>
<
div
style=
{
{
position
:
'
relative
'
,
zIndex
:
1
,
textAlign
:
'
center
'
,
padding
:
'
32px 24px
'
}
}
>
<
h2
style=
{
{
fontSize
:
20
,
fontWeight
:
700
,
color
:
'
#fff
'
,
margin
:
'
0 0 4px
'
}
}
>
互联网医院 · 在线问诊平台
</
h2
>
<
p
style=
{
{
fontSize
:
13
,
color
:
'
rgba(191,219,254,0.8)
'
,
margin
:
'
0 0 20px
'
}
}
>
专业医生在线服务,足不出户享受优质医疗
</
p
>
<
div
style=
{
{
maxWidth
:
440
,
margin
:
'
0 auto
'
}
}
>
<
Input
.
Search
placeholder=
"搜索医生、科室、疾病"
enterButton=
{
<><
SearchOutlined
/>
搜索
</>
}
onSearch=
{
(
value
)
=>
router
.
push
(
`/patient/doctors?keyword=${value}`
)
}
className=
"shadow-lg shadow-black/10
"
size=
"large
"
/>
</
div
>
<
div
className=
"flex justify-center gap-12 mt-5"
>
<
div
className=
"text-center"
>
<
div
className=
"text-white text-xl font-bold"
><
TeamOutlined
className=
"mr-1"
/>
1,280
</
div
>
<
div
className=
"text-blue-200/60 text-xs"
>
注册医生
</
div
>
</
div
>
<
div
className=
"text-center"
>
<
div
className=
"text-white text-xl font-bold"
><
SafetyCertificateOutlined
className=
"mr-1"
/>
56,800
</
div
>
<
div
className=
"text-blue-200/60 text-xs"
>
服务患者
</
div
>
</
div
>
<
div
className=
"text-center"
>
<
div
className=
"text-white text-xl font-bold"
><
ClockCircleOutlined
className=
"mr-1"
/>
3分钟
</
div
>
<
div
className=
"text-blue-200/60 text-xs"
>
平均响应
<
/
div
>
</
div
>
<
div
style=
{
{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
gap
:
48
,
marginTop
:
20
}
}
>
{
[
{
icon
:
<
TeamOutlined
/>,
value
:
'
1,280
'
,
label
:
'
注册医生
'
},
{
icon
:
<
SafetyCertificateOutlined
/>,
value
:
'
56,800
'
,
label
:
'
服务患者
'
},
{
icon
:
<
ClockCircleOutlined
/>,
value
:
'
3分钟
'
,
label
:
'
平均响应
'
},
].
map
((
stat
)
=>
(
<
div
key=
{
stat
.
label
}
style=
{
{
textAlign
:
'
center
'
}
}
>
<
div
style=
{
{
fontSize
:
20
,
fontWeight
:
700
,
color
:
'
#fff
'
}
}
>
<
span
style=
{
{
marginRight
:
4
}
}
>
{
stat
.
icon
}
</
span
>
{
stat
.
value
}
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
rgba(191,219,254,0.6)
'
}
}
>
{
stat
.
label
}
</
div
>
</
div
>
))
}
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 服务入口 */
}
<
Card
title=
{
<
span
className=
"text-sm font-semibold"
>
医疗服务
</
span
>
}
size=
"small"
>
<
Row
gutter=
{
[
8
,
8
]
}
>
{
services
.
map
((
service
,
index
)
=>
(
<
Col
xs=
{
12
}
sm=
{
8
}
md=
{
4
}
key=
{
index
}
>
<
div
className=
"text-center rounded-lg p-3 cursor-pointer transition-all hover:scale-105 hover:shadow-md"
style=
{
{
background
:
service
.
color
}
}
onClick=
{
()
=>
router
.
push
(
service
.
path
)
}
>
{
service
.
icon
}
<
div
className=
"mt-2"
>
<
Text
strong
className=
"text-[13px]!"
>
{
service
.
title
}
</
Text
>
{
/* 医疗服务入口 */
}
<
Card
title=
{
<
span
style=
{
{
fontSize
:
14
,
fontWeight
:
600
}
}
>
医疗服务
</
span
>
}
size=
"small"
>
<
Row
gutter=
{
[
8
,
8
]
}
>
{
services
.
map
((
service
,
index
)
=>
(
<
Col
xs=
{
12
}
sm=
{
8
}
md=
{
4
}
key=
{
index
}
>
<
div
style=
{
{
textAlign
:
'
center
'
,
borderRadius
:
8
,
padding
:
12
,
cursor
:
'
pointer
'
,
transition
:
'
all 0.2s
'
,
background
:
service
.
bg
,
}
}
onClick=
{
()
=>
router
.
push
(
service
.
path
)
}
onMouseEnter=
{
(
e
)
=>
{
e
.
currentTarget
.
style
.
transform
=
'
scale(1.03)
'
;
e
.
currentTarget
.
style
.
boxShadow
=
'
0 4px 12px rgba(0,0,0,0.08)
'
;
}
}
onMouseLeave=
{
(
e
)
=>
{
e
.
currentTarget
.
style
.
transform
=
'
scale(1)
'
;
e
.
currentTarget
.
style
.
boxShadow
=
'
none
'
;
}
}
>
<
div
style=
{
{
fontSize
:
32
,
color
:
service
.
color
,
marginBottom
:
8
}
}
>
{
service
.
icon
}
</
div
>
<
div
style=
{
{
fontSize
:
13
,
fontWeight
:
600
,
color
:
'
#1d2129
'
}
}
>
{
service
.
title
}
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#8c8c8c
'
}
}
>
{
service
.
desc
}
</
div
>
</
div
>
<
Text
type=
"secondary"
className=
"text-[11px]!"
>
{
service
.
desc
}
</
Text
>
</
Col
>
))
}
</
Row
>
</
Card
>
<
Row
gutter=
{
[
8
,
8
]
}
>
{
/* 快捷入口 */
}
<
Col
xs=
{
24
}
sm=
{
12
}
>
<
Card
title=
{
<
span
style=
{
{
fontSize
:
14
,
fontWeight
:
600
}
}
>
快捷入口
</
span
>
}
size=
"small"
style=
{
{
height
:
'
100%
'
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
8
}
}
>
{
quickLinks
.
map
((
link
)
=>
(
<
div
key=
{
link
.
title
}
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
space-between
'
,
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
:
10
}
}
>
<
div
style=
{
{
width
:
32
,
height
:
32
,
borderRadius
:
8
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
fontSize
:
16
,
color
:
link
.
color
,
background
:
`${link.color}15`
,
}
}
>
{
link
.
icon
}
</
div
>
<
span
style=
{
{
fontSize
:
13
,
fontWeight
:
500
,
color
:
'
#1d2129
'
}
}
>
{
link
.
title
}
</
span
>
</
div
>
<
RightOutlined
style=
{
{
fontSize
:
12
,
color
:
'
#bfbfbf
'
}
}
/>
</
div
>
))
}
</
div
>
</
Col
>
))
}
</
Row
>
</
Card
>
</
Card
>
</
Col
>
{
/* 科室导航 */
}
<
Card
title=
{
<
span
className=
"text-sm font-semibold"
>
科室导航
</
span
>
}
size=
"small"
>
<
div
className=
"flex flex-wrap gap-2"
>
{
departments
.
map
((
dept
)
=>
(
<
Button
key=
{
dept
}
size=
"small"
className=
"rounded-full!"
onClick=
{
()
=>
router
.
push
(
`/patient/doctors?department=${dept}`
)
}
>
{
dept
}
</
Button
>
))
}
<
Button
type=
"link"
size=
"small"
onClick=
{
()
=>
router
.
push
(
'
/patient/doctors
'
)
}
>
全部科室
>
</
Button
>
</
div
>
</
Card
>
{
/* 科室导航 */
}
<
Col
xs=
{
24
}
sm=
{
12
}
>
<
Card
title=
{
<
span
style=
{
{
fontSize
:
14
,
fontWeight
:
600
}
}
>
科室导航
</
span
>
}
size=
"small"
style=
{
{
height
:
'
100%
'
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
flexWrap
:
'
wrap
'
,
gap
:
8
}
}
>
{
departments
.
map
((
dept
)
=>
(
<
Tag
key=
{
dept
}
style=
{
{
cursor
:
'
pointer
'
,
borderRadius
:
16
,
padding
:
'
2px 12px
'
,
fontSize
:
12
}
}
onClick=
{
()
=>
router
.
push
(
`/patient/doctors?department=${dept}`
)
}
>
{
dept
}
</
Tag
>
))
}
<
Button
type=
"link"
size=
"small"
style=
{
{
fontSize
:
12
,
padding
:
0
}
}
onClick=
{
()
=>
router
.
push
(
'
/patient/doctors
'
)
}
>
全部科室
>
</
Button
>
</
div
>
</
Card
>
</
Col
>
</
Row
>
</
div
>
</
div
>
);
};
export
default
PatientHomePage
;
web/src/pages/patient/Profile/index.tsx
View file @
f1c4920f
...
...
@@ -8,28 +8,47 @@ import {
SafetyCertificateOutlined
,
EditOutlined
,
IdcardOutlined
,
VideoCameraOutlined
,
FileTextOutlined
,
FolderOpenOutlined
,
HeartOutlined
,
ShoppingCartOutlined
,
RobotOutlined
,
RightOutlined
,
}
from
'
@ant-design/icons
'
;
import
{
useRouter
}
from
'
next/navigation
'
;
import
{
useUserStore
}
from
'
../../../store/userStore
'
;
const
{
T
itle
,
T
ext
}
=
Typography
;
const
{
Text
}
=
Typography
;
const
PatientProfilePage
:
React
.
FC
=
()
=>
{
const
{
user
}
=
useUserStore
();
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
(
<
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"
>
<
Row
gutter=
{
12
}
align=
"middle"
>
<
Col
>
<
Avatar
size=
{
56
}
src=
{
user
?.
avatar
}
icon=
{
<
UserOutlined
/>
}
/>
</
Col
>
<
Col
flex=
"auto"
>
<
h4
className=
"text-sm font-bold m-0 mb-0.5"
>
{
user
?.
real_name
||
'
未设置姓名
'
}
</
h4
>
<
span
className=
"text-xs text-gray-400"
><
PhoneOutlined
className=
"mr-1"
/>
{
user
?.
phone
||
'
未绑定手机
'
}
</
span
>
<
div
className=
"mt-1"
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
16
}
}
>
<
Avatar
size=
{
56
}
src=
{
user
?.
avatar
}
icon=
{
<
UserOutlined
/>
}
style=
{
{
backgroundColor
:
'
#1890ff
'
,
flexShrink
:
0
}
}
/>
<
div
style=
{
{
flex
:
1
}
}
>
<
h4
style=
{
{
fontSize
:
15
,
fontWeight
:
700
,
margin
:
'
0 0 4px
'
,
color
:
'
#1d2129
'
}
}
>
{
user
?.
real_name
||
'
未设置姓名
'
}
</
h4
>
<
div
style=
{
{
fontSize
:
12
,
color
:
'
#8c8c8c
'
,
marginBottom
:
6
}
}
>
<
PhoneOutlined
style=
{
{
marginRight
:
4
}
}
/>
{
user
?.
phone
||
'
未绑定手机
'
}
</
div
>
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
4
}
}
>
{
user
?.
is_verified
?
(
<
Tag
icon=
{
<
SafetyCertificateOutlined
/>
}
color=
"success"
>
已实名
</
Tag
>
)
:
(
...
...
@@ -37,52 +56,91 @@ const PatientProfilePage: React.FC = () => {
)
}
<
Tag
color=
"blue"
>
患者
</
Tag
>
</
div
>
</
Col
>
<
Col
>
<
Button
size=
"small"
type=
"primary"
icon=
{
<
EditOutlined
/>
}
>
编辑资料
</
Button
>
</
Col
>
</
Row
>
</
div
>
<
Button
size=
"small"
type=
"primary"
icon=
{
<
EditOutlined
/>
}
>
编辑资料
</
Button
>
</
div
>
</
Card
>
{
/* 基本信息 */
}
<
Card
title=
{
<
span
className=
"text-xs font-semibold"
>
基本信息
</
span
>
}
size=
"small"
>
<
Descriptions
column=
{
2
}
size=
"small"
>
<
Descriptions
.
Item
label=
"用户ID"
>
{
user
?.
id
||
'
-
'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"手机"
>
{
user
?.
phone
||
'
-
'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"真实姓名"
>
{
user
?.
real_name
||
'
-
'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"注册时间"
>
{
user
?.
created_at
||
'
-
'
}
</
Descriptions
.
Item
>
</
Descriptions
>
</
Card
>
<
Row
gutter=
{
[
8
,
8
]
}
>
{
/* 基本信息 */
}
<
Col
xs=
{
24
}
sm=
{
12
}
>
<
Card
title=
{
<
span
style=
{
{
fontSize
:
13
,
fontWeight
:
600
}
}
>
基本信息
</
span
>
}
size=
"small"
style=
{
{
height
:
'
100%
'
}
}
>
<
Descriptions
column=
{
1
}
size=
"small"
>
<
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
?.
created_at
?
new
Date
(
user
.
created_at
).
toLocaleDateString
(
'
zh-CN
'
)
:
'
-
'
}
</
Descriptions
.
Item
>
</
Descriptions
>
</
Card
>
</
Col
>
{
/* 实名认证 */
}
<
Card
title=
{
<
span
className=
"text-xs font-semibold"
>
实名认证
</
span
>
}
size=
"small"
>
{
user
?.
is_verified
?
(
<
div
className=
"text-center py-4"
>
<
div
className=
"w-10 h-10 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-2"
>
<
SafetyCertificateOutlined
className=
"text-xl text-green-500"
/>
</
div
>
<
Text
strong
className=
"text-xs!"
>
已完成实名认证
</
Text
>
</
div
>
)
:
(
<
div
className=
"text-center py-4"
>
<
div
className=
"w-10 h-10 rounded-full bg-yellow-50 flex items-center justify-center mx-auto mb-2"
>
<
IdcardOutlined
className=
"text-xl text-yellow-500"
/>
</
div
>
<
div
className=
"text-xs text-gray-500 mb-2"
>
完成实名认证后可享受完整的线上问诊服务
</
div
>
<
Button
type=
"primary"
size=
"small"
>
去认证
</
Button
>
</
div
>
)
}
</
Card
>
{
/* 实名认证 */
}
<
Col
xs=
{
24
}
sm=
{
12
}
>
<
Card
title=
{
<
span
style=
{
{
fontSize
:
13
,
fontWeight
:
600
}
}
>
实名认证
</
span
>
}
size=
"small"
style=
{
{
height
:
'
100%
'
}
}
>
{
user
?.
is_verified
?
(
<
div
style=
{
{
textAlign
:
'
center
'
,
padding
:
'
16px 0
'
}
}
>
<
div
style=
{
{
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
style=
{
{
fontSize
:
13
,
fontWeight
:
600
,
color
:
'
#1d2129
'
}
}
>
已完成实名认证
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#8c8c8c
'
,
marginTop
:
4
}
}
>
可使用全部在线问诊服务
</
div
>
</
div
>
)
:
(
<
div
style=
{
{
textAlign
:
'
center
'
,
padding
:
'
16px 0
'
}
}
>
<
div
style=
{
{
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
>
<
Button
type=
"primary"
size=
"small"
>
去认证
</
Button
>
</
div
>
)
}
</
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
]
}
>
<
Col
span=
{
8
}
><
Button
block
size=
"small"
onClick=
{
()
=>
router
.
push
(
'
/patient/consult
'
)
}
>
就诊记录
</
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/chronic
'
)
}
>
慢病管理
</
Button
></
Col
>
<
Col
span=
{
8
}
><
Button
block
size=
"small"
onClick=
{
()
=>
router
.
push
(
'
/patient/pharmacy/order
'
)
}
>
药品配送
</
Button
></
Col
>
<
Col
span=
{
8
}
><
Button
block
size=
"small"
onClick=
{
()
=>
router
.
push
(
'
/patient/pre-consult
'
)
}
>
AI预问诊
</
Button
></
Col
>
{
serviceLinks
.
map
((
link
)
=>
(
<
Col
xs=
{
12
}
sm=
{
8
}
md=
{
4
}
key=
{
link
.
title
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
space-between
'
,
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
>
</
Card
>
</
div
>
...
...
@@ -90,4 +148,3 @@ const PatientProfilePage: React.FC = () => {
};
export
default
PatientProfilePage
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment