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
e26e5d42
Commit
e26e5d42
authored
Mar 06, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
45960530
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
453 additions
and
186 deletions
+453
-186
server/internal/service/user/service.go
server/internal/service/user/service.go
+11
-1
web/src/app/(auth)/login/page.tsx
web/src/app/(auth)/login/page.tsx
+142
-37
web/src/app/(main)/doctor/consult/AIPanel.tsx
web/src/app/(main)/doctor/consult/AIPanel.tsx
+24
-5
web/src/app/(main)/doctor/consult/ChatPanel.tsx
web/src/app/(main)/doctor/consult/ChatPanel.tsx
+13
-8
web/src/app/(main)/doctor/consult/PatientList.tsx
web/src/app/(main)/doctor/consult/PatientList.tsx
+131
-71
web/src/app/(main)/doctor/consult/page.tsx
web/src/app/(main)/doctor/consult/page.tsx
+65
-47
web/src/app/(main)/doctor/schedule/page.tsx
web/src/app/(main)/doctor/schedule/page.tsx
+7
-8
web/src/app/(main)/patient/chronic/page.tsx
web/src/app/(main)/patient/chronic/page.tsx
+60
-9
No files found.
server/internal/service/user/service.go
View file @
e26e5d42
...
@@ -122,7 +122,7 @@ func (s *Service) Register(ctx context.Context, req *RegisterRequest) (*LoginRes
...
@@ -122,7 +122,7 @@ func (s *Service) Register(ctx context.Context, req *RegisterRequest) (*LoginRes
return
nil
,
err
return
nil
,
err
}
}
//
如果是患者,创建患者
扩展信息
//
根据角色创建
扩展信息
if
role
==
"patient"
{
if
role
==
"patient"
{
patientProfile
:=
&
model
.
PatientProfile
{
patientProfile
:=
&
model
.
PatientProfile
{
ID
:
uuid
.
New
()
.
String
(),
ID
:
uuid
.
New
()
.
String
(),
...
@@ -131,6 +131,16 @@ func (s *Service) Register(ctx context.Context, req *RegisterRequest) (*LoginRes
...
@@ -131,6 +131,16 @@ func (s *Service) Register(ctx context.Context, req *RegisterRequest) (*LoginRes
if
err
:=
s
.
db
.
Create
(
patientProfile
)
.
Error
;
err
!=
nil
{
if
err
:=
s
.
db
.
Create
(
patientProfile
)
.
Error
;
err
!=
nil
{
fmt
.
Printf
(
"创建患者扩展信息失败: %v
\n
"
,
err
)
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
)
tokenPair
,
err
:=
utils
.
GenerateTokenPair
(
user
.
ID
,
user
.
Role
)
...
...
web/src/app/(auth)/login/page.tsx
View file @
e26e5d42
...
@@ -59,12 +59,23 @@ const LoginPage: React.FC = () => {
...
@@ -59,12 +59,23 @@ const LoginPage: React.FC = () => {
0%, 100% { transform: translateY(0px); }
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
50% { transform: translateY(-20px); }
}
}
@keyframes pulse-glow {
@keyframes float-reverse {
0%, 100% { box-shadow: 0 0 20px rgba(43,109,232,0.3); }
0%, 100% { transform: translateY(-10px) rotate(0deg); }
50% { box-shadow: 0 0 40px rgba(43,109,232,0.6); }
50% { transform: translateY(10px) rotate(3deg); }
}
@keyframes wave-move {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
@keyframes particle-float {
0% { transform: translateY(0) scale(1); opacity: 0; }
10% { opacity: 0.8; }
50% { opacity: 1; }
90% { opacity: 0.6; }
100% { transform: translateY(-200px) scale(0.4); opacity: 0; }
}
}
.login-container {
.login-container {
background: linear-gradient(135deg, #
0A1628 0%, #1A3B7A 25%, #2B6DE8 50%, #5B8FF9 75%, #2B6DE8
100%);
background: linear-gradient(135deg, #
1a3a6c 0%, #2B6DE8 40%, #5B8FF9 70%, #7eb4ff
100%);
background-size: 200% 200%;
background-size: 200% 200%;
animation: gradient-shift 15s ease infinite;
animation: gradient-shift 15s ease infinite;
}
}
...
@@ -77,78 +88,160 @@ const LoginPage: React.FC = () => {
...
@@ -77,78 +88,160 @@ const LoginPage: React.FC = () => {
transform: translateY(-4px);
transform: translateY(-4px);
background: rgba(255,255,255,0.15);
background: rgba(255,255,255,0.15);
}
}
.wave-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 100px;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.wave {
position: absolute;
bottom: 0;
width: 200%;
height: 100%;
animation: wave-move 10s linear infinite;
}
.wave:nth-child(2) {
animation-duration: 14s;
animation-direction: reverse;
opacity: 0.6;
bottom: 4px;
}
.wave:nth-child(3) {
animation-duration: 18s;
opacity: 0.35;
bottom: 8px;
}
.particle {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.3) 60%, transparent 100%);
box-shadow: 0 0 6px 2px rgba(255,255,255,0.3);
animation: particle-float 6s ease-in-out infinite;
}
@media (max-width: 1024px) {
@media (max-width: 1024px) {
.left-intro { display: none !important; }
.left-intro { display: none !important; }
.right-form { width: 100% !important; max-width: 440px !important; }
.right-form { width: 100% !important; max-width: 440px !important;
min-width: 0 !important;
}
}
}
`
}
</
style
>
`
}
</
style
>
<
div
className=
"login-container"
style=
{
{
<
div
className=
"login-container"
style=
{
{
position
:
'
fixed
'
,
inset
:
0
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
position
:
'
fixed
'
,
inset
:
0
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
overflow
:
'
auto
'
,
padding
:
24
,
overflow
:
'
hidden
'
,
padding
:
24
,
}
}
>
}
}
>
{
/* 动态背景装饰 */
}
{
/* 动态背景装饰 */
}
<
div
style=
{
{
position
:
'
absolute
'
,
inset
:
0
,
overflow
:
'
hidden
'
,
pointerEvents
:
'
none
'
}
}
>
<
div
style=
{
{
position
:
'
absolute
'
,
inset
:
0
,
overflow
:
'
hidden
'
,
pointerEvents
:
'
none
'
}
}
>
<
div
style=
{
{
position
:
'
absolute
'
,
top
:
'
15%
'
,
left
:
'
10%
'
,
width
:
400
,
height
:
400
,
background
:
'
radial-gradient(circle, rgba(91,143,249,0.15) 0%, transparent 70%)
'
,
animation
:
'
float 8s ease-in-out infinite
'
}
}
/>
{
/* 浮动光晕 - 柔和 */
}
<
div
style=
{
{
position
:
'
absolute
'
,
bottom
:
'
20%
'
,
right
:
'
15%
'
,
width
:
300
,
height
:
300
,
background
:
'
radial-gradient(circle, rgba(43,109,232,0.2) 0%, transparent 70%)
'
,
animation
:
'
float 10s ease-in-out infinite 2s
'
}
}
/>
<
div
style=
{
{
position
:
'
absolute
'
,
top
:
'
8%
'
,
left
:
'
3%
'
,
width
:
420
,
height
:
420
,
background
:
'
radial-gradient(circle, rgba(255,255,255,0.06) 0%, transparent 70%)
'
,
animation
:
'
float 8s ease-in-out infinite
'
}
}
/>
<
div
style=
{
{
position
:
'
absolute
'
,
top
:
'
50%
'
,
left
:
'
50%
'
,
width
:
500
,
height
:
500
,
background
:
'
radial-gradient(circle, rgba(255,255,255,0.03) 0%, transparent 70%)
'
,
animation
:
'
float 12s ease-in-out infinite 4s
'
}
}
/>
<
div
style=
{
{
position
:
'
absolute
'
,
bottom
:
'
10%
'
,
right
:
'
5%
'
,
width
:
350
,
height
:
350
,
background
:
'
radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%)
'
,
animation
:
'
float 10s ease-in-out infinite 2s
'
}
}
/>
<
div
style=
{
{
position
:
'
absolute
'
,
top
:
'
50%
'
,
left
:
'
45%
'
,
width
:
500
,
height
:
500
,
background
:
'
radial-gradient(circle, rgba(255,255,255,0.03) 0%, transparent 70%)
'
,
animation
:
'
float-reverse 12s ease-in-out infinite 3s
'
}
}
/>
{
/* 浮动粒子 - 更大更亮 */
}
{
[
{
left
:
'
5%
'
,
bottom
:
'
6%
'
,
size
:
8
,
delay
:
'
0s
'
,
dur
:
'
6s
'
},
{
left
:
'
14%
'
,
bottom
:
'
3%
'
,
size
:
6
,
delay
:
'
1.2s
'
,
dur
:
'
7s
'
},
{
left
:
'
24%
'
,
bottom
:
'
10%
'
,
size
:
10
,
delay
:
'
0.4s
'
,
dur
:
'
5.5s
'
},
{
left
:
'
35%
'
,
bottom
:
'
5%
'
,
size
:
7
,
delay
:
'
2.5s
'
,
dur
:
'
6.5s
'
},
{
left
:
'
48%
'
,
bottom
:
'
8%
'
,
size
:
9
,
delay
:
'
1s
'
,
dur
:
'
5.8s
'
},
{
left
:
'
58%
'
,
bottom
:
'
4%
'
,
size
:
6
,
delay
:
'
0.7s
'
,
dur
:
'
7.2s
'
},
{
left
:
'
70%
'
,
bottom
:
'
12%
'
,
size
:
10
,
delay
:
'
2s
'
,
dur
:
'
6.2s
'
},
{
left
:
'
80%
'
,
bottom
:
'
7%
'
,
size
:
8
,
delay
:
'
0.3s
'
,
dur
:
'
5.6s
'
},
{
left
:
'
90%
'
,
bottom
:
'
3%
'
,
size
:
7
,
delay
:
'
1.8s
'
,
dur
:
'
6.8s
'
},
{
left
:
'
10%
'
,
bottom
:
'
15%
'
,
size
:
5
,
delay
:
'
3.2s
'
,
dur
:
'
7.5s
'
},
{
left
:
'
42%
'
,
bottom
:
'
2%
'
,
size
:
8
,
delay
:
'
2.8s
'
,
dur
:
'
6s
'
},
{
left
:
'
65%
'
,
bottom
:
'
14%
'
,
size
:
6
,
delay
:
'
0.5s
'
,
dur
:
'
7s
'
},
{
left
:
'
52%
'
,
bottom
:
'
16%
'
,
size
:
5
,
delay
:
'
3.5s
'
,
dur
:
'
8s
'
},
{
left
:
'
78%
'
,
bottom
:
'
18%
'
,
size
:
7
,
delay
:
'
1.5s
'
,
dur
:
'
6.4s
'
},
{
left
:
'
20%
'
,
bottom
:
'
18%
'
,
size
:
6
,
delay
:
'
4s
'
,
dur
:
'
7.8s
'
},
].
map
((
p
,
i
)
=>
(
<
div
key=
{
i
}
className=
"particle"
style=
{
{
left
:
p
.
left
,
bottom
:
p
.
bottom
,
width
:
p
.
size
,
height
:
p
.
size
,
animationDelay
:
p
.
delay
,
animationDuration
:
p
.
dur
,
}
}
/>
))
}
{
/* 底部波浪 */
}
<
div
className=
"wave-container"
>
<
svg
className=
"wave"
viewBox=
"0 0 1440 100"
preserveAspectRatio=
"none"
style=
{
{
display
:
'
block
'
}
}
>
<
path
d=
"M0,50 C240,85 480,15 720,50 C960,85 1200,15 1440,50 C1680,85 1920,15 2160,50 C2400,85 2640,15 2880,50 L2880,100 L0,100 Z"
fill=
"rgba(255,255,255,0.08)"
/>
</
svg
>
<
svg
className=
"wave"
viewBox=
"0 0 1440 100"
preserveAspectRatio=
"none"
style=
{
{
display
:
'
block
'
}
}
>
<
path
d=
"M0,65 C240,35 480,85 720,65 C960,35 1200,85 1440,65 C1680,35 1920,85 2160,65 C2400,35 2640,85 2880,65 L2880,100 L0,100 Z"
fill=
"rgba(255,255,255,0.05)"
/>
</
svg
>
<
svg
className=
"wave"
viewBox=
"0 0 1440 100"
preserveAspectRatio=
"none"
style=
{
{
display
:
'
block
'
}
}
>
<
path
d=
"M0,75 C360,50 720,90 1080,75 C1440,50 1800,90 2160,75 C2520,50 2880,90 2880,75 L2880,100 L0,100 Z"
fill=
"rgba(255,255,255,0.03)"
/>
</
svg
>
</
div
>
</
div
>
</
div
>
<
div
style=
{
{
<
div
style=
{
{
position
:
'
relative
'
,
zIndex
:
10
,
display
:
'
flex
'
,
width
:
'
100%
'
,
maxWidth
:
1400
,
position
:
'
relative
'
,
zIndex
:
10
,
display
:
'
flex
'
,
width
:
'
100%
'
,
maxWidth
:
1400
,
m
inHeight
:
680
,
borderRadius
:
24
,
overflow
:
'
hidden
'
,
m
axHeight
:
'
calc(100vh - 48px)
'
,
borderRadius
:
24
,
overflow
:
'
hidden
'
,
boxShadow
:
'
0
30px 80px rgba(0,0,0,0.4
)
'
,
boxShadow
:
'
0
20px 60px rgba(0,0,0,0.3
)
'
,
}
}
>
}
}
>
{
/* 左侧系统介绍 - 2/3 */
}
{
/* 左侧系统介绍 - 2/3 */
}
<
div
className=
"left-intro"
style=
{
{
<
div
className=
"left-intro"
style=
{
{
flex
:
2
,
padding
:
60
,
color
:
'
#fff
'
,
flex
:
2
,
padding
:
'
40px 48px
'
,
color
:
'
#fff
'
,
background
:
'
linear-gradient(135deg, rgba(26,59,122,0.95) 0%, rgba(43,109,232,0.92) 100%)
'
,
background
:
'
linear-gradient(135deg, rgba(26,59,122,0.95) 0%, rgba(43,109,232,0.92) 100%)
'
,
backdropFilter
:
'
blur(30px)
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
justifyContent
:
'
space-between
'
,
backdropFilter
:
'
blur(30px)
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
justifyContent
:
'
space-between
'
,
overflow
:
'
auto
'
,
}
}
>
}
}
>
{
/* Logo & 标题 */
}
{
/* Logo & 标题 */
}
<
div
>
<
div
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
6
,
marginBottom
:
48
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
4
,
marginBottom
:
32
}
}
>
<
div
style=
{
{
<
div
style=
{
{
width
:
56
,
height
:
56
,
borderRadius
:
16
,
width
:
48
,
height
:
48
,
borderRadius
:
14
,
background
:
'
rgba(255,255,255,0.2)
'
,
backdropFilter
:
'
blur(10px)
'
,
background
:
'
rgba(255,255,255,0.2)
'
,
backdropFilter
:
'
blur(10px)
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
}
}
>
}
}
>
<
MedicineBoxOutlined
style=
{
{
fontSize
:
2
8
,
color
:
'
#fff
'
}
}
/>
<
MedicineBoxOutlined
style=
{
{
fontSize
:
2
4
,
color
:
'
#fff
'
}
}
/>
</
div
>
</
div
>
<
div
>
<
div
>
<
div
style=
{
{
fontSize
:
2
4
,
fontWeight
:
700
,
letterSpacing
:
1
}
}
>
互联网医院
</
div
>
<
div
style=
{
{
fontSize
:
2
2
,
fontWeight
:
700
,
letterSpacing
:
1
}
}
>
互联网医院
</
div
>
<
div
style=
{
{
fontSize
:
1
3
,
color
:
'
rgba(255,255,255,0.7)
'
,
marginTop
:
2
}
}
>
Internet Hospital Platform
</
div
>
<
div
style=
{
{
fontSize
:
1
2
,
color
:
'
rgba(255,255,255,0.7)
'
,
marginTop
:
2
}
}
>
Internet Hospital Platform
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
h1
style=
{
{
fontSize
:
42
,
fontWeight
:
700
,
lineHeight
:
1.3
,
marginBottom
:
2
0
}
}
>
<
h1
style=
{
{
fontSize
:
36
,
fontWeight
:
700
,
lineHeight
:
1.3
,
marginBottom
:
14
,
margin
:
0
}
}
>
智能医疗
<
br
/>
触手可及
智能医疗
<
br
/>
触手可及
</
h1
>
</
h1
>
<
p
style=
{
{
fontSize
:
1
6
,
color
:
'
rgba(255,255,255,0.8)
'
,
lineHeight
:
1.8
,
marginBottom
:
48
}
}
>
<
p
style=
{
{
fontSize
:
1
5
,
color
:
'
rgba(255,255,255,0.8)
'
,
lineHeight
:
1.8
,
marginBottom
:
28
,
marginTop
:
10
}
}
>
连接全国优质医疗资源,AI 赋能精准诊疗
<
br
/>
连接全国优质医疗资源,AI 赋能精准诊疗
<
br
/>
为您提供 7×24 小时在线医疗健康服务
为您提供 7×24 小时在线医疗健康服务
</
p
>
</
p
>
{
/* 数据统计 */
}
{
/* 数据统计 */
}
<
div
style=
{
{
display
:
'
grid
'
,
gridTemplateColumns
:
'
repeat(3, 1fr)
'
,
gap
:
24
,
marginBottom
:
4
8
}
}
>
<
div
style=
{
{
display
:
'
grid
'
,
gridTemplateColumns
:
'
repeat(3, 1fr)
'
,
gap
:
16
,
marginBottom
:
2
8
}
}
>
<
div
className=
"glass-card"
style=
{
{
padding
:
20
,
borderRadius
:
16
,
transition
:
'
all 0.3s
'
}
}
>
<
div
className=
"glass-card"
style=
{
{
padding
:
'
14px 16px
'
,
borderRadius
:
14
,
transition
:
'
all 0.3s
'
}
}
>
<
div
style=
{
{
fontSize
:
32
,
fontWeight
:
700
,
marginBottom
:
4
}
}
>
5000+
</
div
>
<
div
style=
{
{
fontSize
:
28
,
fontWeight
:
700
,
marginBottom
:
2
}
}
>
5000+
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
rgba(255,255,255,0.7)
'
}
}
>
执业医师
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
rgba(255,255,255,0.7)
'
}
}
>
执业医师
</
div
>
</
div
>
</
div
>
<
div
className=
"glass-card"
style=
{
{
padding
:
20
,
borderRadius
:
16
,
transition
:
'
all 0.3s
'
}
}
>
<
div
className=
"glass-card"
style=
{
{
padding
:
'
14px 16px
'
,
borderRadius
:
14
,
transition
:
'
all 0.3s
'
}
}
>
<
div
style=
{
{
fontSize
:
32
,
fontWeight
:
700
,
marginBottom
:
4
}
}
>
200万+
</
div
>
<
div
style=
{
{
fontSize
:
28
,
fontWeight
:
700
,
marginBottom
:
2
}
}
>
200万+
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
rgba(255,255,255,0.7)
'
}
}
>
服务患者
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
rgba(255,255,255,0.7)
'
}
}
>
服务患者
</
div
>
</
div
>
</
div
>
<
div
className=
"glass-card"
style=
{
{
padding
:
20
,
borderRadius
:
16
,
transition
:
'
all 0.3s
'
}
}
>
<
div
className=
"glass-card"
style=
{
{
padding
:
'
14px 16px
'
,
borderRadius
:
14
,
transition
:
'
all 0.3s
'
}
}
>
<
div
style=
{
{
fontSize
:
32
,
fontWeight
:
700
,
marginBottom
:
4
}
}
>
98%
</
div
>
<
div
style=
{
{
fontSize
:
28
,
fontWeight
:
700
,
marginBottom
:
2
}
}
>
98%
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
rgba(255,255,255,0.7)
'
}
}
>
满意度
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
rgba(255,255,255,0.7)
'
}
}
>
满意度
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 核心功能 */
}
{
/* 核心功能 */
}
<
div
style=
{
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
1
6
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
1
0
}
}
>
<
div
className=
"feature-card glass-card"
style=
{
{
<
div
className=
"feature-card glass-card"
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
6
,
padding
:
20
,
borderRadius
:
16
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
4
,
padding
:
'
14px 16px
'
,
borderRadius
:
14
,
transition
:
'
all 0.3s
'
,
cursor
:
'
pointer
'
,
transition
:
'
all 0.3s
'
,
cursor
:
'
pointer
'
,
}
}
>
}
}
>
<
div
style=
{
{
<
div
style=
{
{
...
@@ -166,7 +259,7 @@ const LoginPage: React.FC = () => {
...
@@ -166,7 +259,7 @@ const LoginPage: React.FC = () => {
</
div
>
</
div
>
<
div
className=
"feature-card glass-card"
style=
{
{
<
div
className=
"feature-card glass-card"
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
6
,
padding
:
20
,
borderRadius
:
16
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
4
,
padding
:
'
14px 16px
'
,
borderRadius
:
14
,
transition
:
'
all 0.3s
'
,
cursor
:
'
pointer
'
,
transition
:
'
all 0.3s
'
,
cursor
:
'
pointer
'
,
}
}
>
}
}
>
<
div
style=
{
{
<
div
style=
{
{
...
@@ -184,7 +277,7 @@ const LoginPage: React.FC = () => {
...
@@ -184,7 +277,7 @@ const LoginPage: React.FC = () => {
</
div
>
</
div
>
<
div
className=
"feature-card glass-card"
style=
{
{
<
div
className=
"feature-card glass-card"
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
6
,
padding
:
20
,
borderRadius
:
16
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
1
4
,
padding
:
'
14px 16px
'
,
borderRadius
:
14
,
transition
:
'
all 0.3s
'
,
cursor
:
'
pointer
'
,
transition
:
'
all 0.3s
'
,
cursor
:
'
pointer
'
,
}
}
>
}
}
>
<
div
style=
{
{
<
div
style=
{
{
...
@@ -213,10 +306,10 @@ const LoginPage: React.FC = () => {
...
@@ -213,10 +306,10 @@ const LoginPage: React.FC = () => {
{
/* 右侧登录表单 - 1/3 */
}
{
/* 右侧登录表单 - 1/3 */
}
<
div
className=
"right-form"
style=
{
{
<
div
className=
"right-form"
style=
{
{
flex
:
1
,
backgroundColor
:
'
#fff
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
flex
:
1
,
backgroundColor
:
'
#fff
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
justifyContent
:
'
center
'
,
padding
:
60
,
minWidth
:
42
0
,
justifyContent
:
'
center
'
,
padding
:
'
40px 48px
'
,
minWidth
:
40
0
,
}
}
>
}
}
>
<
div
style=
{
{
marginBottom
:
40
}
}
>
<
div
style=
{
{
marginBottom
:
32
}
}
>
<
h2
style=
{
{
fontSize
:
2
8
,
fontWeight
:
700
,
marginBottom
:
8
,
color
:
'
#1d2129
'
}
}
>
欢迎登录
</
h2
>
<
h2
style=
{
{
fontSize
:
2
6
,
fontWeight
:
700
,
marginBottom
:
6
,
color
:
'
#1d2129
'
}
}
>
欢迎登录
</
h2
>
<
p
style=
{
{
fontSize
:
14
,
color
:
'
#8c8c8c
'
,
margin
:
0
}
}
>
登录以使用完整的医疗服务功能
</
p
>
<
p
style=
{
{
fontSize
:
14
,
color
:
'
#8c8c8c
'
,
margin
:
0
}
}
>
登录以使用完整的医疗服务功能
</
p
>
</
div
>
</
div
>
...
@@ -256,23 +349,35 @@ const LoginPage: React.FC = () => {
...
@@ -256,23 +349,35 @@ const LoginPage: React.FC = () => {
</
Form
.
Item
>
</
Form
.
Item
>
</
Form
>
</
Form
>
<
div
style=
{
{
textAlign
:
'
center
'
,
marginTop
:
2
8
,
fontSize
:
14
}
}
>
<
div
style=
{
{
textAlign
:
'
center
'
,
marginTop
:
2
0
,
fontSize
:
14
}
}
>
<
span
style=
{
{
color
:
'
#8c8c8c
'
}
}
>
还没有账号?
</
span
>
<
span
style=
{
{
color
:
'
#8c8c8c
'
}
}
>
还没有账号?
</
span
>
<
Link
href=
"/register"
style=
{
{
marginLeft
:
6
,
fontWeight
:
600
,
color
:
'
#2B6DE8
'
}
}
>
<
Link
href=
"/register"
style=
{
{
marginLeft
:
6
,
fontWeight
:
600
,
color
:
'
#2B6DE8
'
}
}
>
立即注册
立即注册
</
Link
>
</
Link
>
</
div
>
</
div
>
<
div
style=
{
{
marginTop
:
40
,
paddingTop
:
24
,
textAlign
:
'
center
'
,
borderTop
:
'
1px solid #f0f0f0
'
}
}
>
<
div
style=
{
{
marginTop
:
28
,
paddingTop
:
16
,
textAlign
:
'
center
'
,
borderTop
:
'
1px solid #f0f0f0
'
,
}
}
>
<
p
style=
{
{
fontSize
:
12
,
color
:
'
#bfbfbf
'
,
margin
:
0
,
lineHeight
:
1.6
}
}
>
<
p
style=
{
{
fontSize
:
12
,
color
:
'
#bfbfbf
'
,
margin
:
0
,
lineHeight
:
1.6
}
}
>
登录即表示同意
登录即表示同意
<
a
onClick=
{
()
=>
message
.
info
(
'
协议内容完善中
'
)
}
style=
{
{
color
:
'
#2B6DE8
'
,
margin
:
'
0 4px
'
}
}
>
《用户服务协议》
</
a
>
<
a
onClick=
{
()
=>
message
.
info
(
'
协议内容完善中
'
)
}
style=
{
{
color
:
'
#2B6DE8
'
,
margin
:
'
0 4px
'
,
cursor
:
'
pointer
'
}
}
>
《用户服务协议》
</
a
>
和
和
<
a
onClick=
{
()
=>
message
.
info
(
'
协议内容完善中
'
)
}
style=
{
{
color
:
'
#2B6DE8
'
,
margin
:
'
0 4px
'
}
}
>
《隐私政策》
</
a
>
<
a
onClick=
{
()
=>
message
.
info
(
'
协议内容完善中
'
)
}
style=
{
{
color
:
'
#2B6DE8
'
,
margin
:
'
0 4px
'
,
cursor
:
'
pointer
'
}
}
>
《隐私政策》
</
a
>
</
p
>
</
p
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 底部版权信息 */
}
<
div
style=
{
{
position
:
'
absolute
'
,
bottom
:
16
,
left
:
0
,
right
:
0
,
zIndex
:
20
,
textAlign
:
'
center
'
,
color
:
'
rgba(255,255,255,0.4)
'
,
fontSize
:
12
,
letterSpacing
:
0.5
,
}
}
>
<
span
>
{
'
\
u00A9
'
}
2026 互联网医院 · 技术驱动健康未来
</
span
>
</
div
>
</
div
>
</
div
>
<
Modal
<
Modal
...
...
web/src/app/(main)/doctor/consult/AIPanel.tsx
View file @
e26e5d42
...
@@ -362,6 +362,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
...
@@ -362,6 +362,7 @@ const AIPanel: React.FC<AIPanelProps> = ({
display
:
'
flex
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
flexDirection
:
'
column
'
,
overflow
:
'
hidden
'
,
overflow
:
'
hidden
'
,
boxShadow
:
'
0 1px 3px rgba(0,0,0,0.04)
'
,
}
}
>
}
}
>
{
/* 标题 */
}
{
/* 标题 */
}
<
div
style=
{
{
<
div
style=
{
{
...
@@ -371,9 +372,17 @@ const AIPanel: React.FC<AIPanelProps> = ({
...
@@ -371,9 +372,17 @@ const AIPanel: React.FC<AIPanelProps> = ({
alignItems
:
'
center
'
,
alignItems
:
'
center
'
,
gap
:
8
,
gap
:
8
,
flexShrink
:
0
,
flexShrink
:
0
,
background
:
'
linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%)
'
,
}
}
>
}
}
>
<
RobotOutlined
style=
{
{
color
:
'
#52c41a
'
,
fontSize
:
16
}
}
/>
<
div
style=
{
{
<
span
style=
{
{
fontWeight
:
600
,
fontSize
:
14
}
}
>
AI 辅助
</
span
>
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
&&
(
{
hasActiveConsult
&&
(
<
Badge
status=
"processing"
style=
{
{
marginLeft
:
2
}
}
/>
<
Badge
status=
"processing"
style=
{
{
marginLeft
:
2
}
}
/>
)
}
)
}
...
@@ -470,9 +479,19 @@ const AIPanel: React.FC<AIPanelProps> = ({
...
@@ -470,9 +479,19 @@ const AIPanel: React.FC<AIPanelProps> = ({
]
}
]
}
/>
/>
)
:
(
)
:
(
<
div
style=
{
{
textAlign
:
'
center
'
,
paddingTop
:
60
}
}
>
<
div
style=
{
{
textAlign
:
'
center
'
,
paddingTop
:
60
,
padding
:
'
60px 24px
'
}
}
>
<
RobotOutlined
style=
{
{
fontSize
:
40
,
color
:
'
#d9d9d9
'
,
display
:
'
block
'
,
marginBottom
:
14
}
}
/>
<
div
style=
{
{
<
Text
type=
"secondary"
style=
{
{
fontSize
:
13
}
}
>
接诊后自动开始AI辅助
</
Text
>
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
>
)
}
)
}
</
div
>
</
div
>
...
...
web/src/app/(main)/doctor/consult/ChatPanel.tsx
View file @
e26e5d42
...
@@ -382,6 +382,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
...
@@ -382,6 +382,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
border
:
'
1px solid #E5E8EF
'
,
border
:
'
1px solid #E5E8EF
'
,
background
:
'
#fff
'
,
background
:
'
#fff
'
,
overflow
:
'
hidden
'
,
overflow
:
'
hidden
'
,
boxShadow
:
'
0 1px 3px rgba(0,0,0,0.04)
'
,
}
}
>
}
}
>
{
/* 顶部患者信息栏 */
}
{
/* 顶部患者信息栏 */
}
<
div
style=
{
{
<
div
style=
{
{
...
@@ -391,7 +392,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
...
@@ -391,7 +392,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
alignItems
:
'
center
'
,
alignItems
:
'
center
'
,
gap
:
10
,
gap
:
10
,
flexShrink
:
0
,
flexShrink
:
0
,
background
:
'
#fff
'
,
background
:
'
linear-gradient(135deg, #ffffff 0%, #f9fafb 100%)
'
,
}
}
>
}
}
>
{
activeConsult
?
(
{
activeConsult
?
(
<>
<>
...
@@ -429,8 +430,9 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
...
@@ -429,8 +430,9 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
{
activeConsult
.
started_at
&&
activeConsult
.
status
===
'
in_progress
'
&&
(
{
activeConsult
.
started_at
&&
activeConsult
.
status
===
'
in_progress
'
&&
(
<
div
style=
{
{
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
,
background
:
'
#f6ffed
'
,
border
:
'
1px solid #b7eb8f
'
,
borderRadius
:
6
,
background
:
'
linear-gradient(135deg, #f6ffed 0%, #e8ffd6 100%)
'
,
padding
:
'
2px 10px
'
,
fontSize
:
13
,
fontWeight
:
500
,
color
:
'
#52c41a
'
,
border
:
'
1px solid #b7eb8f
'
,
borderRadius
:
8
,
padding
:
'
3px 12px
'
,
fontSize
:
13
,
fontWeight
:
600
,
color
:
'
#389e0d
'
,
fontVariantNumeric
:
'
tabular-nums
'
,
fontVariantNumeric
:
'
tabular-nums
'
,
}
}
>
}
}
>
<
ClockCircleOutlined
style=
{
{
fontSize
:
12
}
}
/>
<
ClockCircleOutlined
style=
{
{
fontSize
:
12
}
}
/>
...
@@ -488,7 +490,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
...
@@ -488,7 +490,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
</
div
>
</
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
?
(
{
activeConsult
?
(
<>
<>
{
messages
.
length
===
0
&&
(
{
messages
.
length
===
0
&&
(
...
@@ -617,13 +619,16 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
...
@@ -617,13 +619,16 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
onClick=
{
handleSend
}
onClick=
{
handleSend
}
loading=
{
sending
}
loading=
{
sending
}
style=
{
{
style=
{
{
background
:
'
#52c41a
'
,
background
:
'
linear-gradient(135deg, #52c41a 0%, #73d13d 100%)
'
,
borderColor
:
'
#52c41a
'
,
borderColor
:
'
#52c41a
'
,
borderRadius
:
8
,
borderRadius
:
10
,
height
:
'
auto
'
,
height
:
'
auto
'
,
alignSelf
:
'
flex-end
'
,
alignSelf
:
'
flex-end
'
,
paddingBottom
:
8
,
paddingBottom
:
10
,
paddingTop
:
8
,
paddingTop
:
10
,
paddingLeft
:
16
,
paddingRight
:
16
,
boxShadow
:
'
0 2px 6px rgba(82, 196, 26, 0.3)
'
,
}
}
}
}
>
>
发送
发送
...
...
web/src/app/(main)/doctor/consult/PatientList.tsx
View file @
e26e5d42
...
@@ -4,7 +4,7 @@ import {
...
@@ -4,7 +4,7 @@ import {
UserOutlined
,
MessageOutlined
,
VideoCameraOutlined
,
UserOutlined
,
MessageOutlined
,
VideoCameraOutlined
,
CheckOutlined
,
CloseOutlined
,
ClockCircleOutlined
,
CheckOutlined
,
CloseOutlined
,
ClockCircleOutlined
,
AlertOutlined
,
ThunderboltOutlined
,
SearchOutlined
,
AlertOutlined
,
ThunderboltOutlined
,
SearchOutlined
,
MedicineBoxOutlined
,
ProfileOutlined
,
ProfileOutlined
,
}
from
'
@ant-design/icons
'
;
}
from
'
@ant-design/icons
'
;
import
type
{
PatientListItem
}
from
'
@/api/consult
'
;
import
type
{
PatientListItem
}
from
'
@/api/consult
'
;
...
@@ -47,108 +47,150 @@ const PatientCard: React.FC<{
...
@@ -47,108 +47,150 @@ const PatientCard: React.FC<{
const
isInProgress
=
patient
.
status
===
'
in_progress
'
;
const
isInProgress
=
patient
.
status
===
'
in_progress
'
;
const
isCompleted
=
patient
.
status
===
'
completed
'
;
const
isCompleted
=
patient
.
status
===
'
completed
'
;
const
avatarColor
=
isInProgress
?
'
#52c41a
'
:
isWaiting
?
'
#fa8c16
'
:
'
#d9d9d9
'
;
const
statusConfig
=
isInProgress
const
avatarBg
=
isInProgress
?
'
#f6ffed
'
:
isWaiting
?
'
#fff7e6
'
:
'
#fafafa
'
;
?
{
avatarBg
:
'
#F6FFED
'
,
avatarBorder
:
'
#52c41a
'
,
avatarIcon
:
'
#52c41a
'
}
const
riskColor
=
riskColorMap
[
patient
.
risk_level
]
||
'
#52c41a
'
;
:
isWaiting
?
{
avatarBg
:
'
#FFF7E6
'
,
avatarBorder
:
'
#fa8c16
'
,
avatarIcon
:
'
#fa8c16
'
}
:
{
avatarBg
:
'
#f5f5f5
'
,
avatarBorder
:
'
#d9d9d9
'
,
avatarIcon
:
'
#bfbfbf
'
};
const
riskColor
=
riskColorMap
[
patient
.
risk_level
]
||
'
#e8e8e8
'
;
const
cardBg
=
isSelected
?
'
linear-gradient(135deg, #F0FFF5 0%, #E6FFE6 100%)
'
:
isWaiting
?
'
#FFFCF5
'
:
'
#fff
'
;
return
(
return
(
<
div
<
div
onClick=
{
()
=>
!
isWaiting
&&
onSelect
()
}
onClick=
{
()
=>
!
isWaiting
&&
onSelect
()
}
style=
{
{
style=
{
{
padding
:
'
10px 12px
'
,
padding
:
'
10px 12px
'
,
background
:
isSelected
?
'
#F0FFF5
'
:
'
#fff
'
,
margin
:
'
4px 6px
'
,
borderLeft
:
`3px solid ${isSelected ? '#2B9E6E' : riskColor}`
,
borderRadius
:
10
,
borderBottom
:
'
1px solid #f5f5f5
'
,
background
:
cardBg
,
border
:
isSelected
?
'
1.5px solid #52c41a
'
:
'
1px solid #f0f0f0
'
,
boxShadow
:
isSelected
?
'
0 2px 8px rgba(82, 196, 26, 0.15)
'
:
'
0 1px 3px rgba(0,0,0,0.04)
'
,
cursor
:
isWaiting
?
'
default
'
:
'
pointer
'
,
cursor
:
isWaiting
?
'
default
'
:
'
pointer
'
,
transition
:
'
background 0.15s
'
,
transition
:
'
all 0.2s ease
'
,
position
:
'
relative
'
,
}
}
}
}
onMouseEnter=
{
e
=>
{
onMouseEnter=
{
e
=>
{
if
(
!
isSelected
&&
!
isWaiting
)
(
e
.
currentTarget
as
HTMLDivElement
).
style
.
background
=
'
#fafafa
'
;
if
(
!
isSelected
)
{
(
e
.
currentTarget
as
HTMLDivElement
).
style
.
boxShadow
=
'
0 3px 10px rgba(0,0,0,0.08)
'
;
(
e
.
currentTarget
as
HTMLDivElement
).
style
.
transform
=
'
translateY(-1px)
'
;
}
}
}
}
}
onMouseLeave=
{
e
=>
{
onMouseLeave=
{
e
=>
{
if
(
!
isSelected
)
(
e
.
currentTarget
as
HTMLDivElement
).
style
.
background
=
'
#fff
'
;
if
(
!
isSelected
)
{
(
e
.
currentTarget
as
HTMLDivElement
).
style
.
boxShadow
=
'
0 1px 3px rgba(0,0,0,0.04)
'
;
(
e
.
currentTarget
as
HTMLDivElement
).
style
.
transform
=
'
translateY(0)
'
;
}
}
}
}
}
>
>
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
9
,
alignItems
:
'
flex-start
'
}
}
>
{
/* 左侧风险条 */
}
<
div
style=
{
{
position
:
'
absolute
'
,
left
:
0
,
top
:
8
,
bottom
:
8
,
width
:
3
,
borderRadius
:
3
,
background
:
riskColor
,
}
}
/>
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
10
,
alignItems
:
'
flex-start
'
,
paddingLeft
:
4
}
}
>
{
/* Avatar */
}
{
/* Avatar */
}
<
div
style=
{
{
<
div
style=
{
{
width
:
3
4
,
height
:
34
,
borderRadius
:
'
50%
'
,
flexShrink
:
0
,
width
:
3
6
,
height
:
36
,
borderRadius
:
10
,
flexShrink
:
0
,
background
:
avatarBg
,
background
:
statusConfig
.
avatarBg
,
border
:
`
2px solid ${avatarColo
r}`
,
border
:
`
1.5px solid ${statusConfig.avatarBorde
r}`
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
}
}
>
}
}
>
<
UserOutlined
style=
{
{
fontSize
:
1
5
,
color
:
avatarColor
}
}
/>
<
UserOutlined
style=
{
{
fontSize
:
1
6
,
color
:
statusConfig
.
avatarIcon
}
}
/>
</
div
>
</
div
>
{
/* Info */
}
{
/* Info */
}
<
div
style=
{
{
flex
:
1
,
minWidth
:
0
}
}
>
<
div
style=
{
{
flex
:
1
,
minWidth
:
0
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
space-between
'
,
marginBottom
:
3
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
space-between
'
,
marginBottom
:
2
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
5
}
}
>
<
Text
<
Text
strong
strong
style=
{
{
fontSize
:
13
,
cursor
:
'
pointer
'
}
}
style=
{
{
fontSize
:
13
,
cursor
:
'
pointer
'
,
lineHeight
:
1.3
}
}
onClick=
{
e
=>
{
e
.
stopPropagation
();
onOpenProfile
?.();
}
}
onClick=
{
e
=>
{
e
.
stopPropagation
();
onOpenProfile
?.();
}
}
>
>
{
patient
.
patient_name
||
'
患者
'
}
{
patient
.
patient_name
||
'
患者
'
}
</
Text
>
</
Text
>
{
patient
.
is_revisit
&&
(
{
patient
.
is_revisit
&&
(
<
Tooltip
title=
{
`复诊 · 第${patient.visit_count + 1}次就诊`
}
>
<
Tooltip
title=
{
`复诊 · 第${patient.visit_count + 1}次就诊`
}
>
<
ThunderboltOutlined
style=
{
{
fontSize
:
11
,
color
:
'
#2B9E6E
'
}
}
/>
<
Tag
color=
"cyan"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 4px
'
,
borderRadius
:
4
}
}
>
<
ThunderboltOutlined
style=
{
{
marginRight
:
1
}
}
/>
复诊
</
Tag
>
</
Tooltip
>
</
Tooltip
>
)
}
)
}
</
div
>
</
div
>
<
div
style=
{
{
width
:
20
,
height
:
20
,
borderRadius
:
6
,
background
:
patient
.
type
===
'
video
'
?
'
#E6F7FF
'
:
'
#F6FFED
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
}
}
>
{
patient
.
type
===
'
video
'
{
patient
.
type
===
'
video
'
?
<
VideoCameraOutlined
style=
{
{
fontSize
:
12
,
color
:
'
#0891B2
'
}
}
/>
?
<
VideoCameraOutlined
style=
{
{
fontSize
:
11
,
color
:
'
#0891B2
'
}
}
/>
:
<
MessageOutlined
style=
{
{
fontSize
:
12
,
color
:
'
#52c41a
'
}
}
/>
:
<
MessageOutlined
style=
{
{
fontSize
:
11
,
color
:
'
#52c41a
'
}
}
/>
}
}
</
div
>
</
div
>
<
div
style=
{
{
fontSize
:
10
,
color
:
'
#2B9E6E
'
,
fontFamily
:
'
monospace
'
,
marginBottom
:
2
}
}
>
{
patient
.
serial_number
}
</
div
>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#8c8c8c
'
,
marginBottom
:
3
}
}
>
{
patient
.
patient_gender
&&
`${patient.patient_gender} · `
}
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#999
'
,
marginBottom
:
3
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
}
}
>
{
patient
.
patient_age
?
`${patient.patient_age}岁`
:
''
}
<
span
>
{
patient
.
visit_count
>
0
&&
` · 第${patient.visit_count + 1}次`
}
{
patient
.
patient_gender
&&
`${patient.patient_gender}`
}
{
patient
.
patient_age
?
` · ${patient.patient_age}岁`
:
''
}
</
span
>
{
patient
.
visit_count
>
0
&&
(
<
span
style=
{
{
color
:
'
#bbb
'
}
}
>
· 第
{
patient
.
visit_count
+
1
}
次
</
span
>
)
}
</
div
>
</
div
>
<
div
style=
{
{
<
div
style=
{
{
fontSize
:
1
1
,
color
:
'
#666
'
,
fontSize
:
1
2
,
color
:
'
#555
'
,
lineHeight
:
1.5
,
overflow
:
'
hidden
'
,
whiteSpace
:
'
nowrap
'
,
textOverflow
:
'
ellipsis
'
,
overflow
:
'
hidden
'
,
whiteSpace
:
'
nowrap
'
,
textOverflow
:
'
ellipsis
'
,
}
}
>
}
}
>
{
patient
.
chief_complaint
||
'
暂无主诉
'
}
{
patient
.
chief_complaint
||
'
暂无主诉
'
}
</
div
>
</
div
>
{
/* 标签区域
:过敏 + 慢病
*/
}
{
/* 标签区域 */
}
<
div
style=
{
{
display
:
'
flex
'
,
flexWrap
:
'
wrap
'
,
gap
:
3
,
marginTop
:
4
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
flexWrap
:
'
wrap
'
,
gap
:
3
,
marginTop
:
5
}
}
>
{
patient
.
allergy_history
&&
(
{
patient
.
allergy_history
&&
(
<
Tag
color=
"
red"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 4px
'
}
}
>
<
Tag
color=
"
error"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 5px
'
,
borderRadius
:
4
}
}
>
<
AlertOutlined
style=
{
{
marginRight
:
2
}
}
/>
过敏
<
AlertOutlined
style=
{
{
marginRight
:
2
}
}
/>
过敏
</
Tag
>
</
Tag
>
)
}
)
}
{
patient
.
chronic_diseases
?.
slice
(
0
,
2
).
map
(
d
=>
(
{
patient
.
chronic_diseases
?.
slice
(
0
,
2
).
map
(
d
=>
(
<
Tag
key=
{
d
}
color=
"
orange"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 4px
'
}
}
>
<
Tag
key=
{
d
}
color=
"
warning"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 5px
'
,
borderRadius
:
4
}
}
>
{
d
.
length
>
4
?
d
.
slice
(
0
,
4
)
+
'
..
'
:
d
}
{
d
.
length
>
4
?
d
.
slice
(
0
,
4
)
+
'
..
'
:
d
}
</
Tag
>
</
Tag
>
))
}
))
}
{
patient
.
chronic_diseases
?.
length
>
2
&&
(
{
patient
.
chronic_diseases
?.
length
>
2
&&
(
<
Tag
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0
4px
'
}
}
>
<
Tag
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0
5px
'
,
borderRadius
:
4
,
color
:
'
#999
'
}
}
>
+
{
patient
.
chronic_diseases
.
length
-
2
}
+
{
patient
.
chronic_diseases
.
length
-
2
}
</
Tag
>
</
Tag
>
)
}
)
}
{
patient
.
pre_consult_summary
&&
(
{
patient
.
pre_consult_summary
&&
(
<
Tag
color=
"
blue"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 4px
'
}
}
>
<
Tag
color=
"
processing"
style=
{
{
fontSize
:
10
,
lineHeight
:
'
16px
'
,
margin
:
0
,
padding
:
'
0 5px
'
,
borderRadius
:
4
}
}
>
<
ProfileOutlined
style=
{
{
marginRight
:
2
}
}
/>
预问诊
<
ProfileOutlined
style=
{
{
marginRight
:
2
}
}
/>
预问诊
</
Tag
>
</
Tag
>
)
}
)
}
</
div
>
</
div
>
{
isWaiting
&&
patient
.
waiting_seconds
>
0
&&
(
{
isWaiting
&&
patient
.
waiting_seconds
>
0
&&
(
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#fa8c16
'
,
marginTop
:
4
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
3
}
}
>
<
div
style=
{
{
<
ClockCircleOutlined
/>
fontSize
:
11
,
color
:
'
#fa8c16
'
,
marginTop
:
5
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
,
background
:
'
#FFF7E6
'
,
padding
:
'
2px 8px
'
,
borderRadius
:
4
,
width
:
'
fit-content
'
,
}
}
>
<
ClockCircleOutlined
style=
{
{
fontSize
:
10
}
}
/>
等待
{
formatWaitTime
(
patient
.
waiting_seconds
)
}
等待
{
formatWaitTime
(
patient
.
waiting_seconds
)
}
</
div
>
</
div
>
)
}
)
}
{
isCompleted
&&
(
{
isCompleted
&&
(
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#
8c8c8c
'
,
marginTop
:
4
}
}
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#
bbb
'
,
marginTop
:
5
}
}
>
已完成
{
patient
.
last_visit_date
?
` · ${patient.last_visit_date}`
:
''
}
已完成
{
patient
.
last_visit_date
?
` · ${patient.last_visit_date}`
:
''
}
</
div
>
</
div
>
)
}
)
}
...
@@ -157,21 +199,28 @@ const PatientCard: React.FC<{
...
@@ -157,21 +199,28 @@ const PatientCard: React.FC<{
{
/* 接诊/拒诊按钮 */
}
{
/* 接诊/拒诊按钮 */
}
{
isWaiting
&&
(
{
isWaiting
&&
(
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
6
,
marginTop
:
8
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
6
,
marginTop
:
8
,
paddingLeft
:
4
}
}
>
<
Button
<
Button
type=
"primary"
type=
"primary"
size=
"small"
size=
"small"
icon=
{
<
CheckOutlined
/>
}
icon=
{
<
CheckOutlined
/>
}
style=
{
{
flex
:
1
,
fontSize
:
11
,
height
:
26
}
}
style=
{
{
flex
:
1
,
fontSize
:
12
,
height
:
28
,
borderRadius
:
6
,
background
:
'
linear-gradient(135deg, #52c41a 0%, #73d13d 100%)
'
,
borderColor
:
'
#52c41a
'
,
}
}
onClick=
{
e
=>
{
e
.
stopPropagation
();
onAccept
();
}
}
onClick=
{
e
=>
{
e
.
stopPropagation
();
onAccept
();
}
}
>
>
接诊
接诊
</
Button
>
</
Button
>
<
Button
<
Button
danger
size=
"small"
size=
"small"
icon=
{
<
CloseOutlined
/>
}
icon=
{
<
CloseOutlined
/>
}
style=
{
{
flex
:
1
,
fontSize
:
11
,
height
:
26
}
}
style=
{
{
flex
:
1
,
fontSize
:
12
,
height
:
28
,
borderRadius
:
6
,
color
:
'
#ff4d4f
'
,
borderColor
:
'
#ffccc7
'
,
background
:
'
#fff2f0
'
,
}
}
onClick=
{
e
=>
{
e
.
stopPropagation
();
onReject
();
}
}
onClick=
{
e
=>
{
e
.
stopPropagation
();
onReject
();
}
}
>
>
拒诊
拒诊
...
@@ -189,7 +238,6 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -189,7 +238,6 @@ const PatientList: React.FC<PatientListProps> = ({
const
[
sortMode
,
setSortMode
]
=
useState
<
SortMode
>
(
'
default
'
);
const
[
sortMode
,
setSortMode
]
=
useState
<
SortMode
>
(
'
default
'
);
const
[
activeTab
,
setActiveTab
]
=
useState
(
'
waiting
'
);
const
[
activeTab
,
setActiveTab
]
=
useState
(
'
waiting
'
);
// 筛选
const
filtered
=
useMemo
(()
=>
{
const
filtered
=
useMemo
(()
=>
{
if
(
!
patients
)
return
[];
if
(
!
patients
)
return
[];
if
(
!
searchText
.
trim
())
return
patients
;
if
(
!
searchText
.
trim
())
return
patients
;
...
@@ -200,7 +248,6 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -200,7 +248,6 @@ const PatientList: React.FC<PatientListProps> = ({
);
);
},
[
patients
,
searchText
]);
},
[
patients
,
searchText
]);
// 排序
const
sorted
=
useMemo
(()
=>
{
const
sorted
=
useMemo
(()
=>
{
if
(
sortMode
===
'
default
'
)
return
filtered
;
if
(
sortMode
===
'
default
'
)
return
filtered
;
return
[...
filtered
].
sort
((
a
,
b
)
=>
{
return
[...
filtered
].
sort
((
a
,
b
)
=>
{
...
@@ -228,15 +275,25 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -228,15 +275,25 @@ const PatientList: React.FC<PatientListProps> = ({
const
renderCardList
=
(
list
:
PatientListItem
[])
=>
{
const
renderCardList
=
(
list
:
PatientListItem
[])
=>
{
if
(
list
.
length
===
0
)
{
if
(
list
.
length
===
0
)
{
return
(
return
(
<
div
style=
{
{
textAlign
:
'
center
'
,
padding
:
'
40px 0
'
,
color
:
'
#bbb
'
}
}
>
<
div
style=
{
{
textAlign
:
'
center
'
,
padding
:
'
48px 16px
'
,
color
:
'
#ccc
'
}
}
>
<
UserOutlined
style=
{
{
fontSize
:
28
,
display
:
'
block
'
,
marginBottom
:
8
}
}
/>
<
div
style=
{
{
<
div
style=
{
{
fontSize
:
12
}
}
>
width
:
52
,
height
:
52
,
borderRadius
:
14
,
margin
:
'
0 auto 12px
'
,
background
:
'
#f8f9fa
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
}
}
>
<
UserOutlined
style=
{
{
fontSize
:
24
,
color
:
'
#d9d9d9
'
}
}
/>
</
div
>
<
div
style=
{
{
fontSize
:
13
,
color
:
'
#bbb
'
}
}
>
{
searchText
?
'
没有匹配的患者
'
:
'
暂无患者
'
}
{
searchText
?
'
没有匹配的患者
'
:
'
暂无患者
'
}
</
div
>
</
div
>
<
div
style=
{
{
fontSize
:
11
,
color
:
'
#ddd
'
,
marginTop
:
4
}
}
>
{
searchText
?
'
试试其他关键词
'
:
'
新的候诊患者将显示在这里
'
}
</
div
>
</
div
>
</
div
>
);
);
}
}
return
list
.
map
(
p
=>
(
return
(
<
div
style=
{
{
padding
:
'
2px 0
'
}
}
>
{
list
.
map
(
p
=>
(
<
PatientCard
<
PatientCard
key=
{
p
.
consult_id
}
key=
{
p
.
consult_id
}
patient=
{
p
}
patient=
{
p
}
...
@@ -246,7 +303,9 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -246,7 +303,9 @@ const PatientList: React.FC<PatientListProps> = ({
onReject=
{
()
=>
onReject
(
p
)
}
onReject=
{
()
=>
onReject
(
p
)
}
onOpenProfile=
{
()
=>
onOpenProfile
?.(
p
.
patient_id
)
}
onOpenProfile=
{
()
=>
onOpenProfile
?.(
p
.
patient_id
)
}
/>
/>
));
))
}
</
div
>
);
};
};
return
(
return
(
...
@@ -258,9 +317,10 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -258,9 +317,10 @@ const PatientList: React.FC<PatientListProps> = ({
display
:
'
flex
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
flexDirection
:
'
column
'
,
overflow
:
'
hidden
'
,
overflow
:
'
hidden
'
,
boxShadow
:
'
0 1px 3px rgba(0,0,0,0.04)
'
,
}
}
>
}
}
>
{
/* Tabs */
}
{
/* Tabs */
}
<
div
style=
{
{
flexShrink
:
0
}
}
>
<
div
style=
{
{
flexShrink
:
0
,
borderBottom
:
'
1px solid #f0f0f0
'
}
}
>
<
Tabs
<
Tabs
activeKey=
{
activeTab
}
activeKey=
{
activeTab
}
onChange=
{
setActiveTab
}
onChange=
{
setActiveTab
}
...
@@ -271,7 +331,7 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -271,7 +331,7 @@ const PatientList: React.FC<PatientListProps> = ({
{
{
key
:
'
waiting
'
,
key
:
'
waiting
'
,
label
:
(
label
:
(
<
span
style=
{
{
fontSize
:
1
2
}
}
>
<
span
style=
{
{
fontSize
:
1
3
,
fontWeight
:
activeTab
===
'
waiting
'
?
600
:
400
}
}
>
候诊
<
Badge
count=
{
waiting
.
length
}
size=
"small"
style=
{
{
backgroundColor
:
'
#fa8c16
'
}
}
/>
候诊
<
Badge
count=
{
waiting
.
length
}
size=
"small"
style=
{
{
backgroundColor
:
'
#fa8c16
'
}
}
/>
</
span
>
</
span
>
),
),
...
@@ -279,7 +339,7 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -279,7 +339,7 @@ const PatientList: React.FC<PatientListProps> = ({
{
{
key
:
'
inProgress
'
,
key
:
'
inProgress
'
,
label
:
(
label
:
(
<
span
style=
{
{
fontSize
:
1
2
}
}
>
<
span
style=
{
{
fontSize
:
1
3
,
fontWeight
:
activeTab
===
'
inProgress
'
?
600
:
400
}
}
>
接诊
<
Badge
count=
{
inProgress
.
length
}
size=
"small"
style=
{
{
backgroundColor
:
'
#52c41a
'
}
}
/>
接诊
<
Badge
count=
{
inProgress
.
length
}
size=
"small"
style=
{
{
backgroundColor
:
'
#52c41a
'
}
}
/>
</
span
>
</
span
>
),
),
...
@@ -287,8 +347,8 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -287,8 +347,8 @@ const PatientList: React.FC<PatientListProps> = ({
{
{
key
:
'
completed
'
,
key
:
'
completed
'
,
label
:
(
label
:
(
<
span
style=
{
{
fontSize
:
1
2
}
}
>
<
span
style=
{
{
fontSize
:
1
3
,
fontWeight
:
activeTab
===
'
completed
'
?
600
:
400
}
}
>
已完成
<
Badge
count=
{
completed
.
length
}
size=
"small"
style=
{
{
backgroundColor
:
'
#8c8c8c
'
}
}
/>
完成
<
Badge
count=
{
completed
.
length
}
size=
"small"
style=
{
{
backgroundColor
:
'
#bbb
'
}
}
/>
</
span
>
</
span
>
),
),
},
},
...
@@ -297,21 +357,21 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -297,21 +357,21 @@ const PatientList: React.FC<PatientListProps> = ({
</
div
>
</
div
>
{
/* 搜索 + 排序栏 */
}
{
/* 搜索 + 排序栏 */
}
<
div
style=
{
{
padding
:
'
6
px 10px
'
,
borderBottom
:
'
1px solid #f5f5f5
'
,
flexShrink
:
0
}
}
>
<
div
style=
{
{
padding
:
'
8
px 10px
'
,
borderBottom
:
'
1px solid #f5f5f5
'
,
flexShrink
:
0
}
}
>
<
Input
<
Input
size=
"small"
size=
"small"
placeholder=
"搜索姓名
/
主诉..."
placeholder=
"搜索姓名
/
主诉..."
prefix=
{
<
SearchOutlined
style=
{
{
color
:
'
#b
bb
'
}
}
/>
}
prefix=
{
<
SearchOutlined
style=
{
{
color
:
'
#b
fbfbf
'
}
}
/>
}
value=
{
searchText
}
value=
{
searchText
}
onChange=
{
e
=>
setSearchText
(
e
.
target
.
value
)
}
onChange=
{
e
=>
setSearchText
(
e
.
target
.
value
)
}
allowClear
allowClear
style=
{
{
marginBottom
:
4
,
borderRadius
:
6
}
}
style=
{
{
marginBottom
:
6
,
borderRadius
:
8
}
}
/>
/>
<
Select
<
Select
size=
"small"
size=
"small"
value=
{
sortMode
}
value=
{
sortMode
}
onChange=
{
setSortMode
}
onChange=
{
setSortMode
}
style=
{
{
width
:
'
100%
'
,
fontSize
:
11
}
}
style=
{
{
width
:
'
100%
'
}
}
options=
{
[
options=
{
[
{
value
:
'
default
'
,
label
:
'
默认排序
'
},
{
value
:
'
default
'
,
label
:
'
默认排序
'
},
{
value
:
'
wait_time
'
,
label
:
'
等待时长优先
'
},
{
value
:
'
wait_time
'
,
label
:
'
等待时长优先
'
},
...
@@ -322,7 +382,7 @@ const PatientList: React.FC<PatientListProps> = ({
...
@@ -322,7 +382,7 @@ const PatientList: React.FC<PatientListProps> = ({
</
div
>
</
div
>
{
/* 列表区域 */
}
{
/* 列表区域 */
}
<
div
style=
{
{
flex
:
1
,
overflow
:
'
auto
'
}
}
>
<
div
style=
{
{
flex
:
1
,
overflow
:
'
auto
'
,
background
:
'
#fafbfc
'
}
}
>
{
renderCardList
(
currentList
)
}
{
renderCardList
(
currentList
)
}
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/app/(main)/doctor/consult/page.tsx
View file @
e26e5d42
...
@@ -287,74 +287,92 @@ const ConsultPage: React.FC = () => {
...
@@ -287,74 +287,92 @@ const ConsultPage: React.FC = () => {
{
label
:
'
AI使用率
'
,
value
:
workbenchStats
.
ai_usage_rate
.
toFixed
(
0
),
unit
:
'
%
'
,
icon
:
<
RobotOutlined
/>,
color
:
'
#0891B2
'
},
{
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
(
return
(
<
div
style=
{
{
height
:
'
calc(100vh - 72px)
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
1
6
,
padding
:
'
12px 16px
'
}
}
>
<
div
style=
{
{
height
:
'
calc(100vh - 72px)
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
1
2
,
padding
:
'
12px 16px
'
}
}
>
{
/* 顶部
标题 + 统计卡片
*/
}
{
/* 顶部
统计栏
*/
}
<
div
style=
{
{
flexShrink
:
0
}
}
>
<
div
style=
{
{
flexShrink
:
0
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
16
}
}
>
<
div
style=
{
{
display
:
'
flex
'
,
alignItems
:
'
stretch
'
,
gap
:
10
}
}
>
<
div
style=
{
{
marginLeft
:
'
auto
'
,
display
:
'
flex
'
,
gap
:
10
,
alignItems
:
'
center
'
}
}
>
{
statItems
.
map
(({
label
,
value
,
color
,
icon
,
unit
})
=>
(
{
statItems
.
map
(({
label
,
value
,
color
,
icon
,
unit
})
=>
(
<
div
<
div
key=
{
label
}
key=
{
label
}
style=
{
{
style=
{
{
background
:
'
#fff
'
,
flex
:
1
,
borderRadius
:
10
,
background
:
statBgMap
[
label
]
||
'
#fff
'
,
padding
:
'
8px 18px
'
,
borderRadius
:
12
,
border
:
'
1px solid #E5E8EF
'
,
padding
:
'
12px 16px
'
,
minWidth
:
100
,
border
:
'
1px solid rgba(0,0,0,0.04)
'
,
boxShadow
:
'
0 1px 4px 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)
'
;
}
}
}
}
>
>
<
div
style=
{
{
fontSize
:
11
,
color
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
,
marginBottom
:
2
}
}
>
<
div
style=
{
{
fontSize
:
12
,
color
:
'
#666
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
5
,
marginBottom
:
6
}
}
>
{
icon
}
{
React
.
cloneElement
(
icon
as
React
.
ReactElement
,
{
style
:
{
color
,
fontSize
:
14
}
})
}
{
label
}
{
label
}
</
div
>
</
div
>
<
div
style=
{
{
fontSize
:
22
,
fontWeight
:
700
,
color
,
lineHeight
:
1.2
}
}
>
<
div
style=
{
{
fontSize
:
26
,
fontWeight
:
700
,
color
,
lineHeight
:
1
,
fontVariantNumeric
:
'
tabular-nums
'
}
}
>
{
value
}
{
value
}
<
span
style=
{
{
fontSize
:
11
,
fontWeight
:
400
,
marginLeft
:
2
}
}
>
{
unit
}
</
span
>
<
span
style=
{
{
fontSize
:
12
,
fontWeight
:
400
,
marginLeft
:
3
,
color
:
'
#999
'
}
}
>
{
unit
}
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
))
}
))
}
<
div
<
div
onClick=
{
()
=>
setShowEfficiency
(
!
showEfficiency
)
}
onClick=
{
()
=>
setShowEfficiency
(
!
showEfficiency
)
}
style=
{
{
style=
{
{
cursor
:
'
pointer
'
,
padding
:
'
6px 10px
'
,
borderRadius
:
8
,
cursor
:
'
pointer
'
,
padding
:
'
12px 14px
'
,
borderRadius
:
12
,
background
:
showEfficiency
?
'
#F0FFF5
'
:
'
#f5f5f5
'
,
background
:
showEfficiency
?
'
linear-gradient(135deg, #F0FFF5 0%, #D9F7E0 100%)
'
:
'
#f8f9fa
'
,
border
:
`1px solid ${showEfficiency ? '#91d5f
f' : '#e8e8e8'}`
,
border
:
`1px solid ${showEfficiency ? '#b7eb8
f' : '#e8e8e8'}`
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
4
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
gap
:
4
,
fontSize
:
12
,
color
:
showEfficiency
?
'
#2B9E6E
'
:
'
#8c8c8c
'
,
fontSize
:
12
,
color
:
showEfficiency
?
'
#2B9E6E
'
:
'
#8c8c8c
'
,
transition
:
'
all 0.2s
'
,
transition
:
'
all 0.2s
'
,
minWidth
:
56
,
}
}
}
}
>
>
<
DashboardOutlined
/>
<
DashboardOutlined
style=
{
{
fontSize
:
16
}
}
/>
效率
<
span
style=
{
{
fontSize
:
11
}
}
>
效率
</
span
>
{
showEfficiency
?
<
UpOutlined
style=
{
{
fontSize
:
10
}
}
/>
:
<
DownOutlined
style=
{
{
fontSize
:
10
}
}
/>
}
{
showEfficiency
?
<
UpOutlined
style=
{
{
fontSize
:
9
}
}
/>
:
<
DownOutlined
style=
{
{
fontSize
:
9
}
}
/>
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 可折叠效率指标行 */
}
{
/* 可折叠效率指标行 */
}
{
showEfficiency
&&
efficiencyItems
.
length
>
0
&&
(
{
showEfficiency
&&
efficiencyItems
.
length
>
0
&&
(
<
div
style=
{
{
<
div
style=
{
{
display
:
'
flex
'
,
gap
:
8
,
marginTop
:
8
,
padding
:
'
8px 12
px
'
,
display
:
'
flex
'
,
gap
:
8
,
marginTop
:
8
,
padding
:
'
10px 16
px
'
,
background
:
'
#fafafa
'
,
borderRadius
:
10
,
border
:
'
1px solid #f0f0f0
'
,
background
:
'
linear-gradient(135deg, #fafbfc 0%, #f0f2f5 100%)
'
,
overflow
:
'
auto
'
,
borderRadius
:
12
,
border
:
'
1px solid #eee
'
,
}
}
>
}
}
>
{
efficiencyItems
.
map
(({
label
,
value
,
unit
,
icon
,
color
})
=>
(
{
efficiencyItems
.
map
(({
label
,
value
,
unit
,
icon
,
color
})
=>
(
<
div
<
div
key=
{
label
}
key=
{
label
}
style=
{
{
style=
{
{
flex
:
1
,
minWidth
:
90
,
textAlign
:
'
center
'
,
flex
:
1
,
minWidth
:
80
,
textAlign
:
'
center
'
,
padding
:
'
4px 8px
'
,
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
}
}
>
<
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
:
1
1
}
})
}
{
React
.
cloneElement
(
icon
as
React
.
ReactElement
,
{
style
:
{
color
,
fontSize
:
1
2
}
})
}
{
label
}
{
label
}
</
div
>
</
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
}
{
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
>
</
div
>
</
div
>
))
}
))
}
...
@@ -363,9 +381,9 @@ const ConsultPage: React.FC = () => {
...
@@ -363,9 +381,9 @@ const ConsultPage: React.FC = () => {
</
div
>
</
div
>
{
/* 主体三栏 */
}
{
/* 主体三栏 */
}
<
div
style=
{
{
flex
:
1
,
display
:
'
flex
'
,
gap
:
1
2
,
overflow
:
'
hidden
'
,
minHeight
:
0
}
}
>
<
div
style=
{
{
flex
:
1
,
display
:
'
flex
'
,
gap
:
1
0
,
overflow
:
'
hidden
'
,
minHeight
:
0
}
}
>
{
/* 左:患者列表 */
}
{
/* 左:患者列表 */
}
<
div
style=
{
{
width
:
2
72
,
flexShrink
:
0
}
}
>
<
div
style=
{
{
width
:
2
80
,
flexShrink
:
0
}
}
>
<
PatientList
<
PatientList
patients=
{
patientList
}
patients=
{
patientList
}
onSelectPatient=
{
handleSelectPatient
}
onSelectPatient=
{
handleSelectPatient
}
...
...
web/src/app/(main)/doctor/schedule/page.tsx
View file @
e26e5d42
'
use client
'
;
'
use client
'
;
import
React
,
{
useState
,
useEffect
}
from
'
react
'
;
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
{
DrawerForm
}
from
'
@ant-design/pro-components
'
;
import
{
PlusOutlined
,
DeleteOutlined
,
CalendarOutlined
}
from
'
@ant-design/icons
'
;
import
{
PlusOutlined
,
DeleteOutlined
,
CalendarOutlined
}
from
'
@ant-design/icons
'
;
import
dayjs
,
{
Dayjs
}
from
'
dayjs
'
;
import
dayjs
,
{
Dayjs
}
from
'
dayjs
'
;
...
@@ -10,6 +10,7 @@ import { doctorPortalApi, type ScheduleSlot } from '@/api/doctorPortal';
...
@@ -10,6 +10,7 @@ import { doctorPortalApi, type ScheduleSlot } from '@/api/doctorPortal';
const
{
Text
}
=
Typography
;
const
{
Text
}
=
Typography
;
const
DoctorSchedulePage
:
React
.
FC
=
()
=>
{
const
DoctorSchedulePage
:
React
.
FC
=
()
=>
{
const
{
message
,
modal
}
=
App
.
useApp
();
const
[
selectedDate
,
setSelectedDate
]
=
useState
<
Dayjs
>
(
dayjs
());
const
[
selectedDate
,
setSelectedDate
]
=
useState
<
Dayjs
>
(
dayjs
());
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
);
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
...
@@ -59,7 +60,7 @@ const DoctorSchedulePage: React.FC = () => {
...
@@ -59,7 +60,7 @@ const DoctorSchedulePage: React.FC = () => {
setIsModalOpen
(
true
);
setIsModalOpen
(
true
);
};
};
const
handleSaveSchedule
=
async
(
values
:
any
)
=>
{
const
handleSaveSchedule
=
async
(
values
:
any
)
:
Promise
<
boolean
>
=>
{
try
{
try
{
await
doctorPortalApi
.
createSchedule
([{
await
doctorPortalApi
.
createSchedule
([{
date
:
selectedDate
.
format
(
'
YYYY-MM-DD
'
),
date
:
selectedDate
.
format
(
'
YYYY-MM-DD
'
),
...
@@ -68,15 +69,16 @@ const DoctorSchedulePage: React.FC = () => {
...
@@ -68,15 +69,16 @@ const DoctorSchedulePage: React.FC = () => {
max_count
:
values
.
max_count
,
max_count
:
values
.
max_count
,
}]);
}]);
message
.
success
(
'
排班添加成功
'
);
message
.
success
(
'
排班添加成功
'
);
setIsModalOpen
(
false
);
fetchSchedule
();
fetchSchedule
();
return
true
;
}
catch
{
}
catch
{
message
.
error
(
'
添加排班失败
'
);
message
.
error
(
'
添加排班失败
'
);
return
false
;
}
}
};
};
const
handleDeleteSlot
=
(
slotId
:
string
)
=>
{
const
handleDeleteSlot
=
(
slotId
:
string
)
=>
{
M
odal
.
confirm
({
m
odal
.
confirm
({
title
:
'
确认删除
'
,
title
:
'
确认删除
'
,
content
:
'
确定要删除该排班吗?
'
,
content
:
'
确定要删除该排班吗?
'
,
onOk
:
async
()
=>
{
onOk
:
async
()
=>
{
...
@@ -159,10 +161,7 @@ const DoctorSchedulePage: React.FC = () => {
...
@@ -159,10 +161,7 @@ const DoctorSchedulePage: React.FC = () => {
title=
{
`添加排班 - ${selectedDate.format('YYYY-MM-DD')}`
}
title=
{
`添加排班 - ${selectedDate.format('YYYY-MM-DD')}`
}
open=
{
isModalOpen
}
open=
{
isModalOpen
}
onOpenChange=
{
setIsModalOpen
}
onOpenChange=
{
setIsModalOpen
}
onFinish=
{
async
(
values
:
any
)
=>
{
onFinish=
{
handleSaveSchedule
}
await
handleSaveSchedule
(
values
);
return
true
;
}
}
drawerProps=
{
{
placement
:
'
right
'
,
destroyOnClose
:
true
}
}
drawerProps=
{
{
placement
:
'
right
'
,
destroyOnClose
:
true
}
}
width=
{
480
}
width=
{
480
}
>
>
...
...
web/src/app/(main)/patient/chronic/page.tsx
View file @
e26e5d42
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
import
React
,
{
useState
}
from
'
react
'
;
import
React
,
{
useState
}
from
'
react
'
;
import
{
import
{
Card
,
Tabs
,
Button
,
Tag
,
Space
,
Form
,
Input
,
Select
,
App
,
Card
,
Button
,
Tag
,
Space
,
Form
,
Input
,
Select
,
App
,
DatePicker
,
Switch
,
Popconfirm
,
Typography
,
Row
,
Col
,
DatePicker
,
Switch
,
Popconfirm
,
Typography
,
Row
,
Col
,
TimePicker
,
Statistic
,
Empty
,
TimePicker
,
Statistic
,
Empty
,
}
from
'
antd
'
;
}
from
'
antd
'
;
...
@@ -566,17 +566,68 @@ const MetricsTab: React.FC = () => {
...
@@ -566,17 +566,68 @@ const MetricsTab: React.FC = () => {
// ========== 主页面 ==========
// ========== 主页面 ==========
const
PatientChronicPage
:
React
.
FC
=
()
=>
{
const
PatientChronicPage
:
React
.
FC
=
()
=>
{
const
tabItems
=
[
const
[
activeKey
,
setActiveKey
]
=
useState
(
'
records
'
);
{
key
:
'
records
'
,
label
:
<><
HeartOutlined
/>
慢病档案
</>,
children
:
<
ChronicRecordsTab
/>
},
{
key
:
'
renewals
'
,
label
:
<><
FileAddOutlined
/>
续方申请
</>,
children
:
<
RenewalsTab
/>
},
const
menuItems
=
[
{
key
:
'
reminders
'
,
label
:
<><
BellOutlined
/>
用药提醒
</>,
children
:
<
RemindersTab
/>
},
{
key
:
'
records
'
,
icon
:
<
HeartOutlined
/>,
label
:
'
慢病档案
'
},
{
key
:
'
metrics
'
,
label
:
<><
LineChartOutlined
/>
健康指标
</>,
children
:
<
MetricsTab
/>
},
{
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
(
return
(
<
div
style=
{
{
padding
:
'
12px 16px
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
gap
:
16
}
}
>
<
div
style=
{
{
padding
:
'
12px 16px
'
,
display
:
'
flex
'
,
gap
:
16
,
minHeight
:
'
calc(100vh - 120px)
'
}
}
>
<
Card
style=
{
{
borderRadius
:
12
,
border
:
'
1px solid #E5E8EF
'
}
}
>
{
/* 左侧导航 */
}
<
Tabs
items=
{
tabItems
}
/>
<
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
>
</
Card
>
</
div
>
</
div
>
);
);
...
...
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