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
caca4568
Commit
caca4568
authored
Mar 06, 2026
by
yuguo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
fbf70318
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
457 additions
and
152 deletions
+457
-152
server/cmd/api/main.go
server/cmd/api/main.go
+5
-0
server/internal/agent/seed_prompts.go
server/internal/agent/seed_prompts.go
+11
-17
server/internal/model/workflow.go
server/internal/model/workflow.go
+1
-1
server/internal/service/admin/consult_log.go
server/internal/service/admin/consult_log.go
+1
-1
server/internal/service/admin/handler.go
server/internal/service/admin/handler.go
+72
-0
server/internal/service/admin/service.go
server/internal/service/admin/service.go
+21
-0
server/internal/service/consult/handler.go
server/internal/service/consult/handler.go
+1
-1
server/internal/service/consult/service.go
server/internal/service/consult/service.go
+67
-31
server/pkg/middleware/cors.go
server/pkg/middleware/cors.go
+3
-26
server/pkg/websocket/handler.go
server/pkg/websocket/handler.go
+1
-17
server/pkg/workflow/engine.go
server/pkg/workflow/engine.go
+18
-4
web/src/api/admin.ts
web/src/api/admin.ts
+9
-0
web/src/app/(main)/admin/admins/page.tsx
web/src/app/(main)/admin/admins/page.tsx
+74
-13
web/src/app/(main)/admin/dashboard/page.tsx
web/src/app/(main)/admin/dashboard/page.tsx
+2
-2
web/src/app/(main)/admin/doctors/page.tsx
web/src/app/(main)/admin/doctors/page.tsx
+84
-25
web/src/app/(main)/admin/patients/page.tsx
web/src/app/(main)/admin/patients/page.tsx
+82
-9
web/src/app/(main)/doctor/profile/page.tsx
web/src/app/(main)/doctor/profile/page.tsx
+1
-1
web/src/app/(main)/patient/doctors/[id]/page.tsx
web/src/app/(main)/patient/doctors/[id]/page.tsx
+1
-1
web/src/app/(main)/patient/pre-consult/ReportSection.tsx
web/src/app/(main)/patient/pre-consult/ReportSection.tsx
+1
-1
web/src/components/ConsultCreateDrawer/index.tsx
web/src/components/ConsultCreateDrawer/index.tsx
+1
-1
web/src/store/aiAssistStore.ts
web/src/store/aiAssistStore.ts
+1
-1
No files found.
server/cmd/api/main.go
View file @
caca4568
...
@@ -59,6 +59,11 @@ func main() {
...
@@ -59,6 +59,11 @@ func main() {
log
.
Println
(
"开始执行数据库表迁移..."
)
log
.
Println
(
"开始执行数据库表迁移..."
)
db
:=
database
.
GetDB
()
db
:=
database
.
GetDB
()
// 修复:workflow_executions.trigger_by 从 uuid 改为 varchar(支持 "system" 等非 UUID 值)
if
db
.
Migrator
()
.
HasTable
(
"workflow_executions"
)
{
db
.
Exec
(
"ALTER TABLE workflow_executions ALTER COLUMN trigger_by TYPE VARCHAR(100)"
)
}
// 修复:给已有 consultations 记录补充 serial_number(迁移 NOT NULL 前必须)
// 修复:给已有 consultations 记录补充 serial_number(迁移 NOT NULL 前必须)
if
db
.
Migrator
()
.
HasTable
(
"consultations"
)
{
if
db
.
Migrator
()
.
HasTable
(
"consultations"
)
{
if
!
db
.
Migrator
()
.
HasColumn
(
&
model
.
Consultation
{},
"serial_number"
)
{
if
!
db
.
Migrator
()
.
HasColumn
(
&
model
.
Consultation
{},
"serial_number"
)
{
...
...
server/internal/agent/seed_prompts.go
View file @
caca4568
...
@@ -9,7 +9,7 @@ import (
...
@@ -9,7 +9,7 @@ import (
// currentPromptVersion 当前代码中提示词模板的版本号
// currentPromptVersion 当前代码中提示词模板的版本号
// 每次修改提示词内容时递增此值,ensurePromptTemplates 会自动同步到数据库
// 每次修改提示词内容时递增此值,ensurePromptTemplates 会自动同步到数据库
const
currentPromptVersion
=
3
const
currentPromptVersion
=
5
// ensurePromptTemplates 确保所有内置提示词模板存在于数据库中(种子数据)
// ensurePromptTemplates 确保所有内置提示词模板存在于数据库中(种子数据)
// 逻辑:不存在则创建;已存在但版本低于代码版本则更新内容
// 逻辑:不存在则创建;已存在但版本低于代码版本则更新内容
...
@@ -264,19 +264,16 @@ func ensurePromptTemplates() {
...
@@ -264,19 +264,16 @@ func ensurePromptTemplates() {
Name
:
"鉴别诊断分析"
,
Name
:
"鉴别诊断分析"
,
Scene
:
"consult_diagnosis"
,
Scene
:
"consult_diagnosis"
,
TemplateType
:
"system"
,
TemplateType
:
"system"
,
Content
:
`你是一位经验丰富的临床医生AI助手,请根据
以下患者
信息进行鉴别诊断分析。
Content
:
`你是一位经验丰富的临床医生AI助手,请根据
本次就诊
信息进行鉴别诊断分析。
## 患者信息
## 本次就诊信息
- 主诉:{{chief_complaint}}
{{consult_context}}
{{#pre_consult_analysis}}- 预问诊分析:{{pre_consult_analysis}}{{/pre_consult_analysis}}
{{#allergy_history}}- 过敏史:{{allergy_history}}{{/allergy_history}}
{{#chronic_diseases}}- 慢性病史:{{chronic_diseases}}{{/chronic_diseases}}
## 对话记录
##
医患
对话记录
{{chat_history}}
{{chat_history}}
## 要求
## 要求
请按以下格式给出鉴别诊断建议:
请
结合上述就诊信息和对话记录,
按以下格式给出鉴别诊断建议:
### 初步诊断
### 初步诊断
列出最可能的诊断(按可能性从高到低排列)
列出最可能的诊断(按可能性从高到低排列)
...
@@ -302,19 +299,16 @@ func ensurePromptTemplates() {
...
@@ -302,19 +299,16 @@ func ensurePromptTemplates() {
Name
:
"用药建议分析"
,
Name
:
"用药建议分析"
,
Scene
:
"consult_medication"
,
Scene
:
"consult_medication"
,
TemplateType
:
"system"
,
TemplateType
:
"system"
,
Content
:
`你是一位经验丰富的临床药师AI助手,请根据
以下患者
信息给出用药建议。
Content
:
`你是一位经验丰富的临床药师AI助手,请根据
本次就诊
信息给出用药建议。
## 患者信息
## 本次就诊信息
- 主诉:{{chief_complaint}}
{{consult_context}}
{{#pre_consult_analysis}}- 预问诊分析:{{pre_consult_analysis}}{{/pre_consult_analysis}}
{{#allergy_history}}- 过敏史:{{allergy_history}}{{/allergy_history}}
{{#chronic_diseases}}- 慢性病史:{{chronic_diseases}}{{/chronic_diseases}}
## 对话记录
##
医患
对话记录
{{chat_history}}
{{chat_history}}
## 要求
## 要求
请按以下格式给出用药建议:
请
结合上述就诊信息和对话记录,
按以下格式给出用药建议:
### 推荐用药方案
### 推荐用药方案
...
...
server/internal/model/workflow.go
View file @
caca4568
...
@@ -24,7 +24,7 @@ type WorkflowExecution struct {
...
@@ -24,7 +24,7 @@ type WorkflowExecution struct {
WorkflowID
string
`gorm:"type:varchar(100);index" json:"workflow_id"`
WorkflowID
string
`gorm:"type:varchar(100);index" json:"workflow_id"`
Version
int
`json:"version"`
Version
int
`json:"version"`
TriggerType
string
`gorm:"type:varchar(50)" json:"trigger_type"`
TriggerType
string
`gorm:"type:varchar(50)" json:"trigger_type"`
TriggerBy
string
`gorm:"type:
uuid
" json:"trigger_by"`
TriggerBy
string
`gorm:"type:
varchar(100)
" json:"trigger_by"`
Input
string
`gorm:"type:jsonb" json:"input"`
Input
string
`gorm:"type:jsonb" json:"input"`
Output
string
`gorm:"type:jsonb" json:"output"`
Output
string
`gorm:"type:jsonb" json:"output"`
Status
string
`gorm:"type:varchar(20)" json:"status"`
// pending, running, completed, failed
Status
string
`gorm:"type:varchar(20)" json:"status"`
// pending, running, completed, failed
...
...
server/internal/service/admin/consult_log.go
View file @
caca4568
...
@@ -74,7 +74,7 @@ func (s *Service) ApproveDoctorReview(ctx context.Context, reviewID string) erro
...
@@ -74,7 +74,7 @@ func (s *Service) ApproveDoctorReview(ctx context.Context, reviewID string) erro
DepartmentID
:
dept
.
ID
,
DepartmentID
:
dept
.
ID
,
Hospital
:
review
.
Hospital
,
Hospital
:
review
.
Hospital
,
Rating
:
5.0
,
Rating
:
5.0
,
Price
:
50
00
,
// 默认50元
Price
:
50
,
// 默认50元
Status
:
"approved"
,
Status
:
"approved"
,
}
}
if
err
:=
tx
.
Create
(
doctor
)
.
Error
;
err
!=
nil
{
if
err
:=
tx
.
Create
(
doctor
)
.
Error
;
err
!=
nil
{
...
...
server/internal/service/admin/handler.go
View file @
caca4568
...
@@ -33,6 +33,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
...
@@ -33,6 +33,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
adm
.
DELETE
(
"/users/:id"
,
h
.
DeleteUser
)
adm
.
DELETE
(
"/users/:id"
,
h
.
DeleteUser
)
adm
.
PUT
(
"/users/:id/status"
,
h
.
UpdateUserStatus
)
adm
.
PUT
(
"/users/:id/status"
,
h
.
UpdateUserStatus
)
adm
.
POST
(
"/users/:id/reset-password"
,
h
.
ResetUserPassword
)
adm
.
POST
(
"/users/:id/reset-password"
,
h
.
ResetUserPassword
)
adm
.
POST
(
"/users/:id/change-password"
,
h
.
ChangeUserPassword
)
// 患者管理
// 患者管理
adm
.
GET
(
"/patients"
,
h
.
GetPatientList
)
adm
.
GET
(
"/patients"
,
h
.
GetPatientList
)
...
@@ -41,6 +42,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
...
@@ -41,6 +42,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
adm
.
DELETE
(
"/patients/:id"
,
h
.
DeletePatient
)
adm
.
DELETE
(
"/patients/:id"
,
h
.
DeletePatient
)
adm
.
PUT
(
"/patients/:id/status"
,
h
.
UpdatePatientStatus
)
adm
.
PUT
(
"/patients/:id/status"
,
h
.
UpdatePatientStatus
)
adm
.
POST
(
"/patients/:id/reset-password"
,
h
.
ResetPatientPassword
)
adm
.
POST
(
"/patients/:id/reset-password"
,
h
.
ResetPatientPassword
)
adm
.
POST
(
"/patients/:id/change-password"
,
h
.
ChangePatientPassword
)
// 医生管理
// 医生管理
adm
.
GET
(
"/doctors"
,
h
.
GetDoctorManageList
)
adm
.
GET
(
"/doctors"
,
h
.
GetDoctorManageList
)
...
@@ -49,6 +51,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
...
@@ -49,6 +51,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
adm
.
DELETE
(
"/doctors/:id"
,
h
.
DeleteDoctor
)
adm
.
DELETE
(
"/doctors/:id"
,
h
.
DeleteDoctor
)
adm
.
PUT
(
"/doctors/:id/status"
,
h
.
UpdateDoctorStatus
)
adm
.
PUT
(
"/doctors/:id/status"
,
h
.
UpdateDoctorStatus
)
adm
.
POST
(
"/doctors/:id/reset-password"
,
h
.
ResetDoctorPassword
)
adm
.
POST
(
"/doctors/:id/reset-password"
,
h
.
ResetDoctorPassword
)
adm
.
POST
(
"/doctors/:id/change-password"
,
h
.
ChangeDoctorPassword
)
// 医生审核
// 医生审核
adm
.
GET
(
"/doctor-reviews"
,
h
.
GetDoctorReviewList
)
adm
.
GET
(
"/doctor-reviews"
,
h
.
GetDoctorReviewList
)
...
@@ -68,6 +71,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
...
@@ -68,6 +71,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
adm
.
DELETE
(
"/admins/:id"
,
h
.
DeleteAdmin
)
adm
.
DELETE
(
"/admins/:id"
,
h
.
DeleteAdmin
)
adm
.
PUT
(
"/admins/:id/status"
,
h
.
UpdateAdminStatus
)
adm
.
PUT
(
"/admins/:id/status"
,
h
.
UpdateAdminStatus
)
adm
.
POST
(
"/admins/:id/reset-password"
,
h
.
ResetAdminPassword
)
adm
.
POST
(
"/admins/:id/reset-password"
,
h
.
ResetAdminPassword
)
adm
.
POST
(
"/admins/:id/change-password"
,
h
.
ChangeAdminPassword
)
// 系统日志
// 系统日志
adm
.
GET
(
"/logs"
,
h
.
GetSystemLogs
)
adm
.
GET
(
"/logs"
,
h
.
GetSystemLogs
)
...
@@ -473,6 +477,74 @@ func (h *Handler) ResetAdminPassword(c *gin.Context) {
...
@@ -473,6 +477,74 @@ func (h *Handler) ResetAdminPassword(c *gin.Context) {
response
.
Success
(
c
,
nil
)
response
.
Success
(
c
,
nil
)
}
}
// ChangeUserPassword 修改用户密码
func
(
h
*
Handler
)
ChangeUserPassword
(
c
*
gin
.
Context
)
{
id
:=
c
.
Param
(
"id"
)
var
req
struct
{
Password
string
`json:"password" binding:"required"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"请输入新密码"
)
return
}
if
err
:=
h
.
service
.
ChangeUserPassword
(
c
.
Request
.
Context
(),
id
,
req
.
Password
);
err
!=
nil
{
response
.
Error
(
c
,
500
,
err
.
Error
())
return
}
response
.
Success
(
c
,
nil
)
}
// ChangePatientPassword 修改患者密码
func
(
h
*
Handler
)
ChangePatientPassword
(
c
*
gin
.
Context
)
{
id
:=
c
.
Param
(
"id"
)
var
req
struct
{
Password
string
`json:"password" binding:"required"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"请输入新密码"
)
return
}
if
err
:=
h
.
service
.
ChangeUserPassword
(
c
.
Request
.
Context
(),
id
,
req
.
Password
);
err
!=
nil
{
response
.
Error
(
c
,
500
,
err
.
Error
())
return
}
response
.
Success
(
c
,
nil
)
}
// ChangeDoctorPassword 修改医生密码
func
(
h
*
Handler
)
ChangeDoctorPassword
(
c
*
gin
.
Context
)
{
id
:=
c
.
Param
(
"id"
)
var
req
struct
{
Password
string
`json:"password" binding:"required"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"请输入新密码"
)
return
}
if
err
:=
h
.
service
.
ChangeUserPassword
(
c
.
Request
.
Context
(),
id
,
req
.
Password
);
err
!=
nil
{
response
.
Error
(
c
,
500
,
err
.
Error
())
return
}
response
.
Success
(
c
,
nil
)
}
// ChangeAdminPassword 修改管理员密码
func
(
h
*
Handler
)
ChangeAdminPassword
(
c
*
gin
.
Context
)
{
id
:=
c
.
Param
(
"id"
)
var
req
struct
{
Password
string
`json:"password" binding:"required"`
}
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"请输入新密码"
)
return
}
if
err
:=
h
.
service
.
ChangeUserPassword
(
c
.
Request
.
Context
(),
id
,
req
.
Password
);
err
!=
nil
{
response
.
Error
(
c
,
500
,
err
.
Error
())
return
}
response
.
Success
(
c
,
nil
)
}
// SeedRBACHandler 手动导入RBAC种子数据(角色、权限、菜单)
// SeedRBACHandler 手动导入RBAC种子数据(角色、权限、菜单)
func
(
h
*
Handler
)
SeedRBACHandler
(
c
*
gin
.
Context
)
{
func
(
h
*
Handler
)
SeedRBACHandler
(
c
*
gin
.
Context
)
{
SeedRBAC
()
SeedRBAC
()
...
...
server/internal/service/admin/service.go
View file @
caca4568
...
@@ -368,6 +368,27 @@ func (s *Service) ResetUserPassword(ctx context.Context, userID string) error {
...
@@ -368,6 +368,27 @@ func (s *Service) ResetUserPassword(ctx context.Context, userID string) error {
return
s
.
db
.
Model
(
&
model
.
User
{})
.
Where
(
"id = ?"
,
userID
)
.
Update
(
"password"
,
hashedPassword
)
.
Error
return
s
.
db
.
Model
(
&
model
.
User
{})
.
Where
(
"id = ?"
,
userID
)
.
Update
(
"password"
,
hashedPassword
)
.
Error
}
}
// ChangeUserPassword 修改用户密码(管理员设置指定密码)
func
(
s
*
Service
)
ChangeUserPassword
(
ctx
context
.
Context
,
userID
,
newPassword
string
)
error
{
if
len
(
newPassword
)
<
6
{
return
fmt
.
Errorf
(
"密码长度不能少于6位"
)
}
hashedPassword
,
err
:=
utils
.
HashPassword
(
newPassword
)
if
err
!=
nil
{
return
err
}
return
s
.
db
.
Model
(
&
model
.
User
{})
.
Where
(
"id = ?"
,
userID
)
.
Update
(
"password"
,
hashedPassword
)
.
Error
}
// ChangeDoctorPassword 修改医生密码
func
(
s
*
Service
)
ChangeDoctorPassword
(
ctx
context
.
Context
,
doctorID
,
newPassword
string
)
error
{
var
doctor
model
.
Doctor
if
err
:=
s
.
db
.
Where
(
"id = ?"
,
doctorID
)
.
First
(
&
doctor
)
.
Error
;
err
!=
nil
{
return
err
}
return
s
.
ChangeUserPassword
(
ctx
,
doctor
.
UserID
,
newPassword
)
}
// UpdateDoctorStatus 更新医生状态(通过 user_id 更新用户状态)
// UpdateDoctorStatus 更新医生状态(通过 user_id 更新用户状态)
func
(
s
*
Service
)
UpdateDoctorStatus
(
ctx
context
.
Context
,
doctorID
,
status
string
)
error
{
func
(
s
*
Service
)
UpdateDoctorStatus
(
ctx
context
.
Context
,
doctorID
,
status
string
)
error
{
var
doctor
model
.
Doctor
var
doctor
model
.
Doctor
...
...
server/internal/service/consult/handler.go
View file @
caca4568
...
@@ -343,7 +343,7 @@ func escapeJSON(s string) string {
...
@@ -343,7 +343,7 @@ func escapeJSON(s string) string {
case
'\t'
:
case
'\t'
:
result
=
append
(
result
,
'\\'
,
't'
)
result
=
append
(
result
,
'\\'
,
't'
)
default
:
default
:
result
=
append
(
result
,
byte
(
ch
)
)
result
=
append
(
result
,
[]
byte
(
string
(
ch
))
...
)
}
}
}
}
return
string
(
result
)
return
string
(
result
)
...
...
server/internal/service/consult/service.go
View file @
caca4568
...
@@ -422,6 +422,7 @@ func (s *Service) AIAssist(ctx context.Context, consultID string, scene string)
...
@@ -422,6 +422,7 @@ func (s *Service) AIAssist(ctx context.Context, consultID string, scene string)
}
}
// buildTemplatePrompt 构建模板提示词(共享逻辑)
// buildTemplatePrompt 构建模板提示词(共享逻辑)
// 通过就诊流水号(consultID)查询本次问诊的完整上下文,所有数据通过模板变量统一替换
func
(
s
*
Service
)
buildTemplatePrompt
(
func
(
s
*
Service
)
buildTemplatePrompt
(
scene
string
,
scene
string
,
consult
model
.
Consultation
,
consult
model
.
Consultation
,
...
@@ -434,47 +435,58 @@ func (s *Service) buildTemplatePrompt(
...
@@ -434,47 +435,58 @@ func (s *Service) buildTemplatePrompt(
if
err
:=
s
.
db
.
Where
(
"template_key = ? AND status = 'active'"
,
scene
)
.
First
(
&
tmpl
)
.
Error
;
err
!=
nil
{
if
err
:=
s
.
db
.
Where
(
"template_key = ? AND status = 'active'"
,
scene
)
.
First
(
&
tmpl
)
.
Error
;
err
!=
nil
{
log
.
Printf
(
"[AIAssist] 未找到模板 %s,使用默认提示词"
,
scene
)
log
.
Printf
(
"[AIAssist] 未找到模板 %s,使用默认提示词"
,
scene
)
if
scene
==
"consult_diagnosis"
{
if
scene
==
"consult_diagnosis"
{
tmpl
.
Content
=
"你是一位经验丰富的临床医生AI助手,请根据患者信息进行鉴别诊断分析,列出可能的诊断及依据。"
tmpl
.
Content
=
"你是一位经验丰富的临床医生AI助手,请根据患者信息进行鉴别诊断分析,列出可能的诊断及依据。
\n\n
## 本次就诊信息
\n
{{consult_context}}
\n\n
## 对话记录
\n
{{chat_history}}
\n\n
请给出鉴别诊断建议。
"
}
else
{
}
else
{
tmpl
.
Content
=
"你是一位经验丰富的临床药师AI助手,请根据患者信息给出用药建议
,包括药品、用法用量和注意事项
。"
tmpl
.
Content
=
"你是一位经验丰富的临床药师AI助手,请根据患者信息给出用药建议
。
\n\n
## 本次就诊信息
\n
{{consult_context}}
\n\n
## 对话记录
\n
{{chat_history}}
\n\n
请给出用药建议
。"
}
}
}
}
systemPrompt
:=
tmpl
.
Content
// === 通过 consultID 查询本次问诊关联的患者信息 ===
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{chief_complaint}}"
,
consult
.
ChiefComplaint
)
var
patient
model
.
User
s
.
db
.
Where
(
"id = ?"
,
consult
.
PatientID
)
.
First
(
&
patient
)
// 条件块替换辅助函数
var
patientProfile
model
.
PatientProfile
replaceBlock
:=
func
(
prompt
,
tag
,
value
string
)
string
{
s
.
db
.
Where
(
"user_id = ?"
,
consult
.
PatientID
)
.
First
(
&
patientProfile
)
openTag
:=
"{{#"
+
tag
+
"}}"
closeTag
:=
"{{/"
+
tag
+
"}}"
// 构建本次就诊完整上下文
if
value
!=
""
{
var
ctx
strings
.
Builder
prompt
=
strings
.
ReplaceAll
(
prompt
,
openTag
,
""
)
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 就诊流水号:%s
\n
"
,
consult
.
SerialNumber
))
prompt
=
strings
.
ReplaceAll
(
prompt
,
closeTag
,
""
)
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 问诊类型:%s
\n
"
,
consult
.
Type
))
prompt
=
strings
.
ReplaceAll
(
prompt
,
"{{"
+
tag
+
"}}"
,
value
)
if
patient
.
RealName
!=
""
{
}
else
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 患者姓名:%s
\n
"
,
patient
.
RealName
))
for
{
}
start
:=
strings
.
Index
(
prompt
,
openTag
)
if
patient
.
Gender
!=
""
{
if
start
==
-
1
{
gender
:=
patient
.
Gender
break
if
gender
==
"male"
{
}
gender
=
"男"
end
:=
strings
.
Index
(
prompt
,
closeTag
)
}
else
if
gender
==
"female"
{
if
end
==
-
1
{
gender
=
"女"
break
}
prompt
=
prompt
[
:
start
]
+
prompt
[
end
+
len
(
closeTag
)
:
]
}
}
}
return
prompt
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 性别:%s
\n
"
,
gender
))
}
if
patient
.
Age
>
0
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 年龄:%d岁
\n
"
,
patient
.
Age
))
}
if
consult
.
ChiefComplaint
!=
""
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 主诉:%s
\n
"
,
consult
.
ChiefComplaint
))
}
if
consult
.
MedicalHistory
!=
""
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 本次病史:%s
\n
"
,
consult
.
MedicalHistory
))
}
if
patientProfile
.
MedicalHistory
!=
""
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 既往病史:%s
\n
"
,
patientProfile
.
MedicalHistory
))
}
if
allergyHistory
!=
""
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 过敏史:%s
\n
"
,
allergyHistory
))
}
}
systemPrompt
=
replaceBlock
(
systemPrompt
,
"pre_consult_analysis"
,
preConsult
.
AIAnalysis
)
systemPrompt
=
replaceBlock
(
systemPrompt
,
"allergy_history"
,
allergyHistory
)
if
len
(
chronicDiseases
)
>
0
{
if
len
(
chronicDiseases
)
>
0
{
systemPrompt
=
replaceBlock
(
systemPrompt
,
"chronic_diseases"
,
strings
.
Join
(
chronicDiseases
,
"、"
))
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 慢性病史:%s
\n
"
,
strings
.
Join
(
chronicDiseases
,
"、"
)))
}
else
{
}
systemPrompt
=
replaceBlock
(
systemPrompt
,
"chronic_diseases"
,
""
)
if
preConsult
.
AIAnalysis
!=
""
{
ctx
.
WriteString
(
fmt
.
Sprintf
(
"- 预问诊分析:%s
\n
"
,
preConsult
.
AIAnalysis
))
}
}
// 构建对话记录
var
chatText
strings
.
Builder
var
chatText
strings
.
Builder
for
_
,
msg
:=
range
chatHistory
{
for
_
,
msg
:=
range
chatHistory
{
role
:=
msg
[
"role"
]
role
:=
msg
[
"role"
]
...
@@ -489,8 +501,32 @@ func (s *Service) buildTemplatePrompt(
...
@@ -489,8 +501,32 @@ func (s *Service) buildTemplatePrompt(
chatText
.
WriteString
(
msg
[
"content"
])
chatText
.
WriteString
(
msg
[
"content"
])
chatText
.
WriteString
(
"
\n
"
)
chatText
.
WriteString
(
"
\n
"
)
}
}
// 统一替换所有模板变量
systemPrompt
:=
tmpl
.
Content
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{consult_context}}"
,
ctx
.
String
())
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{serial_number}}"
,
consult
.
SerialNumber
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{consult_type}}"
,
consult
.
Type
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{patient_name}}"
,
patient
.
RealName
)
if
patient
.
Gender
==
"male"
{
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{patient_gender}}"
,
"男"
)
}
else
if
patient
.
Gender
==
"female"
{
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{patient_gender}}"
,
"女"
)
}
else
{
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{patient_gender}}"
,
patient
.
Gender
)
}
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{patient_age}}"
,
fmt
.
Sprintf
(
"%d"
,
patient
.
Age
))
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{chief_complaint}}"
,
consult
.
ChiefComplaint
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{medical_history}}"
,
consult
.
MedicalHistory
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{past_medical_history}}"
,
patientProfile
.
MedicalHistory
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{allergy_history}}"
,
allergyHistory
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{chronic_diseases}}"
,
strings
.
Join
(
chronicDiseases
,
"、"
))
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{pre_consult_analysis}}"
,
preConsult
.
AIAnalysis
)
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{chat_history}}"
,
chatText
.
String
())
systemPrompt
=
strings
.
ReplaceAll
(
systemPrompt
,
"{{chat_history}}"
,
chatText
.
String
())
log
.
Printf
(
"[AIAssist] 场景=%s 流水号=%s 患者=%s 主诉=%s 聊天记录=%d条"
,
scene
,
consult
.
SerialNumber
,
patient
.
RealName
,
consult
.
ChiefComplaint
,
len
(
chatHistory
))
return
systemPrompt
return
systemPrompt
}
}
...
...
server/pkg/middleware/cors.go
View file @
caca4568
...
@@ -2,43 +2,20 @@ package middleware
...
@@ -2,43 +2,20 @@ package middleware
import
(
import
(
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
"strings"
)
)
// 允许的源列表,生产环境应从配置读取
var
allowedOrigins
=
[]
string
{
"http://localhost:3000"
,
"http://localhost:5173"
,
"http://127.0.0.1:3000"
,
"http://127.0.0.1:5173"
,
}
func
CORS
()
gin
.
HandlerFunc
{
func
CORS
()
gin
.
HandlerFunc
{
return
func
(
c
*
gin
.
Context
)
{
return
func
(
c
*
gin
.
Context
)
{
origin
:=
c
.
Request
.
Header
.
Get
(
"Origin"
)
origin
:=
c
.
Request
.
Header
.
Get
(
"Origin"
)
// 验证Origin是否在允许列表中
if
origin
!=
""
{
allowed
:=
false
for
_
,
allowedOrigin
:=
range
allowedOrigins
{
if
origin
==
allowedOrigin
||
strings
.
HasPrefix
(
origin
,
allowedOrigin
)
{
allowed
=
true
break
}
}
if
allowed
{
c
.
Header
(
"Access-Control-Allow-Origin"
,
origin
)
c
.
Header
(
"Access-Control-Allow-Origin"
,
origin
)
c
.
Header
(
"Access-Control-Allow-Credentials"
,
"true"
)
c
.
Header
(
"Access-Control-Allow-Credentials"
,
"true"
)
}
else
if
origin
==
""
{
// 无Origin头的请求(如Postman),开发环境允许
c
.
Header
(
"Access-Control-Allow-Origin"
,
"*"
)
}
else
{
}
else
{
// 拒绝未授权的源
c
.
Header
(
"Access-Control-Allow-Origin"
,
"*"
)
c
.
AbortWithStatus
(
403
)
return
}
}
c
.
Header
(
"Access-Control-Allow-Methods"
,
"GET, POST, PUT, DELETE, OPTIONS"
)
c
.
Header
(
"Access-Control-Allow-Methods"
,
"GET, POST, PUT, DELETE, OPTIONS
, PATCH
"
)
c
.
Header
(
"Access-Control-Allow-Headers"
,
"Origin, Content-Type, Authorization, X-Requested-With"
)
c
.
Header
(
"Access-Control-Allow-Headers"
,
"Origin, Content-Type, Authorization, X-Requested-With"
)
c
.
Header
(
"Access-Control-Expose-Headers"
,
"Content-Length, Content-Type"
)
c
.
Header
(
"Access-Control-Expose-Headers"
,
"Content-Length, Content-Type"
)
...
...
server/pkg/websocket/handler.go
View file @
caca4568
...
@@ -17,27 +17,11 @@ import (
...
@@ -17,27 +17,11 @@ import (
"internet-hospital/pkg/utils"
"internet-hospital/pkg/utils"
)
)
var
allowedOrigins
=
[]
string
{
"http://localhost:3000"
,
"http://localhost:5173"
,
"http://127.0.0.1:3000"
,
"http://127.0.0.1:5173"
,
}
var
upgrader
=
websocket
.
Upgrader
{
var
upgrader
=
websocket
.
Upgrader
{
ReadBufferSize
:
1024
,
ReadBufferSize
:
1024
,
WriteBufferSize
:
1024
,
WriteBufferSize
:
1024
,
CheckOrigin
:
func
(
r
*
http
.
Request
)
bool
{
CheckOrigin
:
func
(
r
*
http
.
Request
)
bool
{
origin
:=
r
.
Header
.
Get
(
"Origin"
)
return
true
if
origin
==
""
{
return
true
// 无Origin头(如原生应用)
}
for
_
,
allowed
:=
range
allowedOrigins
{
if
origin
==
allowed
||
strings
.
HasPrefix
(
origin
,
allowed
)
{
return
true
}
}
return
false
},
},
}
}
...
...
server/pkg/workflow/engine.go
View file @
caca4568
...
@@ -149,11 +149,11 @@ func (e *Engine) Execute(ctx context.Context, workflowID string, input map[strin
...
@@ -149,11 +149,11 @@ func (e *Engine) Execute(ctx context.Context, workflowID string, input map[strin
outputJSON
,
_
:=
json
.
Marshal
(
output
)
outputJSON
,
_
:=
json
.
Marshal
(
output
)
completedAt
:=
time
.
Now
()
completedAt
:=
time
.
Now
()
db
.
Model
(
&
exec
)
.
Updates
(
map
[
string
]
interface
{}{
db
.
Model
(
&
model
.
WorkflowExecution
{})
.
Where
(
"execution_id = ?"
,
executionID
)
.
Updates
(
map
[
string
]
interface
{}{
"status"
:
status
,
"status"
:
status
,
"output"
:
string
(
outputJSON
),
"output"
:
string
(
outputJSON
),
"error_message"
:
errMsg
,
"error_message"
:
errMsg
,
"completed_at"
:
&
completedAt
,
"completed_at"
:
&
completedAt
,
})
})
return
executionID
,
err
return
executionID
,
err
...
@@ -230,6 +230,20 @@ func (e *Engine) executeTool(ctx context.Context, node *Node, execCtx *Execution
...
@@ -230,6 +230,20 @@ func (e *Engine) executeTool(ctx context.Context, node *Node, execCtx *Execution
params
,
_
:=
node
.
Config
[
"params"
]
.
(
map
[
string
]
interface
{})
params
,
_
:=
node
.
Config
[
"params"
]
.
(
map
[
string
]
interface
{})
if
params
==
nil
{
if
params
==
nil
{
params
=
execCtx
.
Variables
params
=
execCtx
.
Variables
}
else
{
// 替换 params 中的 {{var}} 模板变量
resolved
:=
make
(
map
[
string
]
interface
{},
len
(
params
))
for
k
,
v
:=
range
params
{
if
s
,
ok
:=
v
.
(
string
);
ok
{
for
varK
,
varV
:=
range
execCtx
.
Variables
{
s
=
replaceAll
(
s
,
"{{"
+
varK
+
"}}"
,
fmt
.
Sprintf
(
"%v"
,
varV
))
}
resolved
[
k
]
=
s
}
else
{
resolved
[
k
]
=
v
}
}
params
=
resolved
}
}
return
tool
.
Execute
(
ctx
,
params
)
return
tool
.
Execute
(
ctx
,
params
)
}
}
...
...
web/src/api/admin.ts
View file @
caca4568
...
@@ -273,6 +273,9 @@ export const adminApi = {
...
@@ -273,6 +273,9 @@ export const adminApi = {
resetPatientPassword
:
(
patientId
:
string
)
=>
resetPatientPassword
:
(
patientId
:
string
)
=>
post
<
null
>
(
`/admin/patients/
${
patientId
}
/reset-password`
),
post
<
null
>
(
`/admin/patients/
${
patientId
}
/reset-password`
),
changePatientPassword
:
(
patientId
:
string
,
password
:
string
)
=>
post
<
null
>
(
`/admin/patients/
${
patientId
}
/change-password`
,
{
password
}),
// === 医生管理 ===
// === 医生管理 ===
getDoctorList
:
(
params
:
UserListParams
)
=>
getDoctorList
:
(
params
:
UserListParams
)
=>
get
<
PaginatedResponse
<
DoctorItem
>>
(
'
/admin/doctors
'
,
{
params
}),
get
<
PaginatedResponse
<
DoctorItem
>>
(
'
/admin/doctors
'
,
{
params
}),
...
@@ -307,6 +310,9 @@ export const adminApi = {
...
@@ -307,6 +310,9 @@ export const adminApi = {
resetDoctorPassword
:
(
doctorId
:
string
)
=>
resetDoctorPassword
:
(
doctorId
:
string
)
=>
post
<
null
>
(
`/admin/doctors/
${
doctorId
}
/reset-password`
),
post
<
null
>
(
`/admin/doctors/
${
doctorId
}
/reset-password`
),
changeDoctorPassword
:
(
doctorId
:
string
,
password
:
string
)
=>
post
<
null
>
(
`/admin/doctors/
${
doctorId
}
/change-password`
,
{
password
}),
// === 管理员管理 ===
// === 管理员管理 ===
getAdminList
:
(
params
:
UserListParams
)
=>
getAdminList
:
(
params
:
UserListParams
)
=>
get
<
PaginatedResponse
<
UserInfo
>>
(
'
/admin/admins
'
,
{
params
}),
get
<
PaginatedResponse
<
UserInfo
>>
(
'
/admin/admins
'
,
{
params
}),
...
@@ -326,6 +332,9 @@ export const adminApi = {
...
@@ -326,6 +332,9 @@ export const adminApi = {
resetAdminPassword
:
(
adminId
:
string
)
=>
resetAdminPassword
:
(
adminId
:
string
)
=>
post
<
null
>
(
`/admin/admins/
${
adminId
}
/reset-password`
),
post
<
null
>
(
`/admin/admins/
${
adminId
}
/reset-password`
),
changeAdminPassword
:
(
adminId
:
string
,
password
:
string
)
=>
post
<
null
>
(
`/admin/admins/
${
adminId
}
/change-password`
,
{
password
}),
// === 医生审核 ===
// === 医生审核 ===
getDoctorReviewList
:
(
status
?:
string
)
=>
getDoctorReviewList
:
(
status
?:
string
)
=>
get
<
PaginatedResponse
<
DoctorReviewItem
>>
(
'
/admin/doctor-reviews
'
,
{
params
:
{
status
}
}),
get
<
PaginatedResponse
<
DoctorReviewItem
>>
(
'
/admin/doctor-reviews
'
,
{
params
:
{
status
}
}),
...
...
web/src/app/(main)/admin/admins/page.tsx
View file @
caca4568
'
use client
'
;
'
use client
'
;
import
React
,
{
useState
,
useEffect
}
from
'
react
'
;
import
React
,
{
useState
,
useEffect
}
from
'
react
'
;
import
{
Card
,
Input
,
Button
,
Space
,
Tag
,
Avatar
,
Modal
,
message
,
Typography
,
Form
}
from
'
antd
'
;
import
{
Card
,
Input
,
Button
,
Space
,
Tag
,
Avatar
,
Modal
,
message
,
Typography
,
Form
,
Dropdown
}
from
'
antd
'
;
import
{
DrawerForm
,
ProFormText
,
ProTable
}
from
'
@ant-design/pro-components
'
;
import
{
DrawerForm
,
ProFormText
,
ProTable
}
from
'
@ant-design/pro-components
'
;
import
{
SearchOutlined
,
UserOutlined
,
ReloadOutlined
,
PlusOutlined
,
EditOutlined
,
DeleteOutlined
}
from
'
@ant-design/icons
'
;
import
{
SearchOutlined
,
UserOutlined
,
ReloadOutlined
,
PlusOutlined
,
EditOutlined
,
DeleteOutlined
,
MoreOutlined
,
KeyOutlined
,
LockOutlined
}
from
'
@ant-design/icons
'
;
import
{
adminApi
}
from
'
@/api/admin
'
;
import
{
adminApi
}
from
'
@/api/admin
'
;
import
type
{
UserInfo
}
from
'
@/api/user
'
;
import
type
{
UserInfo
}
from
'
@/api/user
'
;
...
@@ -22,6 +22,9 @@ const AdminAdminsPage: React.FC = () => {
...
@@ -22,6 +22,9 @@ const AdminAdminsPage: React.FC = () => {
const
[
editDrawerVisible
,
setEditDrawerVisible
]
=
useState
(
false
);
const
[
editDrawerVisible
,
setEditDrawerVisible
]
=
useState
(
false
);
const
[
editLoading
,
setEditLoading
]
=
useState
(
false
);
const
[
editLoading
,
setEditLoading
]
=
useState
(
false
);
const
[
editingAdmin
,
setEditingAdmin
]
=
useState
<
UserInfo
|
null
>
(
null
);
const
[
editingAdmin
,
setEditingAdmin
]
=
useState
<
UserInfo
|
null
>
(
null
);
const
[
pwdModalVisible
,
setPwdModalVisible
]
=
useState
(
false
);
const
[
pwdRecord
,
setPwdRecord
]
=
useState
<
UserInfo
|
null
>
(
null
);
const
[
newPassword
,
setNewPassword
]
=
useState
(
''
);
const
fetchAdmins
=
async
()
=>
{
const
fetchAdmins
=
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
...
@@ -102,6 +105,26 @@ const AdminAdminsPage: React.FC = () => {
...
@@ -102,6 +105,26 @@ const AdminAdminsPage: React.FC = () => {
}
}
};
};
const
handleChangePassword
=
async
()
=>
{
if
(
!
pwdRecord
||
!
newPassword
)
{
message
.
error
(
'
请输入新密码
'
);
return
;
}
if
(
newPassword
.
length
<
6
)
{
message
.
error
(
'
密码长度不能少于6位
'
);
return
;
}
try
{
await
adminApi
.
changeAdminPassword
(
pwdRecord
.
id
,
newPassword
);
message
.
success
(
'
密码修改成功
'
);
setPwdModalVisible
(
false
);
setNewPassword
(
''
);
setPwdRecord
(
null
);
}
catch
{
message
.
error
(
'
修改密码失败
'
);
}
};
const
handleDeleteAdmin
=
(
record
:
UserInfo
)
=>
{
const
handleDeleteAdmin
=
(
record
:
UserInfo
)
=>
{
Modal
.
confirm
({
Modal
.
confirm
({
title
:
'
确认删除该管理员?
'
,
title
:
'
确认删除该管理员?
'
,
...
@@ -192,12 +215,10 @@ const AdminAdminsPage: React.FC = () => {
...
@@ -192,12 +215,10 @@ const AdminAdminsPage: React.FC = () => {
{
{
title
:
'
操作
'
,
title
:
'
操作
'
,
key
:
'
action
'
,
key
:
'
action
'
,
width
:
2
8
0
,
width
:
2
0
0
,
render
:
(
_
,
record
)
=>
(
render
:
(
_
,
record
)
=>
(
<
Space
>
<
Space
size=
{
4
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EditOutlined
/>
}
onClick=
{
()
=>
handleEditAdmin
(
record
)
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EditOutlined
/>
}
onClick=
{
()
=>
handleEditAdmin
(
record
)
}
>
编辑
</
Button
>
编辑
</
Button
>
<
Button
<
Button
type=
"link"
type=
"link"
size=
"small"
size=
"small"
...
@@ -206,12 +227,34 @@ const AdminAdminsPage: React.FC = () => {
...
@@ -206,12 +227,34 @@ const AdminAdminsPage: React.FC = () => {
>
>
{
record
.
status
===
'
active
'
?
'
禁用
'
:
'
启用
'
}
{
record
.
status
===
'
active
'
?
'
禁用
'
:
'
启用
'
}
</
Button
>
</
Button
>
<
Button
type=
"link"
size=
"small"
onClick=
{
()
=>
handleResetPassword
(
record
)
}
>
<
Dropdown
重置密码
menu=
{
{
</
Button
>
items
:
[
<
Button
type=
"link"
size=
"small"
danger
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
handleDeleteAdmin
(
record
)
}
>
{
删除
key
:
'
changePwd
'
,
</
Button
>
icon
:
<
KeyOutlined
/>,
label
:
'
修改密码
'
,
onClick
:
()
=>
{
setPwdRecord
(
record
);
setPwdModalVisible
(
true
);
},
},
{
key
:
'
resetPwd
'
,
icon
:
<
LockOutlined
/>,
label
:
'
重置密码
'
,
onClick
:
()
=>
handleResetPassword
(
record
),
},
{
type
:
'
divider
'
},
{
key
:
'
delete
'
,
icon
:
<
DeleteOutlined
/>,
label
:
'
删除
'
,
danger
:
true
,
onClick
:
()
=>
handleDeleteAdmin
(
record
),
},
],
}
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
MoreOutlined
/>
}
/>
</
Dropdown
>
</
Space
>
</
Space
>
),
),
},
},
...
@@ -258,6 +301,24 @@ const AdminAdminsPage: React.FC = () => {
...
@@ -258,6 +301,24 @@ const AdminAdminsPage: React.FC = () => {
/>
/>
</
Card
>
</
Card
>
<
Modal
title=
"修改密码"
open=
{
pwdModalVisible
}
onOk=
{
handleChangePassword
}
onCancel=
{
()
=>
{
setPwdModalVisible
(
false
);
setNewPassword
(
''
);
setPwdRecord
(
null
);
}
}
okText=
"确认修改"
>
<
div
style=
{
{
marginBottom
:
8
,
color
:
'
#8c8c8c
'
}
}
>
管理员:
{
pwdRecord
?.
real_name
||
pwdRecord
?.
phone
}
</
div
>
<
Input
.
Password
placeholder=
"请输入新密码(至少6位)"
value=
{
newPassword
}
onChange=
{
(
e
)
=>
setNewPassword
(
e
.
target
.
value
)
}
minLength=
{
6
}
/>
</
Modal
>
<
DrawerForm
<
DrawerForm
title=
"添加管理员"
title=
"添加管理员"
open=
{
addDrawerVisible
}
open=
{
addDrawerVisible
}
...
...
web/src/app/(main)/admin/dashboard/page.tsx
View file @
caca4568
...
@@ -139,7 +139,7 @@ const AdminDashboardPage: React.FC = () => {
...
@@ -139,7 +139,7 @@ const AdminDashboardPage: React.FC = () => {
<
Row
gutter=
{
[
16
,
16
]
}
>
<
Row
gutter=
{
[
16
,
16
]
}
>
{
[
{
[
{
title
:
'
注册用户
'
,
value
:
stats
.
total_users
,
prefix
:
''
,
icon
:
<
TeamOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#0D9488
'
}
}
/>,
color
:
'
#0D9488
'
,
bg
:
'
#F0FDFA
'
,
extra
:
''
},
{
title
:
'
注册用户
'
,
value
:
stats
.
total_users
,
prefix
:
''
,
icon
:
<
TeamOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#0D9488
'
}
}
/>,
color
:
'
#0D9488
'
,
bg
:
'
#F0FDFA
'
,
extra
:
''
},
{
title
:
'
注册医生
'
,
value
:
stats
.
total_doctors
,
prefix
:
''
,
icon
:
<
MedicineBoxOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#52c41a
'
}
}
/>,
color
:
'
#52c41a
'
,
bg
:
'
#f6ffed
'
,
extra
:
`在线 ${stats.online_doctors}`
},
{
title
:
'
注册医生
'
,
value
:
stats
.
total_doctors
,
prefix
:
''
,
icon
:
<
MedicineBoxOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#52c41a
'
}
}
/>,
color
:
'
#52c41a
'
,
bg
:
'
#f6ffed
'
,
extra
:
''
,
suffix
:
<
span
style=
{
{
fontSize
:
12
,
color
:
'
#bfbfbf
'
,
fontWeight
:
400
}
}
>
/ 在线
{
stats
.
online_doctors
}
</
span
>
},
{
title
:
'
今日问诊
'
,
value
:
stats
.
today_consultations
,
prefix
:
''
,
icon
:
<
MessageOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#0891B2
'
}
}
/>,
color
:
'
#0891B2
'
,
bg
:
'
#ECFEFF
'
,
extra
:
''
},
{
title
:
'
今日问诊
'
,
value
:
stats
.
today_consultations
,
prefix
:
''
,
icon
:
<
MessageOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#0891B2
'
}
}
/>,
color
:
'
#0891B2
'
,
bg
:
'
#ECFEFF
'
,
extra
:
''
},
{
title
:
'
今日收入
'
,
value
:
(
stats
.
revenue_today
/
100
),
prefix
:
'
¥
'
,
icon
:
<
DollarOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#fa8c16
'
}
}
/>,
color
:
'
#fa8c16
'
,
bg
:
'
#fff7e6
'
,
extra
:
''
},
{
title
:
'
今日收入
'
,
value
:
(
stats
.
revenue_today
/
100
),
prefix
:
'
¥
'
,
icon
:
<
DollarOutlined
style=
{
{
fontSize
:
18
,
color
:
'
#fa8c16
'
}
}
/>,
color
:
'
#fa8c16
'
,
bg
:
'
#fff7e6
'
,
extra
:
''
},
].
map
((
item
,
i
)
=>
(
].
map
((
item
,
i
)
=>
(
...
@@ -150,7 +150,7 @@ const AdminDashboardPage: React.FC = () => {
...
@@ -150,7 +150,7 @@ const AdminDashboardPage: React.FC = () => {
title
:
item
.
title
,
title
:
item
.
title
,
value
:
item
.
value
,
value
:
item
.
value
,
prefix
:
item
.
prefix
||
undefined
,
prefix
:
item
.
prefix
||
undefined
,
description
:
item
.
extra
?
<
Text
style=
{
{
fontSize
:
12
,
color
:
'
#bfbfbf
'
}
}
>
{
item
.
extra
}
</
Text
>
:
undefined
,
suffix
:
(
item
as
any
).
suffix
||
undefined
,
icon
:
(
icon
:
(
<
div
style=
{
{
width
:
36
,
height
:
36
,
borderRadius
:
10
,
background
:
item
.
bg
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
}
}
>
<
div
style=
{
{
width
:
36
,
height
:
36
,
borderRadius
:
10
,
background
:
item
.
bg
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
}
}
>
{
item
.
icon
}
{
item
.
icon
}
...
...
web/src/app/(main)/admin/doctors/page.tsx
View file @
caca4568
...
@@ -4,11 +4,12 @@ import React, { useRef, useState, useEffect } from 'react';
...
@@ -4,11 +4,12 @@ import React, { useRef, useState, useEffect } from 'react';
import
{
useSearchParams
}
from
'
next/navigation
'
;
import
{
useSearchParams
}
from
'
next/navigation
'
;
import
{
import
{
Tag
,
Typography
,
Space
,
Button
,
Avatar
,
Modal
,
Drawer
,
App
,
Tag
,
Typography
,
Space
,
Button
,
Avatar
,
Modal
,
Drawer
,
App
,
Input
,
Descriptions
,
Input
,
Descriptions
,
Dropdown
,
}
from
'
antd
'
;
}
from
'
antd
'
;
import
{
import
{
UserOutlined
,
CheckCircleOutlined
,
UserOutlined
,
CheckCircleOutlined
,
EyeOutlined
,
EditOutlined
,
PlusOutlined
,
DeleteOutlined
,
EyeOutlined
,
EditOutlined
,
PlusOutlined
,
DeleteOutlined
,
MoreOutlined
,
KeyOutlined
,
LockOutlined
,
}
from
'
@ant-design/icons
'
;
}
from
'
@ant-design/icons
'
;
import
{
import
{
ProTable
,
DrawerForm
,
ProFormText
,
ProFormSelect
,
ProFormTextArea
,
ProTable
,
DrawerForm
,
ProFormText
,
ProFormSelect
,
ProFormTextArea
,
...
@@ -49,6 +50,11 @@ const AdminDoctorsPage: React.FC = () => {
...
@@ -49,6 +50,11 @@ const AdminDoctorsPage: React.FC = () => {
const
[
reviewModalVisible
,
setReviewModalVisible
]
=
useState
(
false
);
const
[
reviewModalVisible
,
setReviewModalVisible
]
=
useState
(
false
);
const
[
rejectReason
,
setRejectReason
]
=
useState
(
''
);
const
[
rejectReason
,
setRejectReason
]
=
useState
(
''
);
// Change password state
const
[
pwdModalVisible
,
setPwdModalVisible
]
=
useState
(
false
);
const
[
pwdRecord
,
setPwdRecord
]
=
useState
<
DoctorItem
|
null
>
(
null
);
const
[
newPassword
,
setNewPassword
]
=
useState
(
''
);
// Department options from backend
// Department options from backend
const
[
departmentOptions
,
setDepartmentOptions
]
=
useState
<
DeptOption
[]
>
([]);
const
[
departmentOptions
,
setDepartmentOptions
]
=
useState
<
DeptOption
[]
>
([]);
...
@@ -160,6 +166,26 @@ const AdminDoctorsPage: React.FC = () => {
...
@@ -160,6 +166,26 @@ const AdminDoctorsPage: React.FC = () => {
});
});
};
};
const
handleChangePassword
=
async
()
=>
{
if
(
!
pwdRecord
||
!
newPassword
)
{
message
.
error
(
'
请输入新密码
'
);
return
;
}
if
(
newPassword
.
length
<
6
)
{
message
.
error
(
'
密码长度不能少于6位
'
);
return
;
}
try
{
await
adminApi
.
changeDoctorPassword
(
pwdRecord
.
user_id
,
newPassword
);
message
.
success
(
'
密码修改成功
'
);
setPwdModalVisible
(
false
);
setNewPassword
(
''
);
setPwdRecord
(
null
);
}
catch
{
message
.
error
(
'
修改密码失败
'
);
}
};
const
handleEditDoctor
=
(
record
:
DoctorItem
)
=>
{
const
handleEditDoctor
=
(
record
:
DoctorItem
)
=>
{
setEditingDoctor
(
record
);
setEditingDoctor
(
record
);
setEditModalVisible
(
true
);
setEditModalVisible
(
true
);
...
@@ -257,34 +283,48 @@ const AdminDoctorsPage: React.FC = () => {
...
@@ -257,34 +283,48 @@ const AdminDoctorsPage: React.FC = () => {
{
{
title
:
'
操作
'
,
title
:
'
操作
'
,
valueType
:
'
option
'
,
valueType
:
'
option
'
,
width
:
32
0
,
width
:
24
0
,
render
:
(
_
,
record
)
=>
(
render
:
(
_
,
record
)
=>
(
<
Space
size=
{
0
}
>
<
Space
size=
{
4
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EyeOutlined
/>
}
onClick=
{
()
=>
handleViewDetail
(
record
)
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EyeOutlined
/>
}
onClick=
{
()
=>
handleViewDetail
(
record
)
}
>
详情
</
Button
>
详情
</
Button
>
{
record
.
review_status
===
'
pending
'
&&
(
{
record
.
review_status
===
'
pending
'
&&
(
<
Button
type=
"link"
size=
"small"
icon=
{
<
CheckCircleOutlined
/>
}
onClick=
{
()
=>
handleReview
(
record
)
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
CheckCircleOutlined
/>
}
onClick=
{
()
=>
handleReview
(
record
)
}
>
审核
</
Button
>
审核
</
Button
>
)
}
)
}
<
Button
type=
"link"
size=
"small"
icon=
{
<
EditOutlined
/>
}
onClick=
{
()
=>
handleEditDoctor
(
record
)
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EditOutlined
/>
}
onClick=
{
()
=>
handleEditDoctor
(
record
)
}
>
编辑
</
Button
>
编辑
<
Dropdown
</
Button
>
menu=
{
{
<
Button
items
:
[
type=
"link"
{
size=
"small"
key
:
'
toggle
'
,
danger=
{
record
.
user_status
===
'
active
'
}
label
:
record
.
user_status
===
'
active
'
?
'
禁用
'
:
'
启用
'
,
onClick=
{
()
=>
handleToggleStatus
(
record
)
}
danger
:
record
.
user_status
===
'
active
'
,
onClick
:
()
=>
handleToggleStatus
(
record
),
},
{
key
:
'
changePwd
'
,
icon
:
<
KeyOutlined
/>,
label
:
'
修改密码
'
,
onClick
:
()
=>
{
setPwdRecord
(
record
);
setPwdModalVisible
(
true
);
},
},
{
key
:
'
resetPwd
'
,
icon
:
<
LockOutlined
/>,
label
:
'
重置密码
'
,
onClick
:
()
=>
handleResetPassword
(
record
),
},
{
type
:
'
divider
'
},
{
key
:
'
delete
'
,
icon
:
<
DeleteOutlined
/>,
label
:
'
删除
'
,
danger
:
true
,
onClick
:
()
=>
handleDelete
(
record
),
},
],
}
}
>
>
{
record
.
user_status
===
'
active
'
?
'
禁用
'
:
'
启用
'
}
<
Button
type=
"link"
size=
"small"
icon=
{
<
MoreOutlined
/>
}
/>
</
Button
>
</
Dropdown
>
<
Button
type=
"link"
size=
"small"
onClick=
{
()
=>
handleResetPassword
(
record
)
}
>
重置密码
</
Button
>
<
Button
type=
"link"
size=
"small"
danger
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
handleDelete
(
record
)
}
>
删除
</
Button
>
</
Space
>
</
Space
>
),
),
},
},
...
@@ -514,6 +554,25 @@ const AdminDoctorsPage: React.FC = () => {
...
@@ -514,6 +554,25 @@ const AdminDoctorsPage: React.FC = () => {
/>
/>
</
DrawerForm
>
</
DrawerForm
>
{
/* ========== Change Password Modal ========== */
}
<
Modal
title=
"修改密码"
open=
{
pwdModalVisible
}
onOk=
{
handleChangePassword
}
onCancel=
{
()
=>
{
setPwdModalVisible
(
false
);
setNewPassword
(
''
);
setPwdRecord
(
null
);
}
}
okText=
"确认修改"
>
<
div
style=
{
{
marginBottom
:
8
,
color
:
'
#8c8c8c
'
}
}
>
医生:
{
pwdRecord
?.
name
}
</
div
>
<
Input
.
Password
placeholder=
"请输入新密码(至少6位)"
value=
{
newPassword
}
onChange=
{
(
e
)
=>
setNewPassword
(
e
.
target
.
value
)
}
minLength=
{
6
}
/>
</
Modal
>
{
/* ========== Detail Drawer ========== */
}
{
/* ========== Detail Drawer ========== */
}
<
Drawer
<
Drawer
title=
"医生详情"
title=
"医生详情"
...
...
web/src/app/(main)/admin/patients/page.tsx
View file @
caca4568
...
@@ -2,9 +2,10 @@
...
@@ -2,9 +2,10 @@
import
React
,
{
useState
,
useEffect
,
useRef
}
from
'
react
'
;
import
React
,
{
useState
,
useEffect
,
useRef
}
from
'
react
'
;
import
{
useSearchParams
}
from
'
next/navigation
'
;
import
{
useSearchParams
}
from
'
next/navigation
'
;
import
{
Button
,
Space
,
Tag
,
Avatar
,
Modal
,
Typography
,
App
}
from
'
antd
'
;
import
{
Button
,
Space
,
Tag
,
Avatar
,
Modal
,
Typography
,
App
,
Dropdown
,
Input
}
from
'
antd
'
;
import
{
import
{
UserOutlined
,
PlusOutlined
,
EditOutlined
,
DeleteOutlined
,
UserOutlined
,
PlusOutlined
,
EditOutlined
,
DeleteOutlined
,
MoreOutlined
,
KeyOutlined
,
LockOutlined
,
StopOutlined
,
}
from
'
@ant-design/icons
'
;
}
from
'
@ant-design/icons
'
;
import
{
import
{
ProTable
,
DrawerForm
,
ProFormText
,
ProFormSelect
,
ProFormDigit
,
ProTable
,
DrawerForm
,
ProFormText
,
ProFormSelect
,
ProFormDigit
,
...
@@ -27,6 +28,11 @@ const AdminPatientsPage: React.FC = () => {
...
@@ -27,6 +28,11 @@ const AdminPatientsPage: React.FC = () => {
const
[
editModalVisible
,
setEditModalVisible
]
=
useState
(
false
);
const
[
editModalVisible
,
setEditModalVisible
]
=
useState
(
false
);
const
[
editingRecord
,
setEditingRecord
]
=
useState
<
UserInfo
|
null
>
(
null
);
const
[
editingRecord
,
setEditingRecord
]
=
useState
<
UserInfo
|
null
>
(
null
);
// Change password state
const
[
pwdModalVisible
,
setPwdModalVisible
]
=
useState
(
false
);
const
[
pwdRecord
,
setPwdRecord
]
=
useState
<
UserInfo
|
null
>
(
null
);
const
[
newPassword
,
setNewPassword
]
=
useState
(
''
);
// ?action=add auto-open add modal
// ?action=add auto-open add modal
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
searchParams
.
get
(
'
action
'
)
===
'
add
'
)
{
if
(
searchParams
.
get
(
'
action
'
)
===
'
add
'
)
{
...
@@ -71,6 +77,26 @@ const AdminPatientsPage: React.FC = () => {
...
@@ -71,6 +77,26 @@ const AdminPatientsPage: React.FC = () => {
setEditModalVisible
(
true
);
setEditModalVisible
(
true
);
};
};
const
handleChangePassword
=
async
()
=>
{
if
(
!
pwdRecord
||
!
newPassword
)
{
message
.
error
(
'
请输入新密码
'
);
return
;
}
if
(
newPassword
.
length
<
6
)
{
message
.
error
(
'
密码长度不能少于6位
'
);
return
;
}
try
{
await
adminApi
.
changePatientPassword
(
pwdRecord
.
id
,
newPassword
);
message
.
success
(
'
密码修改成功
'
);
setPwdModalVisible
(
false
);
setNewPassword
(
''
);
setPwdRecord
(
null
);
}
catch
{
message
.
error
(
'
修改密码失败
'
);
}
};
const
handleDelete
=
(
record
:
UserInfo
)
=>
{
const
handleDelete
=
(
record
:
UserInfo
)
=>
{
Modal
.
confirm
({
Modal
.
confirm
({
title
:
'
确认删除该患者?
'
,
title
:
'
确认删除该患者?
'
,
...
@@ -145,18 +171,46 @@ const AdminPatientsPage: React.FC = () => {
...
@@ -145,18 +171,46 @@ const AdminPatientsPage: React.FC = () => {
{
{
title
:
'
操作
'
,
title
:
'
操作
'
,
valueType
:
'
option
'
,
valueType
:
'
option
'
,
width
:
2
6
0
,
width
:
2
0
0
,
render
:
(
_
,
record
)
=>
(
render
:
(
_
,
record
)
=>
(
<
Space
size=
{
0
}
>
<
Space
size=
{
4
}
>
<
a
onClick=
{
()
=>
handleEdit
(
record
)
}
><
EditOutlined
/>
编辑
</
a
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
EditOutlined
/>
}
onClick=
{
()
=>
handleEdit
(
record
)
}
>
编辑
</
Button
>
<
a
<
Button
style=
{
record
.
status
===
'
active
'
?
{
color
:
'
#ff4d4f
'
}
:
undefined
}
type=
"link"
size=
"small"
danger=
{
record
.
status
===
'
active
'
}
onClick=
{
()
=>
handleToggleStatus
(
record
)
}
onClick=
{
()
=>
handleToggleStatus
(
record
)
}
>
>
{
record
.
status
===
'
active
'
?
'
禁用
'
:
'
启用
'
}
{
record
.
status
===
'
active
'
?
'
禁用
'
:
'
启用
'
}
</
a
>
</
Button
>
<
a
onClick=
{
()
=>
handleResetPassword
(
record
)
}
>
重置密码
</
a
>
<
Dropdown
<
a
style=
{
{
color
:
'
#ff4d4f
'
}
}
onClick=
{
()
=>
handleDelete
(
record
)
}
><
DeleteOutlined
/>
删除
</
a
>
menu=
{
{
items
:
[
{
key
:
'
changePwd
'
,
icon
:
<
KeyOutlined
/>,
label
:
'
修改密码
'
,
onClick
:
()
=>
{
setPwdRecord
(
record
);
setPwdModalVisible
(
true
);
},
},
{
key
:
'
resetPwd
'
,
icon
:
<
LockOutlined
/>,
label
:
'
重置密码
'
,
onClick
:
()
=>
handleResetPassword
(
record
),
},
{
type
:
'
divider
'
},
{
key
:
'
delete
'
,
icon
:
<
DeleteOutlined
/>,
label
:
'
删除
'
,
danger
:
true
,
onClick
:
()
=>
handleDelete
(
record
),
},
],
}
}
>
<
Button
type=
"link"
size=
"small"
icon=
{
<
MoreOutlined
/>
}
/>
</
Dropdown
>
</
Space
>
</
Space
>
),
),
},
},
...
@@ -254,6 +308,25 @@ const AdminPatientsPage: React.FC = () => {
...
@@ -254,6 +308,25 @@ const AdminPatientsPage: React.FC = () => {
/>
/>
</
DrawerForm
>
</
DrawerForm
>
{
/* Change Password Modal */
}
<
Modal
title=
"修改密码"
open=
{
pwdModalVisible
}
onOk=
{
handleChangePassword
}
onCancel=
{
()
=>
{
setPwdModalVisible
(
false
);
setNewPassword
(
''
);
setPwdRecord
(
null
);
}
}
okText=
"确认修改"
>
<
div
style=
{
{
marginBottom
:
8
,
color
:
'
#8c8c8c
'
}
}
>
患者:
{
pwdRecord
?.
real_name
||
pwdRecord
?.
phone
}
</
div
>
<
Input
.
Password
placeholder=
"请输入新密码(至少6位)"
value=
{
newPassword
}
onChange=
{
(
e
)
=>
setNewPassword
(
e
.
target
.
value
)
}
minLength=
{
6
}
/>
</
Modal
>
{
/* Edit Patient DrawerForm */
}
{
/* Edit Patient DrawerForm */
}
<
DrawerForm
<
DrawerForm
title=
"编辑患者"
title=
"编辑患者"
...
...
web/src/app/(main)/doctor/profile/page.tsx
View file @
caca4568
...
@@ -103,7 +103,7 @@ const DoctorProfilePage: React.FC = () => {
...
@@ -103,7 +103,7 @@ const DoctorProfilePage: React.FC = () => {
<
Descriptions
.
Item
label=
"医院"
>
{
profile
.
hospital
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"医院"
>
{
profile
.
hospital
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"科室"
>
{
profile
.
department_name
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"科室"
>
{
profile
.
department_name
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"问诊价格"
>
<
Descriptions
.
Item
label=
"问诊价格"
>
<
span
style=
{
{
fontSize
:
16
,
fontWeight
:
700
,
color
:
'
#52c41a
'
}
}
>
¥
{
(
profile
.
price
/
100
).
toFixed
(
0
)
}
</
span
>
<
span
style=
{
{
fontSize
:
16
,
fontWeight
:
700
,
color
:
'
#52c41a
'
}
}
>
¥
{
profile
.
price
}
</
span
>
<
span
style=
{
{
fontSize
:
12
,
color
:
'
#8c8c8c
'
}
}
>
/次
</
span
>
<
span
style=
{
{
fontSize
:
12
,
color
:
'
#8c8c8c
'
}
}
>
/次
</
span
>
</
Descriptions
.
Item
>
</
Descriptions
.
Item
>
</
Descriptions
>
</
Descriptions
>
...
...
web/src/app/(main)/patient/doctors/[id]/page.tsx
View file @
caca4568
...
@@ -60,7 +60,7 @@ const DoctorDetailPage: React.FC = () => {
...
@@ -60,7 +60,7 @@ const DoctorDetailPage: React.FC = () => {
<
Col
>
<
Col
>
<
div
className=
"text-right"
>
<
div
className=
"text-right"
>
<
div
className=
"mb-2"
>
<
div
className=
"mb-2"
>
<
Text
strong
className=
"text-xl! text-red-500!"
>
¥
{
((
doctor
.
price
||
0
)
/
100
).
toFixed
(
0
)
}
</
Text
>
<
Text
strong
className=
"text-xl! text-red-500!"
>
¥
{
doctor
.
price
||
0
}
</
Text
>
<
Text
type=
"secondary"
className=
"text-xs!"
>
/次
</
Text
>
<
Text
type=
"secondary"
className=
"text-xs!"
>
/次
</
Text
>
</
div
>
</
div
>
<
Space
>
<
Space
>
...
...
web/src/app/(main)/patient/pre-consult/ReportSection.tsx
View file @
caca4568
...
@@ -111,7 +111,7 @@ const DoctorRecommendations: React.FC<DoctorRecommendationsProps> = ({
...
@@ -111,7 +111,7 @@ const DoctorRecommendations: React.FC<DoctorRecommendationsProps> = ({
<
Space
split=
{
<
Divider
type=
"vertical"
/>
}
>
<
Space
split=
{
<
Divider
type=
"vertical"
/>
}
>
<
span
>
评分
{
doctor
.
rating
}
</
span
>
<
span
>
评分
{
doctor
.
rating
}
</
span
>
<
span
>
问诊
{
doctor
.
consult_count
}
次
</
span
>
<
span
>
问诊
{
doctor
.
consult_count
}
次
</
span
>
<
span
style=
{
{
color
:
'
#fa8c16
'
}
}
>
¥
{
(
doctor
.
price
/
100
).
toFixed
(
0
)
}
/次
</
span
>
<
span
style=
{
{
color
:
'
#fa8c16
'
}
}
>
¥
{
doctor
.
price
}
/次
</
span
>
</
Space
>
</
Space
>
}
}
/>
/>
...
...
web/src/components/ConsultCreateDrawer/index.tsx
View file @
caca4568
...
@@ -158,7 +158,7 @@ const ConsultCreateDrawer: React.FC<ConsultCreateDrawerProps> = ({
...
@@ -158,7 +158,7 @@ const ConsultCreateDrawer: React.FC<ConsultCreateDrawerProps> = ({
<
Text
type=
"secondary"
>
{
doctor
.
hospital
}
</
Text
>
<
Text
type=
"secondary"
>
{
doctor
.
hospital
}
</
Text
>
<
br
/>
<
br
/>
<
Space
style=
{
{
marginTop
:
4
}
}
>
<
Space
style=
{
{
marginTop
:
4
}
}
>
<
Tag
color=
"orange"
>
¥
{
((
doctor
.
price
||
0
)
/
100
).
toFixed
(
0
)
}
/次
</
Tag
>
<
Tag
color=
"orange"
>
¥
{
doctor
.
price
||
0
}
/次
</
Tag
>
<
Text
type=
"secondary"
>
评分
{
doctor
.
rating
}
· 问诊
{
doctor
.
consult_count
}
次
</
Text
>
<
Text
type=
"secondary"
>
评分
{
doctor
.
rating
}
· 问诊
{
doctor
.
consult_count
}
次
</
Text
>
</
Space
>
</
Space
>
</
div
>
</
div
>
...
...
web/src/store/aiAssistStore.ts
View file @
caca4568
...
@@ -48,7 +48,7 @@ interface AIAssistState {
...
@@ -48,7 +48,7 @@ interface AIAssistState {
closeDrawer
:
()
=>
void
;
closeDrawer
:
()
=>
void
;
}
}
const
DEFAULT_BOUNDS
:
Bounds
=
{
x
:
-
1
,
y
:
80
,
width
:
420
,
height
:
6
00
};
const
DEFAULT_BOUNDS
:
Bounds
=
{
x
:
-
1
,
y
:
80
,
width
:
840
,
height
:
8
00
};
function
loadBounds
(
role
?:
string
):
Bounds
{
function
loadBounds
(
role
?:
string
):
Bounds
{
if
(
typeof
window
===
'
undefined
'
)
return
DEFAULT_BOUNDS
;
if
(
typeof
window
===
'
undefined
'
)
return
DEFAULT_BOUNDS
;
...
...
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