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
f350fadd
Commit
f350fadd
authored
Feb 28, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
52e57c72
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
392 additions
and
98 deletions
+392
-98
web/src/app/(auth)/login/page.tsx
web/src/app/(auth)/login/page.tsx
+53
-27
web/src/app/(auth)/register/page.tsx
web/src/app/(auth)/register/page.tsx
+10
-10
web/src/app/globals.css
web/src/app/globals.css
+3
-0
web/src/app/providers.tsx
web/src/app/providers.tsx
+20
-16
web/src/pages/admin/Patients/index.tsx
web/src/pages/admin/Patients/index.tsx
+261
-0
web/src/pages/admin/Statistics/index.tsx
web/src/pages/admin/Statistics/index.tsx
+3
-3
web/src/pages/admin/Users/index.tsx
web/src/pages/admin/Users/index.tsx
+11
-11
web/src/pages/doctor/Certification/index.tsx
web/src/pages/doctor/Certification/index.tsx
+10
-10
web/src/pages/doctor/ChronicReview/index.tsx
web/src/pages/doctor/ChronicReview/index.tsx
+21
-21
No files found.
web/src/app/(auth)/login/page.tsx
View file @
f350fadd
...
@@ -44,7 +44,7 @@ const LoginPage: React.FC = () => {
...
@@ -44,7 +44,7 @@ const LoginPage: React.FC = () => {
key
:
'
phone
'
,
key
:
'
phone
'
,
label
:
'
手机号登录
'
,
label
:
'
手机号登录
'
,
children
:
(
children
:
(
<
Form
onFinish=
{
handleLogin
}
size=
"
middle
"
>
<
Form
onFinish=
{
handleLogin
}
size=
"
large"
className=
"mt-4
"
>
<
Form
.
Item
<
Form
.
Item
name=
"phone"
name=
"phone"
rules=
{
[
rules=
{
[
...
@@ -52,14 +52,22 @@ const LoginPage: React.FC = () => {
...
@@ -52,14 +52,22 @@ const LoginPage: React.FC = () => {
{
pattern
:
/^1
\d
{10}$/
,
message
:
'
请输入正确的手机号
'
},
{
pattern
:
/^1
\d
{10}$/
,
message
:
'
请输入正确的手机号
'
},
]
}
]
}
>
>
<
Input
prefix=
{
<
MobileOutlined
className=
"text-primary/60"
/>
}
placeholder=
"手机号"
/>
<
Input
prefix=
{
<
MobileOutlined
className=
"text-gray-400"
/>
}
placeholder=
"手机号"
className=
"h-11 rounded-lg"
/>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
name=
"password"
rules=
{
[{
required
:
true
,
message
:
'
请输入密码
'
}]
}
>
<
Form
.
Item
name=
"password"
rules=
{
[{
required
:
true
,
message
:
'
请输入密码
'
}]
}
>
<
Input
.
Password
prefix=
{
<
LockOutlined
className=
"text-primary/60"
/>
}
placeholder=
"密码"
/>
<
Input
.
Password
prefix=
{
<
LockOutlined
className=
"text-gray-400"
/>
}
placeholder=
"密码"
className=
"h-11 rounded-lg"
/>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
>
<
Form
.
Item
className=
"mb-0 mt-2"
>
<
Button
type=
"primary"
htmlType=
"submit"
loading=
{
loading
}
block
<
Button
type=
"primary"
htmlType=
"submit"
loading=
{
loading
}
block
className=
"h-1
0! rounded-lg! font-semibold! text-sm! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! hover:from-[#40a9ff]! hover:to-[#1890ff]! shadow-md! shadow-blue-200!
"
>
className=
"h-1
1 rounded-lg font-semibold text-base shadow-lg shadow-blue-200/50
"
>
登录
登录
</
Button
>
</
Button
>
</
Form
.
Item
>
</
Form
.
Item
>
...
@@ -70,16 +78,24 @@ const LoginPage: React.FC = () => {
...
@@ -70,16 +78,24 @@ const LoginPage: React.FC = () => {
key
:
'
username
'
,
key
:
'
username
'
,
label
:
'
账号登录
'
,
label
:
'
账号登录
'
,
children
:
(
children
:
(
<
Form
onFinish=
{
handleLogin
}
size=
"
middle
"
>
<
Form
onFinish=
{
handleLogin
}
size=
"
large"
className=
"mt-4
"
>
<
Form
.
Item
name=
"username"
rules=
{
[{
required
:
true
,
message
:
'
请输入用户名
'
}]
}
>
<
Form
.
Item
name=
"username"
rules=
{
[{
required
:
true
,
message
:
'
请输入用户名
'
}]
}
>
<
Input
prefix=
{
<
UserOutlined
className=
"text-primary/60"
/>
}
placeholder=
"用户名"
/>
<
Input
prefix=
{
<
UserOutlined
className=
"text-gray-400"
/>
}
placeholder=
"用户名"
className=
"h-11 rounded-lg"
/>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
name=
"password"
rules=
{
[{
required
:
true
,
message
:
'
请输入密码
'
}]
}
>
<
Form
.
Item
name=
"password"
rules=
{
[{
required
:
true
,
message
:
'
请输入密码
'
}]
}
>
<
Input
.
Password
prefix=
{
<
LockOutlined
className=
"text-primary/60"
/>
}
placeholder=
"密码"
/>
<
Input
.
Password
prefix=
{
<
LockOutlined
className=
"text-gray-400"
/>
}
placeholder=
"密码"
className=
"h-11 rounded-lg"
/>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
>
<
Form
.
Item
className=
"mb-0 mt-2"
>
<
Button
type=
"primary"
htmlType=
"submit"
loading=
{
loading
}
block
<
Button
type=
"primary"
htmlType=
"submit"
loading=
{
loading
}
block
className=
"h-1
0! rounded-lg! font-semibold! text-sm! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! hover:from-[#40a9ff]! hover:to-[#1890ff]! shadow-md! shadow-blue-200!
"
>
className=
"h-1
1 rounded-lg font-semibold text-base shadow-lg shadow-blue-200/50
"
>
登录
登录
</
Button
>
</
Button
>
</
Form
.
Item
>
</
Form
.
Item
>
...
@@ -89,7 +105,7 @@ const LoginPage: React.FC = () => {
...
@@ -89,7 +105,7 @@ const LoginPage: React.FC = () => {
];
];
return
(
return
(
<
div
className=
"fixed inset-0 flex items-center justify-center overflow-
hidden
"
<
div
className=
"fixed inset-0 flex items-center justify-center overflow-
auto py-8
"
style=
{
{
background
:
'
linear-gradient(135deg, #001529 0%, #003a8c 40%, #0050b3 70%, #1890ff 100%)
'
}
}
>
style=
{
{
background
:
'
linear-gradient(135deg, #001529 0%, #003a8c 40%, #0050b3 70%, #1890ff 100%)
'
}
}
>
{
/* 装饰粒子 */
}
{
/* 装饰粒子 */
}
<
div
className=
"absolute inset-0 overflow-hidden pointer-events-none"
>
<
div
className=
"absolute inset-0 overflow-hidden pointer-events-none"
>
...
@@ -99,8 +115,8 @@ const LoginPage: React.FC = () => {
...
@@ -99,8 +115,8 @@ const LoginPage: React.FC = () => {
</
div
>
</
div
>
<
div
className=
"relative z-10 flex w-full max-w-[960px] mx-4 rounded-2xl overflow-hidden shadow-2xl shadow-black/30"
>
<
div
className=
"relative z-10 flex w-full max-w-[960px] mx-4 rounded-2xl overflow-hidden shadow-2xl shadow-black/30"
>
{
/* 左侧品牌区 */
}
{
/* 左侧品牌区
- 仅在 md 及以上屏幕显示
*/
}
<
div
className=
"flex flex-col justify-between w-[420px] shrink-0 p-8 text-white"
<
div
className=
"
hidden md:
flex flex-col justify-between w-[420px] shrink-0 p-8 text-white"
style=
{
{
background
:
'
linear-gradient(180deg, rgba(0,58,140,0.9) 0%, rgba(0,80,179,0.85) 50%, rgba(24,144,255,0.8) 100%)
'
,
backdropFilter
:
'
blur(20px)
'
}
}
>
style=
{
{
background
:
'
linear-gradient(180deg, rgba(0,58,140,0.9) 0%, rgba(0,80,179,0.85) 50%, rgba(24,144,255,0.8) 100%)
'
,
backdropFilter
:
'
blur(20px)
'
}
}
>
<
div
>
<
div
>
<
div
className=
"flex items-center gap-3 mb-8"
>
<
div
className=
"flex items-center gap-3 mb-8"
>
...
@@ -155,25 +171,35 @@ const LoginPage: React.FC = () => {
...
@@ -155,25 +171,35 @@ const LoginPage: React.FC = () => {
</
div
>
</
div
>
{
/* 右侧登录区 */
}
{
/* 右侧登录区 */
}
<
div
className=
"flex-1 bg-white p-8 flex flex-col justify-center min-h-[520px]"
>
<
div
className=
"flex-1 bg-white p-8 md:p-10 flex flex-col justify-center min-h-[520px]"
>
<
div
className=
"mb-6"
>
{
/* 移动端 Logo(仅在左侧面板隐藏时显示) */
}
<
h3
className=
"text-xl font-bold text-gray-800 mb-1"
>
欢迎登录
</
h3
>
<
div
className=
"flex md:hidden items-center gap-2 mb-6"
>
<
div
className=
"w-9 h-9 rounded-xl bg-[#1890ff] flex items-center justify-center"
>
<
MedicineBoxOutlined
className=
"text-base text-white"
/>
</
div
>
<
div
className=
"text-base font-bold text-gray-800"
>
互联网医院
</
div
>
</
div
>
<
div
className=
"mb-8"
>
<
h3
className=
"text-2xl font-bold text-gray-800 mb-2"
>
欢迎登录
</
h3
>
<
p
className=
"text-sm text-gray-400"
>
登录以使用完整的医疗服务功能
</
p
>
<
p
className=
"text-sm text-gray-400"
>
登录以使用完整的医疗服务功能
</
p
>
</
div
>
</
div
>
<
Tabs
items=
{
tabItems
}
centered
size=
"small"
/>
<
div
className=
"w-full max-w-[320px] mx-auto"
>
<
Tabs
items=
{
tabItems
}
centered
size=
"middle"
className=
"login-tabs"
/>
<
div
className=
"text-center mt-4
text-sm"
>
<
div
className=
"text-center mt-6
text-sm"
>
<
span
className=
"text-gray-400"
>
还没有账号?
</
span
>
<
span
className=
"text-gray-400"
>
还没有账号?
</
span
>
<
Link
href=
"/register"
className=
"ml-1 font-medium
"
>
立即注册
</
Link
>
<
Link
href=
"/register"
className=
"ml-1 font-medium text-[#1890ff] hover:text-[#40a9ff]
"
>
立即注册
</
Link
>
</
div
>
</
div
>
<
div
className=
"mt-6 pt-4 border-t border-gray-100 text-center"
>
<
div
className=
"mt-8 pt-4 border-t border-gray-100 text-center"
>
<
p
className=
"text-xs text-gray-300"
>
<
p
className=
"text-xs text-gray-400"
>
登录即表示同意
登录即表示同意
<
a
href=
"#"
className=
"text-[#1890ff] mx-0.5"
>
《用户服务协议》
</
a
>
和
<
a
href=
"#"
className=
"text-[#1890ff] mx-0.5 hover:underline"
>
《用户服务协议》
</
a
>
和
<
a
href=
"#"
className=
"text-[#1890ff] mx-0.5"
>
《隐私政策》
</
a
>
<
a
href=
"#"
className=
"text-[#1890ff] mx-0.5 hover:underline"
>
《隐私政策》
</
a
>
</
p
>
</
p
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/app/(auth)/register/page.tsx
View file @
f350fadd
...
@@ -72,7 +72,7 @@ const RegisterPage: React.FC = () => {
...
@@ -72,7 +72,7 @@ const RegisterPage: React.FC = () => {
<
div
className=
"w-12 h-12 rounded-xl bg-linear-to-br from-[#1890ff] to-[#096dd9] flex items-center justify-center mx-auto mb-3"
>
<
div
className=
"w-12 h-12 rounded-xl bg-linear-to-br from-[#1890ff] to-[#096dd9] flex items-center justify-center mx-auto mb-3"
>
<
MedicineBoxOutlined
className=
"text-xl text-white"
/>
<
MedicineBoxOutlined
className=
"text-xl text-white"
/>
</
div
>
</
div
>
<
Title
level=
{
4
}
className=
"mb-1
! text-gray-800!
"
>
注册互联网医院
</
Title
>
<
Title
level=
{
4
}
className=
"mb-1
text-gray-800
"
>
注册互联网医院
</
Title
>
<
Text
type=
"secondary"
className=
"text-xs"
>
选择您的身份开始注册
</
Text
>
<
Text
type=
"secondary"
className=
"text-xs"
>
选择您的身份开始注册
</
Text
>
</
div
>
</
div
>
...
@@ -81,7 +81,7 @@ const RegisterPage: React.FC = () => {
...
@@ -81,7 +81,7 @@ const RegisterPage: React.FC = () => {
{
currentStep
===
0
&&
(
{
currentStep
===
0
&&
(
<
div
>
<
div
>
<
Paragraph
className=
"text-center mb-4 text-gray-400 text-sm
!
"
>
请选择您的注册身份
</
Paragraph
>
<
Paragraph
className=
"text-center mb-4 text-gray-400 text-sm"
>
请选择您的注册身份
</
Paragraph
>
<
Radio
.
Group
value=
{
registerType
}
onChange=
{
(
e
)
=>
setRegisterType
(
e
.
target
.
value
)
}
className=
"w-full"
>
<
Radio
.
Group
value=
{
registerType
}
onChange=
{
(
e
)
=>
setRegisterType
(
e
.
target
.
value
)
}
className=
"w-full"
>
<
Space
direction=
"vertical"
className=
"w-full"
>
<
Space
direction=
"vertical"
className=
"w-full"
>
<
div
className=
{
`p-4 rounded-xl border-2 cursor-pointer transition-all ${registerType === 'patient' ? 'border-[#1890ff] bg-primary-bg' : 'border-gray-200 hover:border-blue-200'}`
}
<
div
className=
{
`p-4 rounded-xl border-2 cursor-pointer transition-all ${registerType === 'patient' ? 'border-[#1890ff] bg-primary-bg' : 'border-gray-200 hover:border-blue-200'}`
}
...
@@ -92,8 +92,8 @@ const RegisterPage: React.FC = () => {
...
@@ -92,8 +92,8 @@ const RegisterPage: React.FC = () => {
<
UserOutlined
className=
"text-[#1890ff]"
/>
<
UserOutlined
className=
"text-[#1890ff]"
/>
</
div
>
</
div
>
<
div
>
<
div
>
<
Text
strong
className=
"text-sm
!
"
>
我是患者
</
Text
><
br
/>
<
Text
strong
className=
"text-sm"
>
我是患者
</
Text
><
br
/>
<
Text
type=
"secondary"
className=
"text-xs
!
"
>
在线问诊、找医生、视频通话
</
Text
>
<
Text
type=
"secondary"
className=
"text-xs"
>
在线问诊、找医生、视频通话
</
Text
>
</
div
>
</
div
>
</
Space
>
</
Space
>
</
Radio
>
</
Radio
>
...
@@ -106,8 +106,8 @@ const RegisterPage: React.FC = () => {
...
@@ -106,8 +106,8 @@ const RegisterPage: React.FC = () => {
<
MedicineBoxOutlined
className=
"text-[#1890ff]"
/>
<
MedicineBoxOutlined
className=
"text-[#1890ff]"
/>
</
div
>
</
div
>
<
div
>
<
div
>
<
Text
strong
className=
"text-sm
!
"
>
我是医生
</
Text
><
br
/>
<
Text
strong
className=
"text-sm"
>
我是医生
</
Text
><
br
/>
<
Text
type=
"secondary"
className=
"text-xs
!
"
>
在线接诊、管理排班、服务患者
</
Text
>
<
Text
type=
"secondary"
className=
"text-xs"
>
在线接诊、管理排班、服务患者
</
Text
>
</
div
>
</
div
>
</
Space
>
</
Space
>
</
Radio
>
</
Radio
>
...
@@ -115,7 +115,7 @@ const RegisterPage: React.FC = () => {
...
@@ -115,7 +115,7 @@ const RegisterPage: React.FC = () => {
</
Space
>
</
Space
>
</
Radio
.
Group
>
</
Radio
.
Group
>
<
Button
type=
"primary"
block
<
Button
type=
"primary"
block
className=
"mt-5
! h-10! rounded-lg! font-semibold! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! shadow-md! shadow-blue-200!
"
className=
"mt-5
h-10 rounded-lg font-semibold bg-linear-to-r from-[#1890ff] to-[#096dd9] border-none shadow-md shadow-blue-200
"
onClick=
{
()
=>
setCurrentStep
(
1
)
}
>
下一步
</
Button
>
onClick=
{
()
=>
setCurrentStep
(
1
)
}
>
下一步
</
Button
>
</
div
>
</
div
>
)
}
)
}
...
@@ -158,8 +158,8 @@ const RegisterPage: React.FC = () => {
...
@@ -158,8 +158,8 @@ const RegisterPage: React.FC = () => {
<
Form
.
Item
>
<
Form
.
Item
>
<
Space
className=
"w-full"
direction=
"vertical"
>
<
Space
className=
"w-full"
direction=
"vertical"
>
<
Button
type=
"primary"
htmlType=
"submit"
loading=
{
loading
}
block
<
Button
type=
"primary"
htmlType=
"submit"
loading=
{
loading
}
block
className=
"h-10
! rounded-lg! font-semibold! bg-linear-to-r! from-[#1890ff]! to-[#096dd9]! border-none! shadow-md! shadow-blue-200!
"
>
注册
</
Button
>
className=
"h-10
rounded-lg font-semibold bg-linear-to-r from-[#1890ff] to-[#096dd9] border-none shadow-md shadow-blue-200
"
>
注册
</
Button
>
<
Button
block
className=
"rounded-lg
!
"
onClick=
{
()
=>
setCurrentStep
(
0
)
}
>
上一步
</
Button
>
<
Button
block
className=
"rounded-lg"
onClick=
{
()
=>
setCurrentStep
(
0
)
}
>
上一步
</
Button
>
</
Space
>
</
Space
>
</
Form
.
Item
>
</
Form
.
Item
>
</
Form
>
</
Form
>
...
@@ -170,7 +170,7 @@ const RegisterPage: React.FC = () => {
...
@@ -170,7 +170,7 @@ const RegisterPage: React.FC = () => {
<
div
className=
"w-16 h-16 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4"
>
<
div
className=
"w-16 h-16 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4"
>
<
CheckCircleOutlined
className=
"text-3xl text-green-500"
/>
<
CheckCircleOutlined
className=
"text-3xl text-green-500"
/>
</
div
>
</
div
>
<
Title
level=
{
3
}
className=
"mt-4
!
"
>
注册成功!
</
Title
>
<
Title
level=
{
3
}
className=
"mt-4"
>
注册成功!
</
Title
>
<
Paragraph
type=
"secondary"
>
正在跳转到
{
registerType
===
'
doctor
'
?
'
医生工作台
'
:
'
首页
'
}
...</
Paragraph
>
<
Paragraph
type=
"secondary"
>
正在跳转到
{
registerType
===
'
doctor
'
?
'
医生工作台
'
:
'
首页
'
}
...</
Paragraph
>
</
div
>
</
div
>
)
}
)
}
...
...
web/src/app/globals.css
View file @
f350fadd
/* 将 antd CSS-in-JS 层声明在最前,使其优先级低于 Tailwind utilities */
@layer
antd
;
@import
"tailwindcss"
;
@import
"tailwindcss"
;
@theme
inline
{
@theme
inline
{
...
...
web/src/app/providers.tsx
View file @
f350fadd
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
{
ConfigProvider
,
App
as
AntdApp
}
from
'
antd
'
;
import
{
ConfigProvider
,
App
as
AntdApp
}
from
'
antd
'
;
import
{
StyleProvider
}
from
'
@ant-design/cssinjs
'
;
import
{
QueryClient
,
QueryClientProvider
}
from
'
@tanstack/react-query
'
;
import
{
QueryClient
,
QueryClientProvider
}
from
'
@tanstack/react-query
'
;
import
zhCN
from
'
antd/locale/zh_CN
'
;
import
zhCN
from
'
antd/locale/zh_CN
'
;
import
'
dayjs/locale/zh-cn
'
;
import
'
dayjs/locale/zh-cn
'
;
...
@@ -18,22 +19,25 @@ const queryClient = new QueryClient({
...
@@ -18,22 +19,25 @@ const queryClient = new QueryClient({
export
default
function
Providers
({
children
}:
{
children
:
React
.
ReactNode
})
{
export
default
function
Providers
({
children
}:
{
children
:
React
.
ReactNode
})
{
return
(
return
(
<
QueryClientProvider
client=
{
queryClient
}
>
<
QueryClientProvider
client=
{
queryClient
}
>
<
ConfigProvider
{
/* layer 模式:将 antd CSS-in-JS 注入到 @layer antd,使 Tailwind utilities 可以正常覆盖 antd 样式 */
}
locale=
{
zhCN
}
<
StyleProvider
layer
>
theme=
{
{
<
ConfigProvider
token
:
{
locale=
{
zhCN
}
colorPrimary
:
'
#1677ff
'
,
theme=
{
{
borderRadius
:
8
,
token
:
{
colorBgLayout
:
'
#f5f5f5
'
,
colorPrimary
:
'
#1677ff
'
,
colorLink
:
'
#1677ff
'
,
borderRadius
:
8
,
colorLinkHover
:
'
#4096ff
'
,
colorBgLayout
:
'
#f5f5f5
'
,
},
colorLink
:
'
#1677ff
'
,
}
}
colorLinkHover
:
'
#4096ff
'
,
>
},
<
AntdApp
>
}
}
{
children
}
>
</
AntdApp
>
<
AntdApp
>
</
ConfigProvider
>
{
children
}
</
AntdApp
>
</
ConfigProvider
>
</
StyleProvider
>
</
QueryClientProvider
>
</
QueryClientProvider
>
);
);
}
}
web/src/pages/admin/Patients/index.tsx
0 → 100644
View file @
f350fadd
'
use client
'
;
import
React
,
{
useState
,
useEffect
,
useCallback
}
from
'
react
'
;
import
{
Card
,
Table
,
Input
,
Select
,
Button
,
Space
,
Tag
,
Avatar
,
Modal
,
message
,
Typography
,
Form
,
InputNumber
,
Row
,
Col
,
}
from
'
antd
'
;
import
{
SearchOutlined
,
UserOutlined
,
ReloadOutlined
,
PlusOutlined
,
EditOutlined
,
DeleteOutlined
,
}
from
'
@ant-design/icons
'
;
import
type
{
ColumnsType
}
from
'
antd/es/table
'
;
import
{
adminApi
}
from
'
../../../api/admin
'
;
import
type
{
UserInfo
}
from
'
../../../api/user
'
;
const
{
Text
}
=
Typography
;
const
AdminPatientsPage
:
React
.
FC
=
()
=>
{
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
patients
,
setPatients
]
=
useState
<
UserInfo
[]
>
([]);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
page
,
setPage
]
=
useState
(
1
);
const
[
pageSize
,
setPageSize
]
=
useState
(
10
);
const
[
keyword
,
setKeyword
]
=
useState
(
''
);
const
[
statusFilter
,
setStatusFilter
]
=
useState
<
string
>
(
''
);
const
[
modalVisible
,
setModalVisible
]
=
useState
(
false
);
const
[
modalType
,
setModalType
]
=
useState
<
'
add
'
|
'
edit
'
>
(
'
add
'
);
const
[
editingRecord
,
setEditingRecord
]
=
useState
<
UserInfo
|
null
>
(
null
);
const
[
modalForm
]
=
Form
.
useForm
();
const
[
modalLoading
,
setModalLoading
]
=
useState
(
false
);
const
fetchPatients
=
useCallback
(
async
()
=>
{
setLoading
(
true
);
try
{
const
res
=
await
adminApi
.
getPatientList
({
keyword
,
status
:
statusFilter
,
page
,
page_size
:
pageSize
});
setPatients
(
res
.
data
.
list
||
[]);
setTotal
(
res
.
data
.
total
||
0
);
}
catch
{
message
.
error
(
'
获取患者列表失败
'
);
}
finally
{
setLoading
(
false
);
}
},
[
keyword
,
statusFilter
,
page
,
pageSize
]);
useEffect
(()
=>
{
fetchPatients
();
},
[
fetchPatients
]);
const
handleSearch
=
()
=>
{
setPage
(
1
);
fetchPatients
();
};
const
handleToggleStatus
=
(
record
:
UserInfo
)
=>
{
const
newStatus
=
record
.
status
===
'
active
'
?
'
disabled
'
:
'
active
'
;
Modal
.
confirm
({
title
:
`确认
${
newStatus
===
'
disabled
'
?
'
禁用
'
:
'
启用
'
}
该患者?`
,
content
:
`患者:
${
record
.
real_name
||
record
.
phone
}
`
,
onOk
:
async
()
=>
{
try
{
await
adminApi
.
updatePatientStatus
(
record
.
id
,
newStatus
);
message
.
success
(
'
操作成功
'
);
fetchPatients
();
}
catch
{
message
.
error
(
'
操作失败
'
);
}
},
});
};
const
handleResetPassword
=
(
record
:
UserInfo
)
=>
{
Modal
.
confirm
({
title
:
'
确认重置密码?
'
,
content
:
`将重置患者
${
record
.
real_name
||
record
.
phone
}
的密码为默认密码`
,
onOk
:
async
()
=>
{
try
{
await
adminApi
.
resetPatientPassword
(
record
.
id
);
message
.
success
(
'
密码已重置为 123456
'
);
}
catch
{
message
.
error
(
'
重置失败
'
);
}
},
});
};
const
handleAdd
=
()
=>
{
setModalType
(
'
add
'
);
setEditingRecord
(
null
);
modalForm
.
resetFields
();
setModalVisible
(
true
);
};
const
handleEdit
=
(
record
:
UserInfo
)
=>
{
setModalType
(
'
edit
'
);
setEditingRecord
(
record
);
modalForm
.
setFieldsValue
({
real_name
:
record
.
real_name
,
phone
:
record
.
phone
,
gender
:
record
.
gender
,
age
:
record
.
age
,
});
setModalVisible
(
true
);
};
const
handleDelete
=
(
record
:
UserInfo
)
=>
{
Modal
.
confirm
({
title
:
'
确认删除该患者?
'
,
content
:
`患者:
${
record
.
real_name
||
record
.
phone
}
,删除后不可恢复`
,
okType
:
'
danger
'
,
onOk
:
async
()
=>
{
try
{
await
adminApi
.
deletePatient
(
record
.
id
);
message
.
success
(
'
删除成功
'
);
fetchPatients
();
}
catch
{
message
.
error
(
'
删除失败
'
);
}
},
});
};
const
handleModalSubmit
=
async
()
=>
{
try
{
const
values
=
await
modalForm
.
validateFields
();
setModalLoading
(
true
);
if
(
modalType
===
'
add
'
)
{
await
adminApi
.
createPatient
({
phone
:
values
.
phone
,
password
:
values
.
password
||
'
123456
'
,
real_name
:
values
.
real_name
,
gender
:
values
.
gender
,
age
:
values
.
age
,
});
message
.
success
(
'
患者添加成功
'
);
}
else
if
(
editingRecord
)
{
await
adminApi
.
updatePatient
(
editingRecord
.
id
,
{
real_name
:
values
.
real_name
,
phone
:
values
.
phone
,
gender
:
values
.
gender
,
age
:
values
.
age
,
});
message
.
success
(
'
患者信息更新成功
'
);
}
setModalVisible
(
false
);
modalForm
.
resetFields
();
fetchPatients
();
}
catch
(
error
:
any
)
{
if
(
!
error
?.
errorFields
)
{
message
.
error
(
'
操作失败
'
);
}
}
finally
{
setModalLoading
(
false
);
}
};
const
columns
:
ColumnsType
<
UserInfo
>
=
[
{
title
:
'
患者
'
,
key
:
'
patient
'
,
render
:
(
_
,
record
)
=>
(
<
Space
>
<
Avatar
icon=
{
<
UserOutlined
/>
}
src=
{
record
.
avatar
}
style=
{
{
backgroundColor
:
'
#1890ff
'
}
}
/>
<
div
>
<
Text
strong
>
{
record
.
real_name
||
'
未填写
'
}
</
Text
>
<
br
/>
<
Text
type=
"secondary"
style=
{
{
fontSize
:
12
}
}
>
{
record
.
phone
}
</
Text
>
</
div
>
</
Space
>
),
},
{
title
:
'
性别
'
,
dataIndex
:
'
gender
'
,
key
:
'
gender
'
,
width
:
80
,
render
:
(
v
:
string
)
=>
v
||
'
-
'
},
{
title
:
'
年龄
'
,
dataIndex
:
'
age
'
,
key
:
'
age
'
,
width
:
80
,
render
:
(
v
:
number
)
=>
v
||
'
-
'
},
{
title
:
'
实名认证
'
,
dataIndex
:
'
is_verified
'
,
key
:
'
is_verified
'
,
render
:
(
verified
:
boolean
)
=>
<
Tag
color=
{
verified
?
'
green
'
:
'
default
'
}
>
{
verified
?
'
已认证
'
:
'
未认证
'
}
</
Tag
>,
},
{
title
:
'
状态
'
,
dataIndex
:
'
status
'
,
key
:
'
status
'
,
render
:
(
status
:
string
)
=>
<
Tag
color=
{
status
===
'
active
'
?
'
green
'
:
'
red
'
}
>
{
status
===
'
active
'
?
'
正常
'
:
'
已禁用
'
}
</
Tag
>,
},
{
title
:
'
注册时间
'
,
dataIndex
:
'
created_at
'
,
key
:
'
created_at
'
,
render
:
(
time
:
string
)
=>
time
?
new
Date
(
time
).
toLocaleString
(
'
zh-CN
'
)
:
'
-
'
,
},
{
title
:
'
操作
'
,
key
:
'
action
'
,
width
:
260
,
render
:
(
_
,
record
)
=>
(
<
Space
size=
{
0
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EditOutlined
/>
}
onClick=
{
()
=>
handleEdit
(
record
)
}
>
编辑
</
Button
>
<
Button
type=
"link"
size=
"small"
danger=
{
record
.
status
===
'
active
'
}
onClick=
{
()
=>
handleToggleStatus
(
record
)
}
>
{
record
.
status
===
'
active
'
?
'
禁用
'
:
'
启用
'
}
</
Button
>
<
Button
type=
"link"
size=
"small"
onClick=
{
()
=>
handleResetPassword
(
record
)
}
>
重置密码
</
Button
>
<
Button
type=
"link"
size=
"small"
danger
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
handleDelete
(
record
)
}
>
删除
</
Button
>
</
Space
>
),
},
];
return
(
<
div
className=
"space-y-2"
>
<
div
className=
"flex items-center justify-between"
>
<
h4
className=
"text-sm font-bold text-gray-800 m-0"
>
患者管理
</
h4
>
<
span
className=
"text-xs text-gray-400"
>
共
{
total
}
条记录
</
span
>
</
div
>
<
Card
size=
"small"
>
<
div
className=
"flex flex-wrap items-center justify-between gap-2"
>
<
div
className=
"flex flex-wrap items-center gap-2"
>
<
Input
placeholder=
"搜索姓名/手机号"
prefix=
{
<
SearchOutlined
/>
}
value=
{
keyword
}
onChange=
{
(
e
)
=>
setKeyword
(
e
.
target
.
value
)
}
onPressEnter=
{
handleSearch
}
style=
{
{
width
:
180
}
}
size=
"small"
/>
<
Select
placeholder=
"状态"
allowClear
size=
"small"
value=
{
statusFilter
||
undefined
}
onChange=
{
(
v
)
=>
setStatusFilter
(
v
||
''
)
}
style=
{
{
width
:
100
}
}
options=
{
[{
label
:
'
正常
'
,
value
:
'
active
'
},
{
label
:
'
已禁用
'
,
value
:
'
disabled
'
}]
}
/>
<
Button
size=
"small"
type=
"primary"
icon=
{
<
SearchOutlined
/>
}
onClick=
{
handleSearch
}
>
搜索
</
Button
>
<
Button
size=
"small"
icon=
{
<
ReloadOutlined
/>
}
onClick=
{
()
=>
{
setKeyword
(
''
);
setStatusFilter
(
''
);
setPage
(
1
);
}
}
>
重置
</
Button
>
</
div
>
<
Button
size=
"small"
type=
"primary"
icon=
{
<
PlusOutlined
/>
}
onClick=
{
handleAdd
}
>
添加患者
</
Button
>
</
div
>
</
Card
>
<
Card
size=
"small"
>
<
Table
columns=
{
columns
}
dataSource=
{
patients
}
rowKey=
"id"
loading=
{
loading
}
size=
"small"
pagination=
{
{
current
:
page
,
pageSize
,
total
,
showTotal
:
(
t
)
=>
`共 ${t} 条`
,
size
:
'
small
'
,
onChange
:
(
p
,
ps
)
=>
{
setPage
(
p
);
setPageSize
(
ps
);
},
}
}
/>
</
Card
>
<
Modal
title=
{
modalType
===
'
add
'
?
'
添加患者
'
:
'
编辑患者
'
}
open=
{
modalVisible
}
onOk=
{
handleModalSubmit
}
onCancel=
{
()
=>
setModalVisible
(
false
)
}
confirmLoading=
{
modalLoading
}
destroyOnClose
width=
{
440
}
>
<
Form
form=
{
modalForm
}
layout=
"vertical"
size=
"small"
>
<
Form
.
Item
name=
"real_name"
label=
"姓名"
rules=
{
[{
required
:
true
,
message
:
'
请输入姓名
'
}]
}
>
<
Input
placeholder=
"患者姓名"
/>
</
Form
.
Item
>
<
Form
.
Item
name=
"phone"
label=
"手机号"
rules=
{
[{
required
:
true
,
message
:
'
请输入手机号
'
}]
}
>
<
Input
placeholder=
"手机号"
/>
</
Form
.
Item
>
{
modalType
===
'
add
'
&&
(
<
Form
.
Item
name=
"password"
label=
"初始密码"
>
<
Input
.
Password
placeholder=
"默认 123456"
/>
</
Form
.
Item
>
)
}
<
Row
gutter=
{
12
}
>
<
Col
span=
{
12
}
>
<
Form
.
Item
name=
"gender"
label=
"性别"
>
<
Select
placeholder=
"选择性别"
allowClear
options=
{
[{
label
:
'
男
'
,
value
:
'
男
'
},
{
label
:
'
女
'
,
value
:
'
女
'
}]
}
/>
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
name=
"age"
label=
"年龄"
>
<
InputNumber
style=
{
{
width
:
'
100%
'
}
}
min=
{
1
}
max=
{
150
}
placeholder=
"年龄"
/>
</
Form
.
Item
>
</
Col
>
</
Row
>
</
Form
>
</
Modal
>
</
div
>
);
};
export
default
AdminPatientsPage
;
web/src/pages/admin/Statistics/index.tsx
View file @
f350fadd
...
@@ -13,7 +13,7 @@ const AdminStatisticsPage: React.FC = () => {
...
@@ -13,7 +13,7 @@ const AdminStatisticsPage: React.FC = () => {
<
h4
className=
"text-sm font-bold text-gray-800 m-0"
>
数据统计
</
h4
>
<
h4
className=
"text-sm font-bold text-gray-800 m-0"
>
数据统计
</
h4
>
<
div
className=
"flex items-center gap-2"
>
<
div
className=
"flex items-center gap-2"
>
<
Select
defaultValue=
"month"
size=
"small"
style=
{
{
width
:
100
}
}
<
Select
defaultValue=
"month"
size=
"small"
style=
{
{
width
:
100
}
}
options=
{
[{
label
:
'
??, value:
'
week
'
}, { label:
'
?
0
?,
value
:
'
month
'
},
{
label
:
'
?0?, value:
'
quarter
'
}, { label:
'
??
,
value
:
'
year
'
}]
}
/>
options=
{
[{
label
:
'
本周
'
,
value
:
'
week
'
},
{
label
:
'
本月
'
,
value
:
'
month
'
},
{
label
:
'
本季
'
,
value
:
'
quarter
'
},
{
label
:
'
本年
'
,
value
:
'
year
'
}]
}
/>
<
DatePicker
.
RangePicker
size=
"small"
/>
<
DatePicker
.
RangePicker
size=
"small"
/>
</
div
>
</
div
>
</
div
>
</
div
>
...
@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => {
...
@@ -24,11 +24,11 @@ const AdminStatisticsPage: React.FC = () => {
suffix=
{
<
span
className=
"text-xs text-green-500"
><
RiseOutlined
/>
12.5%
</
span
>
}
/></
Card
>
suffix=
{
<
span
className=
"text-xs text-green-500"
><
RiseOutlined
/>
12.5%
</
span
>
}
/></
Card
>
</
Col
>
</
Col
>
<
Col
xs=
{
12
}
sm=
{
6
}
>
<
Col
xs=
{
12
}
sm=
{
6
}
>
<
Card
size=
"small"
><
Statistic
title=
"问诊量 value={1280} prefix={<MessageOutlined />}
<
Card
size=
"small"
><
Statistic
title=
"问诊量
"
value=
{
1280
}
prefix=
{
<
MessageOutlined
/>
}
suffix=
{
<
span
className=
"text-xs text-green-500"
><
RiseOutlined
/>
8.3%
</
span
>
}
/></
Card
>
suffix=
{
<
span
className=
"text-xs text-green-500"
><
RiseOutlined
/>
8.3%
</
span
>
}
/></
Card
>
</
Col
>
</
Col
>
<
Col
xs=
{
12
}
sm=
{
6
}
>
<
Col
xs=
{
12
}
sm=
{
6
}
>
<
Card
size=
"small"
><
Statistic
title=
"总收入 value={56800} prefix={<DollarOutlined />} precision={0}
<
Card
size=
"small"
><
Statistic
title=
"总收入
"
value=
{
56800
}
prefix=
{
<
DollarOutlined
/>
}
precision=
{
0
}
suffix=
{
<
span
className=
"text-xs text-green-500"
><
RiseOutlined
/>
15.2%
</
span
>
}
/></
Card
>
suffix=
{
<
span
className=
"text-xs text-green-500"
><
RiseOutlined
/>
15.2%
</
span
>
}
/></
Card
>
</
Col
>
</
Col
>
<
Col
xs=
{
12
}
sm=
{
6
}
>
<
Col
xs=
{
12
}
sm=
{
6
}
>
...
...
web/src/pages/admin/Users/index.tsx
View file @
f350fadd
...
@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo {
...
@@ -13,9 +13,9 @@ interface UserRecord extends UserInfo {
}
}
const
roleMap
:
Record
<
string
,
{
text
:
string
;
color
:
string
}
>
=
{
const
roleMap
:
Record
<
string
,
{
text
:
string
;
color
:
string
}
>
=
{
patient
:
{
text
:
'
患者, color:
'
blue
'
},
patient
:
{
text
:
'
患者
'
,
color
:
'
blue
'
},
doctor
:
{
text
:
'
医生
'
,
color
:
'
green
'
},
doctor
:
{
text
:
'
医生
'
,
color
:
'
green
'
},
admin: { text:
'
管理员
,
color
:
'
purple
'
},
admin
:
{
text
:
'
管理员
'
,
color
:
'
purple
'
},
};
};
const
AdminUsersPage
:
React
.
FC
=
()
=>
{
const
AdminUsersPage
:
React
.
FC
=
()
=>
{
...
@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => {
...
@@ -61,7 +61,7 @@ const AdminUsersPage: React.FC = () => {
onOk
:
async
()
=>
{
onOk
:
async
()
=>
{
try
{
try
{
await
adminApi
.
resetUserPassword
(
userId
);
await
adminApi
.
resetUserPassword
(
userId
);
message
.
success
(
'
密码已重);
message
.
success
(
'
密码已重
置
'
);
}
catch
(
error
)
{
}
catch
(
error
)
{
message
.
error
(
'
重置密码失败
'
);
message
.
error
(
'
重置密码失败
'
);
}
}
...
@@ -78,7 +78,7 @@ const AdminUsersPage: React.FC = () => {
...
@@ -78,7 +78,7 @@ const AdminUsersPage: React.FC = () => {
onOk
:
async
()
=>
{
onOk
:
async
()
=>
{
try
{
try
{
await
adminApi
.
updateUserStatus
(
userId
,
newStatus
);
await
adminApi
.
updateUserStatus
(
userId
,
newStatus
);
message.success(`
?
{action}`);
message
.
success
(
`
已
$
{
action
}
`
);
fetchUsers
();
fetchUsers
();
}
catch
(
error
)
{
}
catch
(
error
)
{
message
.
error
(
`
${
action
}
失败`
);
message
.
error
(
`
${
action
}
失败`
);
...
@@ -118,12 +118,12 @@ const AdminUsersPage: React.FC = () => {
...
@@ -118,12 +118,12 @@ const AdminUsersPage: React.FC = () => {
render
:
(
v
:
boolean
)
=>
v
?
<
Tag
color=
"success"
>
已认证
</
Tag
>
:
<
Tag
color=
"warning"
>
未认证
</
Tag
>,
render
:
(
v
:
boolean
)
=>
v
?
<
Tag
color=
"success"
>
已认证
</
Tag
>
:
<
Tag
color=
"warning"
>
未认证
</
Tag
>,
},
},
{
{
title:
'
状态
,
title
:
'
状态
'
,
dataIndex
:
'
status
'
,
dataIndex
:
'
status
'
,
key
:
'
status
'
,
key
:
'
status
'
,
render
:
(
status
:
string
)
=>
(
render
:
(
status
:
string
)
=>
(
<
Tag
color=
{
status
===
'
active
'
?
'
green
'
:
'
red
'
}
>
<
Tag
color=
{
status
===
'
active
'
?
'
green
'
:
'
red
'
}
>
{
status
===
'
active
'
?
'
正常
'
:
'
已禁
}
{
status
===
'
active
'
?
'
正常
'
:
'
已禁
用
'
}
</
Tag
>
</
Tag
>
),
),
},
},
...
@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => {
...
@@ -162,14 +162,14 @@ const AdminUsersPage: React.FC = () => {
<
Card
size=
"small"
>
<
Card
size=
"small"
>
<
div
className=
"flex flex-wrap items-center gap-2"
>
<
div
className=
"flex flex-wrap items-center gap-2"
>
<
Input
placeholder=
"搜索用户名/手机号 prefix={<SearchOutlined />} value={keyword}
<
Input
placeholder=
"搜索用户名/手机号
"
prefix=
{
<
SearchOutlined
/>
}
value=
{
keyword
}
onChange=
{
(
e
)
=>
setKeyword
(
e
.
target
.
value
)
}
style=
{
{
width
:
180
}
}
size=
"small"
/>
onChange=
{
(
e
)
=>
setKeyword
(
e
.
target
.
value
)
}
style=
{
{
width
:
180
}
}
size=
"small"
/>
<
Select
placeholder=
"角色"
style=
{
{
width
:
100
}
}
size=
"small"
allowClear
value=
{
roleFilter
}
<
Select
placeholder=
"角色"
style=
{
{
width
:
100
}
}
size=
"small"
allowClear
value=
{
roleFilter
}
onChange=
{
(
v
)
=>
setRoleFilter
(
v
)
}
options=
{
[
onChange=
{
(
v
)
=>
setRoleFilter
(
v
)
}
options=
{
[
{
label
:
'
患者
, value:
'
patient
'
}, { label:
'
医生
'
, value:
'
doctor
'
}, { label:
'
管理员
,
value
:
'
admin
'
},
{
label
:
'
患者
'
,
value
:
'
patient
'
},
{
label
:
'
医生
'
,
value
:
'
doctor
'
},
{
label
:
'
管理员
'
,
value
:
'
admin
'
},
]
}
/>
]
}
/>
<
Select
placeholder=
"状态 style={{ width: 100 }} size="
small
"
allowClear
<
Select
placeholder=
"状态
"
style=
{
{
width
:
100
}
}
size=
"small"
allowClear
options=
{
[{
label
:
'
正常
'
,
value
:
'
active
'
},
{
label
:
'
已禁用, value:
'
disabled
'
}]
}
/>
options=
{
[{
label
:
'
正常
'
,
value
:
'
active
'
},
{
label
:
'
已禁用
'
,
value
:
'
disabled
'
}]
}
/>
<
Button
type=
"primary"
size=
"small"
icon=
{
<
SearchOutlined
/>
}
onClick=
{
handleSearch
}
>
搜索
</
Button
>
<
Button
type=
"primary"
size=
"small"
icon=
{
<
SearchOutlined
/>
}
onClick=
{
handleSearch
}
>
搜索
</
Button
>
</
div
>
</
div
>
</
Card
>
</
Card
>
...
@@ -178,7 +178,7 @@ const AdminUsersPage: React.FC = () => {
...
@@ -178,7 +178,7 @@ const AdminUsersPage: React.FC = () => {
<
Table
columns=
{
columns
}
dataSource=
{
usersData
}
rowKey=
"id"
loading=
{
loading
}
size=
"small"
<
Table
columns=
{
columns
}
dataSource=
{
usersData
}
rowKey=
"id"
loading=
{
loading
}
size=
"small"
pagination=
{
{
pagination=
{
{
current
:
page
,
pageSize
,
total
,
size
:
'
small
'
,
showSizeChanger
:
true
,
current
:
page
,
pageSize
,
total
,
size
:
'
small
'
,
showSizeChanger
:
true
,
showTotal
:
(
t
)
=>
`
?
${t} 条`
,
showTotal
:
(
t
)
=>
`
共
${t} 条`
,
onChange
:
(
p
,
ps
)
=>
{
setPage
(
p
);
setPageSize
(
ps
);
},
onChange
:
(
p
,
ps
)
=>
{
setPage
(
p
);
setPageSize
(
ps
);
},
}
}
}
}
/>
/>
...
...
web/src/pages/doctor/Certification/index.tsx
View file @
f350fadd
...
@@ -94,7 +94,7 @@ const DoctorCertificationPage: React.FC = () => {
...
@@ -94,7 +94,7 @@ const DoctorCertificationPage: React.FC = () => {
<
div
className=
"w-14 h-14 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4"
>
<
div
className=
"w-14 h-14 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4"
>
<
CheckCircleOutlined
className=
"text-2xl text-green-500"
/>
<
CheckCircleOutlined
className=
"text-2xl text-green-500"
/>
</
div
>
</
div
>
<
h3
className=
"text-base font-bold mb-2"
>
认证申请已提
</
h3
>
<
h3
className=
"text-base font-bold mb-2"
>
认证申请已提
交
</
h3
>
<
Paragraph
type=
"secondary"
className=
"text-xs!"
>
<
Paragraph
type=
"secondary"
className=
"text-xs!"
>
您的资质认证申请已成功提交,管理员将在 1-3 个工作日内完成审核。
</
Paragraph
>
您的资质认证申请已成功提交,管理员将在 1-3 个工作日内完成审核。
</
Paragraph
>
<
Button
type=
"primary"
size=
"small"
onClick=
{
()
=>
router
.
push
(
'
/doctor/workbench
'
)
}
>
返回工作台
</
Button
>
<
Button
type=
"primary"
size=
"small"
onClick=
{
()
=>
router
.
push
(
'
/doctor/workbench
'
)
}
>
返回工作台
</
Button
>
...
@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => {
...
@@ -121,18 +121,18 @@ const DoctorCertificationPage: React.FC = () => {
<
Card
size=
"small"
>
<
Card
size=
"small"
>
<
Form
form=
{
form
}
layout=
"vertical"
size=
"small"
onFinish=
{
handleSubmit
}
>
<
Form
form=
{
form
}
layout=
"vertical"
size=
"small"
onFinish=
{
handleSubmit
}
>
<
Form
.
Item
name=
"license_no"
label=
"执业证号"
rules=
{
[{
required
:
true
,
message
:
'
请输入执业证书 }]
}
>
<
Form
.
Item
name=
"license_no"
label=
"执业证号"
rules=
{
[{
required
:
true
,
message
:
'
请输入执业证书
号
'
}]
}
>
<
Input
placeholder=
"请输入医师执业证书 />
<
Input
placeholder=
"请输入医师执业证书
号"
/>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
name=
"title"
label=
"职称"
rules=
{
[{
required
:
true
,
message
:
'
请选择职称
'
}]
}
>
<
Form
.
Item
name=
"title"
label=
"职称"
rules=
{
[{
required
:
true
,
message
:
'
请选择职称
'
}]
}
>
<
Select
placeholder=
"请选择职称"
>
<
Select
placeholder=
"请选择职称"
>
<
Select
.
Option
value=
"主任医师"
>
主任医师
</
Select
.
Option
>
<
Select
.
Option
value=
"主任医师"
>
主任医师
</
Select
.
Option
>
<
Select
.
Option
value=
"副主任医
>副主任医院
/Select.Option>
<
Select
.
Option
value=
"副主任医
师"
>
副主任医师
<
/
Select
.
Option
>
<
Select
.
Option
value=
"主治医师"
>
主治医师
</
Select
.
Option
>
<
Select
.
Option
value=
"主治医师"
>
主治医师
</
Select
.
Option
>
<
Select
.
Option
value=
"住院医师"
>
住院医师
</
Select
.
Option
>
<
Select
.
Option
value=
"住院医师"
>
住院医师
</
Select
.
Option
>
</
Select
>
</
Select
>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
name=
"hospital"
label=
"所属医院
rules={[{ required: true, message: '请输入所属医院
}]}>
<
Form
.
Item
name=
"hospital"
label=
"所属医院
"
rules=
{
[{
required
:
true
,
message
:
'
请输入所属医院
'
}]
}
>
<
Input
placeholder=
"请输入您所在的医院全称"
/>
<
Input
placeholder=
"请输入您所在的医院全称"
/>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
name=
"department_name"
label=
"科室"
rules=
{
[{
required
:
true
,
message
:
'
请选择科室
'
}]
}
>
<
Form
.
Item
name=
"department_name"
label=
"科室"
rules=
{
[{
required
:
true
,
message
:
'
请选择科室
'
}]
}
>
...
@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => {
...
@@ -143,7 +143,7 @@ const DoctorCertificationPage: React.FC = () => {
</
Select
>
</
Select
>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
label=
"执业证照片 required>
<
Form
.
Item
label=
"执业证照片
"
required
>
<
Upload
<
Upload
listType=
"picture-card"
listType=
"picture-card"
fileList=
{
licenseFileList
}
fileList=
{
licenseFileList
}
...
@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => {
...
@@ -152,13 +152,13 @@ const DoctorCertificationPage: React.FC = () => {
maxCount=
{
1
}
maxCount=
{
1
}
>
>
{
licenseFileList
.
length
===
0
&&
(
{
licenseFileList
.
length
===
0
&&
(
<
div
><
UploadOutlined
/><
div
className=
"mt-1 text-xs"
>
上传执业
</
div
></
div
>
<
div
><
UploadOutlined
/><
div
className=
"mt-1 text-xs"
>
上传执业
证
</
div
></
div
>
)
}
)
}
</
Upload
>
</
Upload
>
<
Text
type=
"secondary"
className=
"text-[11px]!"
>
支持 JPG、PNG,不超过 5MB
</
Text
>
<
Text
type=
"secondary"
className=
"text-[11px]!"
>
支持 JPG、PNG,不超过 5MB
</
Text
>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
label=
"资格证照片 required>
<
Form
.
Item
label=
"资格证照片
"
required
>
<
Upload
<
Upload
listType=
"picture-card"
listType=
"picture-card"
fileList=
{
qualificationFileList
}
fileList=
{
qualificationFileList
}
...
@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => {
...
@@ -167,7 +167,7 @@ const DoctorCertificationPage: React.FC = () => {
maxCount=
{
1
}
maxCount=
{
1
}
>
>
{
qualificationFileList
.
length
===
0
&&
(
{
qualificationFileList
.
length
===
0
&&
(
<
div
><
UploadOutlined
/><
div
className=
"mt-1 text-xs"
>
上传资格
</
div
></
div
>
<
div
><
UploadOutlined
/><
div
className=
"mt-1 text-xs"
>
上传资格
证
</
div
></
div
>
)
}
)
}
</
Upload
>
</
Upload
>
<
Text
type=
"secondary"
className=
"text-[11px]!"
>
支持 JPG、PNG,不超过 5MB
</
Text
>
<
Text
type=
"secondary"
className=
"text-[11px]!"
>
支持 JPG、PNG,不超过 5MB
</
Text
>
...
@@ -183,4 +183,4 @@ const DoctorCertificationPage: React.FC = () => {
...
@@ -183,4 +183,4 @@ const DoctorCertificationPage: React.FC = () => {
};
};
export
default
DoctorCertificationPage
;
export
default
DoctorCertificationPage
;
web/src/pages/doctor/ChronicReview/index.tsx
View file @
f350fadd
...
@@ -36,13 +36,13 @@ const mockData: ChronicPrescription[] = [
...
@@ -36,13 +36,13 @@ const mockData: ChronicPrescription[] = [
id
:
'
1
'
,
id
:
'
1
'
,
patient_name
:
'
张三
'
,
patient_name
:
'
张三
'
,
patient_age
:
58
,
patient_age
:
58
,
patient_gender
:
'
?
,
patient_gender
:
'
男
'
,
disease
:
'
2型糖尿病
'
,
disease
:
'
2型糖尿病
'
,
last_prescription_date
:
'
2026-01-25
'
,
last_prescription_date
:
'
2026-01-25
'
,
ai_draft
:
{
ai_draft
:
{
drugs
:
[
drugs
:
[
{ name:
'
二甲双胍缓释
放
,
spec
:
'
0.5g/?, dosage:
'
每次
1
?,
frequency
:
'
每日2?
, days: 30 },
{
name
:
'
二甲双胍缓释
片
'
,
spec
:
'
0.5g/片
'
,
dosage
:
'
每次1片
'
,
frequency
:
'
每日2次
'
,
days
:
30
},
{ name:
'
格列美脲
类
,
spec
:
'
2mg/?, dosage:
'
每次
1
?,
frequency
:
'
每日1?
, days: 30 },
{
name
:
'
格列美脲
片
'
,
spec
:
'
2mg/片
'
,
dosage
:
'
每次1片
'
,
frequency
:
'
每日1次
'
,
days
:
30
},
],
],
note
:
'
患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%
'
,
note
:
'
患者血糖控制良好,建议继续当前方案。近期HbA1c: 6.8%
'
,
},
},
...
@@ -53,13 +53,13 @@ const mockData: ChronicPrescription[] = [
...
@@ -53,13 +53,13 @@ const mockData: ChronicPrescription[] = [
id
:
'
2
'
,
id
:
'
2
'
,
patient_name
:
'
李四
'
,
patient_name
:
'
李四
'
,
patient_age
:
65
,
patient_age
:
65
,
patient_gender:
'
?
,
patient_gender
:
'
女
'
,
disease
:
'
高血压,
disease
:
'
高血压
'
,
last_prescription_date
:
'
2026-01-20
'
,
last_prescription_date
:
'
2026-01-20
'
,
ai_draft
:
{
ai_draft
:
{
drugs
:
[
drugs
:
[
{ name:
'
氨氯地平
均
,
spec
:
'
5mg/?, dosage:
'
每次
1
?,
frequency
:
'
每日1?
, days: 30 },
{
name
:
'
氨氯地平
片
'
,
spec
:
'
5mg/片
'
,
dosage
:
'
每次1片
'
,
frequency
:
'
每日1次
'
,
days
:
30
},
{ name:
'
缬沙坦胶囊
,
spec
:
'
80mg/?, dosage:
'
每次
1
?,
frequency
:
'
每日1?
, days: 30 },
{
name
:
'
缬沙坦胶囊
'
,
spec
:
'
80mg/粒
'
,
dosage
:
'
每次1粒
'
,
frequency
:
'
每日1次
'
,
days
:
30
},
],
],
note
:
'
患者血压控制稳定,建议继续联合用药方案。近期血压 130/85mmHg
'
,
note
:
'
患者血压控制稳定,建议继续联合用药方案。近期血压 130/85mmHg
'
,
},
},
...
@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => {
...
@@ -81,7 +81,7 @@ const ChronicReviewPage: React.FC = () => {
const
handleApprove
=
(
id
:
string
)
=>
{
const
handleApprove
=
(
id
:
string
)
=>
{
setData
(
data
.
map
((
d
)
=>
(
d
.
id
===
id
?
{
...
d
,
status
:
'
approved
'
as
const
}
:
d
)));
setData
(
data
.
map
((
d
)
=>
(
d
.
id
===
id
?
{
...
d
,
status
:
'
approved
'
as
const
}
:
d
)));
message.success(
'
续方已审核通过并签名发送
);
message
.
success
(
'
续方已审核通过并签名发送
'
);
setDetailModalVisible
(
false
);
setDetailModalVisible
(
false
);
};
};
...
@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => {
...
@@ -89,11 +89,11 @@ const ChronicReviewPage: React.FC = () => {
Modal
.
confirm
({
Modal
.
confirm
({
title
:
'
拒绝续方申请
'
,
title
:
'
拒绝续方申请
'
,
content
:
(
content
:
(
<
TextArea
placeholder=
"请输入拒绝理.."
rows=
{
3
}
/>
<
TextArea
placeholder=
"请输入拒绝理
由.
.."
rows=
{
3
}
/>
),
),
onOk
:
()
=>
{
onOk
:
()
=>
{
setData
(
data
.
map
((
d
)
=>
(
d
.
id
===
id
?
{
...
d
,
status
:
'
rejected
'
as
const
}
:
d
)));
setData
(
data
.
map
((
d
)
=>
(
d
.
id
===
id
?
{
...
d
,
status
:
'
rejected
'
as
const
}
:
d
)));
message
.
info
(
'
续方申请已拒);
message
.
info
(
'
续方申请已拒
绝
'
);
setDetailModalVisible
(
false
);
setDetailModalVisible
(
false
);
},
},
});
});
...
@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => {
...
@@ -101,17 +101,17 @@ const ChronicReviewPage: React.FC = () => {
const
getStatusTag
=
(
status
:
string
)
=>
{
const
getStatusTag
=
(
status
:
string
)
=>
{
const
map
:
Record
<
string
,
{
color
:
string
;
text
:
string
}
>
=
{
const
map
:
Record
<
string
,
{
color
:
string
;
text
:
string
}
>
=
{
pending: { color:
'
orange
'
, text:
'
待审核
},
pending
:
{
color
:
'
orange
'
,
text
:
'
待审核
'
},
approved
:
{
color
:
'
green
'
,
text
:
'
已通过
'
},
approved
:
{
color
:
'
green
'
,
text
:
'
已通过
'
},
rejected
:
{
color
:
'
red
'
,
text
:
'
已拒绝 },
rejected
:
{
color
:
'
red
'
,
text
:
'
已拒绝
'
},
modified: { color:
'
blue
'
, text:
'
已修
},
modified
:
{
color
:
'
blue
'
,
text
:
'
已修
改
'
},
};
};
const
s
=
map
[
status
]
||
{
color
:
'
default
'
,
text
:
status
};
const
s
=
map
[
status
]
||
{
color
:
'
default
'
,
text
:
status
};
return
<
Tag
color=
{
s
.
color
}
>
{
s
.
text
}
</
Tag
>;
return
<
Tag
color=
{
s
.
color
}
>
{
s
.
text
}
</
Tag
>;
};
};
const
columns
=
[
const
columns
=
[
{
title
:
'
患者姓名, dataIndex:
'
patient_name
'
, key:
'
patient_name
'
, width: 100 },
{
title
:
'
患者姓名
'
,
dataIndex
:
'
patient_name
'
,
key
:
'
patient_name
'
,
width
:
100
},
{
{
title
:
'
基本信息
'
,
title
:
'
基本信息
'
,
key
:
'
info
'
,
key
:
'
info
'
,
...
@@ -119,15 +119,15 @@ const ChronicReviewPage: React.FC = () => {
...
@@ -119,15 +119,15 @@ const ChronicReviewPage: React.FC = () => {
render
:
(
_
:
any
,
r
:
ChronicPrescription
)
=>
`
${
r
.
patient_gender
}
/
${
r
.
patient_age
}
岁`
,
render
:
(
_
:
any
,
r
:
ChronicPrescription
)
=>
`
${
r
.
patient_gender
}
/
${
r
.
patient_age
}
岁`
,
},
},
{
title
:
'
慢病诊断
'
,
dataIndex
:
'
disease
'
,
key
:
'
disease
'
,
width
:
120
},
{
title
:
'
慢病诊断
'
,
dataIndex
:
'
disease
'
,
key
:
'
disease
'
,
width
:
120
},
{ title:
'
上次开
始
,
dataIndex
:
'
last_prescription_date
'
,
key
:
'
last_prescription_date
'
,
width
:
120
},
{
title
:
'
上次开
方
'
,
dataIndex
:
'
last_prescription_date
'
,
key
:
'
last_prescription_date
'
,
width
:
120
},
{
{
title
:
'
药品数量
'
,
title
:
'
药品数量
'
,
key
:
'
drug_count
'
,
key
:
'
drug_count
'
,
width
:
80
,
width
:
80
,
render
:
(
_
:
any
,
r
:
ChronicPrescription
)
=>
<
Tag
color=
"blue"
>
{
r
.
ai_draft
.
drugs
.
length
}
?
/Tag
>
,
render
:
(
_
:
any
,
r
:
ChronicPrescription
)
=>
<
Tag
color=
"blue"
>
{
r
.
ai_draft
.
drugs
.
length
}
种
<
/
Tag
>,
},
},
{
{
title
:
'
状态,
title
:
'
状态
'
,
dataIndex
:
'
status
'
,
dataIndex
:
'
status
'
,
key
:
'
status
'
,
key
:
'
status
'
,
width
:
100
,
width
:
100
,
...
@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => {
...
@@ -205,13 +205,13 @@ const ChronicReviewPage: React.FC = () => {
{
currentRecord
&&
(
{
currentRecord
&&
(
<
div
>
<
div
>
<
Descriptions
bordered
size=
"small"
column=
{
2
}
style=
{
{
marginBottom
:
16
}
}
>
<
Descriptions
bordered
size=
"small"
column=
{
2
}
style=
{
{
marginBottom
:
16
}
}
>
<
Descriptions
.
Item
label=
"患者姓>
{
currentRecord
.
patient_name
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"患者姓
名"
>
{
currentRecord
.
patient_name
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"性别/年龄"
>
{
currentRecord
.
patient_gender
}
/
{
currentRecord
.
patient_age
}
?
/Descriptions.Item
>
<
Descriptions
.
Item
label=
"性别/年龄"
>
{
currentRecord
.
patient_gender
}
/
{
currentRecord
.
patient_age
}
岁
<
/
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"慢病诊断"
>
{
currentRecord
.
disease
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"慢病诊断"
>
{
currentRecord
.
disease
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"上次开>
{
currentRecord
.
last_prescription_date
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"上次开
方"
>
{
currentRecord
.
last_prescription_date
}
</
Descriptions
.
Item
>
</
Descriptions
>
</
Descriptions
>
<
Card
title=
"AI 续方草"
size=
"small"
style=
{
{
marginBottom
:
16
,
borderColor
:
'
#52c41a
'
}
}
>
<
Card
title=
"AI 续方草
稿
"
size=
"small"
style=
{
{
marginBottom
:
16
,
borderColor
:
'
#52c41a
'
}
}
>
<
Table
<
Table
size=
"small"
size=
"small"
pagination=
{
false
}
pagination=
{
false
}
...
...
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