This commit is contained in:
Your Name
2026-03-05 11:19:36 +08:00
parent ab77f5488d
commit 9a1b67ca18
17 changed files with 1736 additions and 89 deletions
+114
View File
@@ -0,0 +1,114 @@
# 医生字段安装说明
## 概述
此文档说明如何为医生管理功能添加必要的数据库字段。
## 新增字段列表
### 基本信息
- `gender` - 性别(1=男,2=女)
- `age` - 年龄
- `phone` - 手机号
### 专业信息
- `title` - 职称(如:主任医师、副主任医师)
- `department` - 所属科室(如:中医科、内科)
- `specialty` - 擅长领域(文本)
### 履历信息
- `education` - 教育背景(文本)
- `experience` - 工作经历(文本)
- `honors` - 荣誉资质(文本)
## 安装步骤
### 1. 备份数据库
在执行任何数据库修改之前,请先备份数据库:
```bash
mysqldump -u root -p zyt > zyt_backup_$(date +%Y%m%d_%H%M%S).sql
```
### 2. 执行迁移脚本
#### 方法一:使用MySQL命令行
```bash
mysql -u root -p zyt < server/database/migrations/add_doctor_fields_to_admin.sql
```
#### 方法二:使用数据库管理工具
1. 打开 Navicat、phpMyAdmin 或其他数据库管理工具
2. 连接到数据库
3. 打开 `server/database/migrations/add_doctor_fields_to_admin.sql` 文件
4. 执行SQL脚本
### 3. 验证安装
执行以下SQL查询验证字段是否添加成功:
```sql
DESCRIBE zyt_admin;
```
应该能看到新增的字段:
- gender
- age
- phone
- title
- department
- specialty
- education
- experience
- honors
### 4. 测试功能
1. 登录管理后台
2. 进入"医生管理"页面
3. 点击"新增医生"
4. 填写所有字段并保存
5. 验证数据是否正确保存
## 回滚说明
如果需要撤销这些更改,可以执行回滚脚本:
```bash
mysql -u root -p zyt < server/database/migrations/rollback_doctor_fields_from_admin.sql
```
**警告:** 回滚操作将删除这些字段的所有数据,请谨慎操作!
## 注意事项
1. **数据兼容性**:现有的管理员记录不会受到影响,新字段默认为NULL
2. **索引优化**:为`phone``gender`字段添加了索引以提高查询性能
3. **字段长度**
- `phone`: varchar(20) - 支持国际号码格式
- `title`: varchar(50) - 职称名称
- `department`: varchar(100) - 科室名称
- `specialty`, `education`, `experience`, `honors`: text - 支持长文本
## 常见问题
### Q: 执行脚本时报错"字段已存在"
A: 说明字段已经添加过了,可以跳过此步骤或先执行回滚脚本。
### Q: 现有管理员数据会丢失吗?
A: 不会,新增字段不会影响现有数据。
### Q: 如何批量更新现有医生的信息?
A: 可以通过管理后台逐个编辑,或编写SQL脚本批量更新。
## 相关文件
- 迁移脚本:`server/database/migrations/add_doctor_fields_to_admin.sql`
- 回滚脚本:`server/database/migrations/rollback_doctor_fields_from_admin.sql`
- 前端页面:`admin/src/views/consumer/doctor/edit.vue`
## 技术支持
如有问题,请检查:
1. 数据库连接是否正常
2. 用户是否有ALTER TABLE权限
3. 表名是否正确(默认为`zyt_admin`
+2 -2
View File
@@ -126,11 +126,11 @@ const callInfo = ref<CallInfo>({
// 窗口位置和大小
const windowRef = ref<HTMLElement>()
const windowPosition = ref({ x: 100, y: 100 })
const windowSize = ref({ width: 800, height: 600 })
const windowSize = ref({ width: 300, height: 750 })
// 拖拽相关
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
const dragStart = ref({ x: 10, y: 0 })
// 调整大小相关
const isResizing = ref(false)
+434
View File
@@ -0,0 +1,434 @@
<template>
<div class="edit-drawer">
<el-drawer
v-model="visible"
:title="drawerTitle"
direction="rtl"
size="780px"
:before-close="handleClose"
>
<el-form ref="formRef" :model="formData" label-width="100px" :rules="formRules">
<el-divider content-position="left">基本信息</el-divider>
<!-- 账号输入框 -->
<el-form-item label="账号" prop="account">
<el-input
v-model="formData.account"
placeholder="请输入账号"
clearable
/>
</el-form-item>
<!-- 医生头像 -->
<el-form-item label="头像">
<div>
<div>
<material-picker v-model="formData.avatar" :limit="1" />
</div>
<div class="form-tips">建议尺寸100*100px支持jpgjpegpng格式</div>
</div>
</el-form-item>
<!-- 姓名输入框 -->
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" clearable />
</el-form-item>
<!-- 性别 -->
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="formData.gender">
<el-radio :label="1"></el-radio>
<el-radio :label="2"></el-radio>
</el-radio-group>
</el-form-item>
<!-- 年龄 -->
<el-form-item label="年龄" prop="age">
<el-input-number v-model="formData.age" :min="18" :max="100" placeholder="请输入年龄" />
</el-form-item>
<!-- 手机号输入框 -->
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" clearable />
</el-form-item>
<el-divider content-position="left">组织信息</el-divider>
<!-- 归属部门 -->
<el-form-item label="归属部门" prop="dept_id">
<el-tree-select
class="flex-1"
v-model="formData.dept_id"
:data="optionsData.dept"
clearable
multiple
node-key="id"
:props="{
label: 'name',
disabled(data: any) {
return data.status !== 1
}
}"
check-strictly
:default-expand-all="true"
placeholder="请选择归属部门"
/>
</el-form-item>
<!-- 岗位 -->
<el-form-item label="岗位" prop="jobs_id">
<el-select
class="flex-1"
v-model="formData.jobs_id"
clearable
multiple
placeholder="请选择岗位"
>
<el-option
v-for="(item, index) in optionsData.jobs"
:key="index"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-divider content-position="left">专业信息</el-divider>
<!-- 职称 -->
<el-form-item label="职称" prop="title">
<el-input v-model="formData.title" placeholder="如:主任医师、副主任医师" clearable />
</el-form-item>
<!-- 所属科室 -->
<el-form-item label="所属科室" prop="department">
<el-input v-model="formData.department" placeholder="如:中医科、内科" clearable />
</el-form-item>
<!-- 擅长领域 -->
<el-form-item label="擅长领域" prop="specialty">
<el-input
v-model="formData.specialty"
type="textarea"
:rows="3"
placeholder="请输入擅长领域"
/>
</el-form-item>
<el-divider content-position="left">履历信息</el-divider>
<!-- 教育背景 -->
<el-form-item label="教育背景" prop="education">
<el-input
v-model="formData.education"
type="textarea"
:rows="3"
placeholder="请输入教育背景,如:北京中医药大学 中医学博士"
/>
</el-form-item>
<!-- 工作经历 -->
<el-form-item label="工作经历" prop="experience">
<el-input
v-model="formData.experience"
type="textarea"
:rows="4"
placeholder="请输入工作经历"
/>
</el-form-item>
<!-- 荣誉资质 -->
<el-form-item label="荣誉资质" prop="honors">
<el-input
v-model="formData.honors"
type="textarea"
:rows="3"
placeholder="请输入荣誉资质"
/>
</el-form-item>
<el-divider content-position="left">账号设置</el-divider>
<!-- 密码输入框 -->
<el-form-item label="密码" prop="password">
<el-input
v-model="formData.password"
show-password
clearable
:placeholder="mode === 'edit' ? '不修改请留空' : '请输入密码'"
/>
</el-form-item>
<!-- 确认密码输入框 -->
<el-form-item label="确认密码" prop="password_confirm">
<el-input
v-model="formData.password_confirm"
show-password
clearable
:placeholder="mode === 'edit' ? '不修改请留空' : '请输入确认密码'"
/>
</el-form-item>
<!-- 医生状态 -->
<el-form-item label="医生状态">
<el-switch v-model="formData.disable" :active-value="0" :inactive-value="1" />
<span class="ml-2 text-gray-500">{{ formData.disable === 0 ? '启用' : '禁用' }}</span>
</el-form-item>
<!-- 多处登录 -->
<el-form-item label="多处登录">
<div>
<el-switch
v-model="formData.multipoint_login"
:active-value="1"
:inactive-value="0"
/>
<div class="form-tips">允许多人同时在线登录</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="flex justify-end">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { adminAdd, adminDetail, adminEdit } from '@/api/perms/admin'
import { deptAll } from '@/api/org/department'
import { jobsAll } from '@/api/org/post'
import { useDictOptions } from '@/hooks/useDictOptions'
import feedback from '@/utils/feedback'
const emit = defineEmits(['success', 'close'])
const formRef = shallowRef<FormInstance>()
const visible = ref(false)
const mode = ref('add')
const loading = ref(false)
const drawerTitle = computed(() => {
return mode.value == 'edit' ? '编辑医生' : '新增医生'
})
const formData = reactive({
id: '',
account: '',
name: '',
gender: 1,
age: null as number | null,
phone: '',
role_id: [1], // 固定为医生角色
dept_id: [],
jobs_id: [],
avatar: '',
title: '', // 职称
department: '', // 所属科室
specialty: '', // 擅长领域
education: '', // 教育背景
experience: '', // 工作经历
honors: '', // 荣誉资质
password: '',
password_confirm: '',
disable: 0,
multipoint_login: 1
})
// 获取部门和岗位选项
const { optionsData } = useDictOptions<{
jobs: any[]
dept: any[]
}>({
jobs: {
api: jobsAll
},
dept: {
api: deptAll
}
})
// 密码确认验证
const passwordConfirmValidator = (rule: object, value: string, callback: any) => {
if (formData.password) {
if (!value) callback(new Error('请再次输入密码'))
if (value !== formData.password) callback(new Error('两次输入密码不一致!'))
}
callback()
}
// 手机号验证
const phoneValidator = (rule: object, value: string, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('请输入正确的手机号'))
} else {
callback()
}
}
const formRules = reactive({
account: [
{
required: true,
message: '请输入账号',
trigger: ['blur']
}
],
name: [
{
required: true,
message: '请输入姓名',
trigger: ['blur']
}
],
gender: [
{
required: true,
message: '请选择性别',
trigger: ['change']
}
],
phone: [
{
validator: phoneValidator,
trigger: 'blur'
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['blur']
}
] as any[],
password_confirm: [
{
required: true,
message: '请输入确认密码',
trigger: ['blur']
},
{
validator: passwordConfirmValidator,
trigger: 'blur'
}
] as any[]
})
const handleSubmit = async () => {
try {
await formRef.value?.validate()
loading.value = true
mode.value == 'edit' ? await adminEdit(formData) : await adminAdd(formData)
feedback.msgSuccess(mode.value == 'edit' ? '编辑成功' : '新增成功')
visible.value = false
emit('success')
} catch (error) {
console.error('提交失败:', error)
} finally {
loading.value = false
}
}
const open = (type = 'add') => {
mode.value = type
visible.value = true
// 重置表单
if (type === 'add') {
Object.assign(formData, {
id: '',
account: '',
name: '',
gender: 1,
age: null,
phone: '',
role_id: [1],
dept_id: [],
jobs_id: [],
avatar: '',
title: '',
department: '',
specialty: '',
education: '',
experience: '',
honors: '',
password: '',
password_confirm: '',
disable: 0,
multipoint_login: 1
})
// 新增时密码必填
formRules.password = [
{
required: true,
message: '请输入密码',
trigger: ['blur']
}
]
formRules.password_confirm = [
{
required: true,
message: '请输入确认密码',
trigger: ['blur']
},
{
validator: passwordConfirmValidator,
trigger: 'blur'
}
]
}
}
const setFormData = async (row: any) => {
// 编辑时密码不是必填
formRules.password = []
formRules.password_confirm = [
{
validator: passwordConfirmValidator,
trigger: 'blur'
}
]
const data = await adminDetail({
id: row.id
})
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key]
}
}
// 确保role_id包含医生角色
if (!formData.role_id.includes(1)) {
formData.role_id.push(1)
}
}
const handleClose = () => {
visible.value = false
emit('close')
}
defineExpose({
open,
setFormData
})
</script>
<style scoped lang="scss">
.form-tips {
color: #999;
font-size: 12px;
line-height: 1.5;
margin-top: 4px;
}
</style>
+164
View File
@@ -0,0 +1,164 @@
<!-- 医生列表 -->
<template>
<div class="doctor-list">
<el-card class="!border-none" shadow="never">
<el-form class="mb-[-16px]" :model="formData" inline>
<el-form-item class="w-[280px]" label="医生账号">
<el-input
v-model="formData.account"
placeholder="请输入医生账号"
clearable
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item class="w-[280px]" label="医生名称">
<el-input
v-model="formData.name"
placeholder="请输入医生名称"
clearable
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPage">查询</el-button>
<el-button @click="resetParams">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-loading="pager.loading" class="mt-4 !border-none" shadow="never">
<el-button v-perms="['auth.admin/add']" type="primary" @click="handleAdd">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增医生
</el-button>
<div class="mt-4">
<el-table :data="pager.lists" size="large">
<el-table-column label="ID" prop="id" min-width="60" />
<el-table-column label="头像" min-width="100">
<template #default="{ row }">
<el-avatar :size="50" :src="row.avatar"></el-avatar>
</template>
</el-table-column>
<el-table-column label="账号" prop="account" min-width="120" />
<el-table-column label="姓名" prop="name" min-width="100" />
<el-table-column label="手机号" prop="phone" min-width="120" />
<el-table-column label="创建时间" prop="create_time" min-width="180" />
<el-table-column label="最近登录时间" prop="login_time" min-width="180" />
<el-table-column label="最近登录IP" prop="login_ip" min-width="120" />
<el-table-column label="状态" min-width="100" v-perms="['auth.admin/edit']">
<template #default="{ row }">
<el-switch
v-model="row.disable"
:active-value="0"
:inactive-value="1"
@change="changeStatus(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['auth.admin/edit']"
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-perms="['auth.admin/delete']"
type="danger"
link
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="flex mt-4 justify-end">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
</div>
</template>
<script lang="ts" setup name="doctorList">
import { adminDelete, adminEdit, adminLists } from '@/api/perms/admin'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import EditPopup from './edit.vue'
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
// 表单数据 - 固定role_id=1查询医生
const formData = reactive({
account: '',
name: '',
role_id: 1 // 固定为医生角色
})
const showEdit = ref(false)
const { pager, getLists, resetParams, resetPage } = usePaging({
fetchFun: adminLists,
params: formData
})
// 修改状态
const changeStatus = (data: any) => {
adminEdit({
id: data.id,
avatar: data.avatar,
account: data.account,
name: data.name,
role_id: data.role_id,
jobs_id: data.jobs_id,
dept_id: data.dept_id,
disable: data.disable,
multipoint_login: data.multipoint_login
}).finally(() => {
getLists()
})
}
// 新增医生
const handleAdd = async () => {
showEdit.value = true
await nextTick()
editRef.value?.open('add')
}
// 编辑医生
const handleEdit = async (data: any) => {
showEdit.value = true
await nextTick()
editRef.value?.open('edit')
editRef.value?.setFormData(data)
}
// 删除医生
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除该医生吗?')
await adminDelete({ id })
getLists()
}
onMounted(() => {
getLists()
})
</script>
<style scoped lang="scss">
.doctor-list {
padding: 20px;
}
</style>
@@ -0,0 +1,255 @@
<template>
<el-drawer
v-model="visible"
title="创建处方单"
size="800px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form
ref="formRef"
:model="formData"
label-width="100px"
class="prescription-form"
>
<!-- 患者基本信息 -->
<div class="info-section">
<div class="section-title">患者信息</div>
<el-form-item label="姓名">
<span>{{ formData.patient_name }}</span>
</el-form-item>
<el-form-item label="性别">
<span>{{ formData.gender }}</span>
</el-form-item>
<el-form-item label="年龄">
<span>{{ formData.age }}</span>
</el-form-item>
<el-form-item label="诊号">
<span>{{ formData.diagnosis_no }}</span>
</el-form-item>
<el-form-item label="日期">
<span>{{ formData.date }}</span>
</el-form-item>
<el-form-item label="科室">
<span>{{ formData.department }}</span>
</el-form-item>
<el-form-item label="费别">
<span>{{ formData.fee_type }}</span>
</el-form-item>
<el-form-item label="手机号">
<span>{{ formData.phone }}</span>
</el-form-item>
</div>
<!-- 诊断信息 -->
<div class="info-section">
<div class="section-title">诊断信息</div>
<el-form-item label="诊断" prop="diagnosis">
<el-input
v-model="formData.diagnosis"
type="textarea"
:rows="3"
placeholder="请输入诊断信息"
/>
</el-form-item>
</div>
<!-- 药品信息 -->
<div class="info-section">
<div class="section-title">
药品信息
<el-button type="primary" size="small" @click="handleAddMedicine">
添加药品
</el-button>
</div>
<div
v-for="(medicine, index) in formData.medicines"
:key="index"
class="medicine-item"
>
<el-form-item :label="`药品${index + 1}`">
<el-input
v-model="medicine.name"
placeholder="药品名称"
class="mb-2"
/>
<el-input
v-model="medicine.usage"
type="textarea"
:rows="2"
placeholder="用法用量"
/>
<el-button
type="danger"
size="small"
link
@click="handleRemoveMedicine(index)"
class="mt-2"
>
删除
</el-button>
</el-form-item>
</div>
</div>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import feedback from '@/utils/feedback'
interface Medicine {
name: string
usage: string
}
interface PrescriptionForm {
appointment_id: number
patient_name: string
gender: string
age: string
diagnosis_no: string
date: string
department: string
fee_type: string
phone: string
diagnosis: string
medicines: Medicine[]
}
const emit = defineEmits(['success'])
const visible = ref(false)
const formRef = ref()
const formData = reactive<PrescriptionForm>({
appointment_id: 0,
patient_name: '',
gender: '',
age: '',
diagnosis_no: '',
date: '',
department: '',
fee_type: '',
phone: '',
diagnosis: '',
medicines: []
})
// 打开抽屉
const open = (data: any) => {
formData.appointment_id = data.id
formData.patient_name = data.patient_name || ''
formData.gender = data.patient_gender || ''
formData.age = data.patient_age || ''
formData.diagnosis_no = data.id.toString()
formData.date = data.appointment_date || ''
formData.department = '中医科'
formData.fee_type = data.appointment_type_desc || ''
formData.phone = data.patient_phone || ''
formData.diagnosis = ''
formData.medicines = []
visible.value = true
}
// 关闭抽屉
const handleClose = () => {
visible.value = false
formRef.value?.resetFields()
}
// 添加药品
const handleAddMedicine = () => {
formData.medicines.push({
name: '',
usage: ''
})
}
// 删除药品
const handleRemoveMedicine = (index: number) => {
formData.medicines.splice(index, 1)
}
// 保存处方单
const handleSave = async () => {
if (!formData.diagnosis) {
feedback.msgWarning('请输入诊断信息')
return
}
if (formData.medicines.length === 0) {
feedback.msgWarning('请至少添加一个药品')
return
}
// 验证药品信息
for (const medicine of formData.medicines) {
if (!medicine.name || !medicine.usage) {
feedback.msgWarning('请完善药品信息')
return
}
}
try {
// TODO: 调用保存处方单的API
// await savePrescription(formData)
feedback.msgSuccess('处方单创建成功')
handleClose()
emit('success')
} catch (error) {
feedback.msgError('保存失败')
}
}
defineExpose({
open
})
</script>
<style scoped lang="scss">
.prescription-form {
.info-section {
margin-bottom: 24px;
padding: 16px;
background: #f5f7fa;
border-radius: 4px;
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: #303133;
display: flex;
justify-content: space-between;
align-items: center;
}
.el-form-item {
margin-bottom: 12px;
span {
color: #606266;
}
}
}
.medicine-item {
margin-bottom: 16px;
padding: 12px;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
.el-form-item {
margin-bottom: 0;
}
}
}
</style>
+232 -16
View File
@@ -8,16 +8,16 @@
v-model="formData.patient_name"
placeholder="请输入患者姓名"
clearable
@keyup.enter="resetPage"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item class="w-[280px]" label="医生姓名">
<el-form-item class="w-[280px]" label="医生姓名" v-if="isAdmin">
<el-input
v-model="formData.doctor_name"
placeholder="请输入医生姓名"
clearable
@keyup.enter="resetPage"
@keyup.enter="handleSearch"
/>
</el-form-item>
@@ -26,6 +26,7 @@
<el-option label="已预约" :value="1" />
<el-option label="已取消" :value="2" />
<el-option label="已完成" :value="3" />
<el-option label="已过号" :value="4" />
</el-select>
</el-form-item>
@@ -37,14 +38,43 @@
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPage">查询</el-button>
<el-button @click="resetParams">重置</el-button>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 数据表格 -->
<el-card class="!border-none mt-4" shadow="never">
<!-- tab选项卡 -->
<el-tabs v-model="activeTab" @tab-change="handleTabChange" class="mb-4">
<el-tab-pane label="全部" name="all" />
<el-tab-pane name="1">
<template #label>
<span>已预约</span>
<el-badge v-if="statusCount[1]" :value="statusCount[1]" class="ml-2" type="success" />
</template>
</el-tab-pane>
<el-tab-pane name="4">
<template #label>
<span>已过号</span>
<el-badge v-if="statusCount[4]" :value="statusCount[4]" class="ml-2" type="danger" />
</template>
</el-tab-pane>
<el-tab-pane name="2">
<template #label>
<span>已取消</span>
<el-badge v-if="statusCount[2]" :value="statusCount[2]" class="ml-2" type="info" />
</template>
</el-tab-pane>
<el-tab-pane name="3">
<template #label>
<span>已完成</span>
<el-badge v-if="statusCount[3]" :value="statusCount[3]" class="ml-2" type="primary" />
</template>
</el-tab-pane>
</el-tabs>
<el-table
v-loading="pager.loading"
:data="pager.lists"
@@ -87,10 +117,10 @@
</template>
</el-table-column>
<el-table-column label="状态" width="80">
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag
:type="row.status === 1 ? 'success' : row.status === 2 ? 'info' : 'primary'"
:type="row.status === 1 ? 'success' : row.status === 2 ? 'info' : row.status === 3 ? 'primary' : 'danger'"
size="small"
>
{{ row.status_desc }}
@@ -106,7 +136,7 @@
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<el-table-column label="操作" width="360" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['doctor.appointment/detail']"
@@ -116,6 +146,30 @@
>
详情
</el-button>
<el-button
v-perms="['tcm.diagnosis/edit']"
type="primary"
link
@click="handleEditPatient(row)"
>
编辑患者
</el-button>
<el-button
v-perms="['tcm.diagnosis/video-call']"
type="success"
link
@click="handleVideoCall(row)"
>
1V1通话
</el-button>
<el-button
v-perms="['tcm.diagnosis/video-group']"
type="success"
link
@click="handleGroupVideoCall(row)"
>
群通话
</el-button>
<el-button
v-if="row.status === 1"
v-perms="['doctor.appointment/cancel']"
@@ -123,7 +177,7 @@
link
@click="handleCancel(row)"
>
取消
取消挂号
</el-button>
<el-button
v-if="row.status === 1"
@@ -132,8 +186,17 @@
link
@click="handleComplete(row)"
>
完成
完成视频面诊
</el-button>
<!-- <el-button
v-if="row.status === 1"
v-perms="['doctor.appointment/prescription']"
type="success"
link
@click="handleCreatePrescription(row)"
>
创建处方单
</el-button> -->
</template>
</el-table-column>
</el-table>
@@ -155,7 +218,7 @@
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag
:type="detailData.status === 1 ? 'success' : detailData.status === 2 ? 'info' : 'primary'"
:type="detailData.status === 1 ? 'success' : detailData.status === 2 ? 'info' : detailData.status === 3 ? 'primary' : 'danger'"
size="small"
>
{{ detailData.status_desc }}
@@ -190,6 +253,15 @@
</el-descriptions-item>
</el-descriptions>
</el-dialog>
<!-- 编辑患者弹窗 -->
<edit-popup ref="editRef" @success="getLists" />
<!-- 视频通话组件 -->
<video-call ref="videoCallRef" />
<!-- 处方单抽屉 -->
<prescription-drawer ref="prescriptionDrawerRef" @success="loadData" />
</div>
</template>
@@ -197,22 +269,101 @@
import { usePaging } from '@/hooks/usePaging'
import { appointmentLists, cancelAppointment, completeAppointment, appointmentDetail } from '@/api/doctor'
import feedback from '@/utils/feedback'
import EditPopup from '../diagnosis/edit.vue'
import VideoCall from '@/components/video-call/index.vue'
import PrescriptionDrawer from './components/prescription-drawer.vue'
import useUserStore from '@/stores/modules/user'
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo)
// 判断是否为管理员(非医生、非医助)
const isAdmin = computed(() => {
const roleId = userInfo.value.role_id
// 如果role_id是数组,检查是否包含医生(1)或医助(2)
if (Array.isArray(roleId)) {
return !roleId.includes(1) && !roleId.includes(2)
}
// 如果role_id是单个值,检查是否不是医生或医助
return roleId !== 1 && roleId !== 2
})
const formData = reactive({
patient_name: '',
doctor_name: '',
status: '',
status: '' as string | number,
start_date: '',
end_date: ''
})
const activeTab = ref('all')
const statusCount = ref<Record<number, number>>({
1: 0,
2: 0,
3: 0,
4: 0
})
const { pager, getLists, resetPage, resetParams } = usePaging({
fetchFun: appointmentLists,
params: formData
})
// 获取各状态数量
const getStatusCount = async () => {
try {
// 获取各状态的数量
const promises = [1, 2, 3, 4].map(async (status) => {
const res = await appointmentLists({
...formData,
status,
page_no: 1,
page_size: 1
})
return { status, count: res.count || 0 }
})
const results = await Promise.all(promises)
results.forEach(({ status, count }) => {
statusCount.value[status] = count
})
} catch (error) {
console.error('获取状态数量失败:', error)
}
}
// 切换选项卡
const handleTabChange = (tabName: string | number) => {
if (tabName === 'all') {
formData.status = ''
} else {
formData.status = Number(tabName)
}
resetPage()
}
// 重写getLists,同时更新状态数量
const loadData = async () => {
await getLists()
await getStatusCount()
}
// 搜索
const handleSearch = () => {
resetPage()
}
// 重置
const handleReset = () => {
activeTab.value = 'all'
resetParams()
}
const detailVisible = ref(false)
const detailData = ref<any>(null)
const editRef = ref()
const videoCallRef = ref()
const prescriptionDrawerRef = ref()
// 查看详情
const handleDetail = async (row: any) => {
@@ -231,7 +382,7 @@ const handleCancel = async (row: any) => {
await feedback.confirm('确定要取消该挂号吗?')
await cancelAppointment({ id: row.id })
feedback.msgSuccess('取消成功')
getLists()
loadData()
} catch (error) {
// 用户取消操作
}
@@ -243,13 +394,78 @@ const handleComplete = async (row: any) => {
await feedback.confirm('确认患者已就诊完成?')
await completeAppointment({ id: row.id })
feedback.msgSuccess('操作成功')
getLists()
loadData()
} catch (error) {
// 用户取消操作
}
}
getLists()
// 编辑患者资料
const handleEditPatient = (row: any) => {
console.log()
if (!row.patient_id) {
feedback.msgWarning('该预约没有关联诊单信息')
return
}
editRef.value?.open('edit', row.patient_id)
}
// 1v1视频通话
const handleVideoCall = (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
videoCallRef.value?.open({
diagnosisId: row.diagnosis_id || row.id,
patientId: row.patient_id,
patientName: row.patient_name,
userId: `patient_${row.patient_id}`,
isGroup: false
})
}
// 群组视频通话
const handleGroupVideoCall = (row: any) => {
if (!row.patient_id) {
feedback.msgWarning('患者信息不完整')
return
}
if (!row.doctor_id) {
feedback.msgWarning('该预约没有医生信息,无法发起群组通话')
return
}
// 群组通话参与者:患者和医生
const userIds = [
`patient_${row.patient_id}`,
`doctor_${row.doctor_id}`
]
videoCallRef.value?.open({
diagnosisId: row.diagnosis_id || row.id,
patientId: row.patient_id,
patientName: row.patient_name,
doctorId: row.doctor_id,
userIds: userIds,
isGroup: true
})
}
// 创建处方单
const handleCreatePrescription = async (row: any) => {
try {
// 获取详细信息
const detail = await appointmentDetail({ id: row.id })
prescriptionDrawerRef.value?.open(detail)
} catch (error) {
feedback.msgError('获取患者信息失败')
}
}
loadData()
</script>
<style scoped lang="scss">
+153 -42
View File
@@ -3,12 +3,13 @@
<el-drawer
v-model="visible"
:title="drawerTitle"
size="60%"
size="70%"
:before-close="handleClose"
:z-index="1500"
:modal="true"
class="diagnosis-drawer"
>
<el-tabs v-model="activeTab" class="px-4">
<el-tabs v-model="activeTab" class="diagnosis-tabs">
<!-- 基本信息标签页 -->
<el-tab-pane label="基本信息" name="basic">
<el-form
@@ -133,7 +134,25 @@
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-col :span="12">
<el-form-item label="糖尿病期数" prop="diabetes_type">
<el-select
v-model="formData.diabetes_type"
placeholder="请选择糖尿病期数"
class="w-full"
:popper-options="{ strategy: 'fixed' }"
popper-class="high-z-index"
>
<el-option
v-for="item in diabetesTypeOptions"
:key="item.value"
:label="item.name + (item.remark ? ' (' + item.remark + ')' : '')"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
@@ -148,7 +167,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="口腔感觉">
<el-form-item label="口腔感觉" class="compact-form-item">
<el-radio-group v-model="formData.appetite">
<el-radio v-for="item in appetiteOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -157,7 +176,7 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="每日饮水量">
<el-form-item label="每日饮水量" class="compact-form-item">
<el-radio-group v-model="formData.water_intake">
<el-radio v-for="item in waterIntakeOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -167,7 +186,28 @@
</el-col>
</el-row>
<el-form-item label="饮食情况">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="体重变化" class="compact-form-item">
<el-radio-group v-model="formData.weight_change">
<el-radio v-for="item in weightChangeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="脂肪肝程度" class="compact-form-item">
<el-radio-group v-model="formData.fatty_liver_degree">
<el-radio v-for="item in fattyLiverDegreeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="饮食情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.diet_condition">
<el-checkbox v-for="item in dietConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -175,15 +215,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="体重变化">
<el-radio-group v-model="formData.weight_change">
<el-radio v-for="item in weightChangeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="肢体感觉">
<el-form-item label="肢体感觉" class="checkbox-form-item">
<el-checkbox-group v-model="formData.body_feeling">
<el-checkbox v-for="item in bodyFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -191,7 +223,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="睡眠情况">
<el-form-item label="睡眠情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.sleep_condition">
<el-checkbox v-for="item in sleepConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -199,7 +231,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="眼睛情况">
<el-form-item label="眼睛情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.eye_condition">
<el-checkbox v-for="item in eyeConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -207,7 +239,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="头部感觉">
<el-form-item label="头部感觉" class="checkbox-form-item">
<el-checkbox-group v-model="formData.head_feeling">
<el-checkbox v-for="item in headFeelingOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -215,7 +247,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="出汗情况">
<el-form-item label="出汗情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.sweat_condition">
<el-checkbox v-for="item in sweatConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -223,7 +255,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="皮肤情况">
<el-form-item label="皮肤情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.skin_condition">
<el-checkbox v-for="item in skinConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -231,7 +263,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="小便情况">
<el-form-item label="小便情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.urine_condition">
<el-checkbox v-for="item in urineConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -239,7 +271,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="大便情况">
<el-form-item label="大便情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.stool_condition">
<el-checkbox v-for="item in stoolConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -247,7 +279,7 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="腰肾情况">
<el-form-item label="腰肾情况" class="checkbox-form-item">
<el-checkbox-group v-model="formData.kidney_condition">
<el-checkbox v-for="item in kidneyConditionOptions" :key="item.value" :label="item.value">
{{ item.name }}
@@ -255,14 +287,6 @@
</el-checkbox-group>
</el-form-item>
<el-form-item label="脂肪肝程度">
<el-radio-group v-model="formData.fatty_liver_degree">
<el-radio v-for="item in fattyLiverDegreeOptions" :key="item.value" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 既往史 -->
<el-divider content-position="left">既往史</el-divider>
@@ -469,6 +493,7 @@ const formData = ref({
diagnosis_date: '',
diagnosis_type: '',
syndrome_type: '',
diabetes_type: '',
// 现病史字段
appetite: '',
water_intake: '',
@@ -535,12 +560,14 @@ const formRules = {
age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
diagnosis_date: [{ required: true, message: '请选择诊断日期', trigger: 'change' }],
diagnosis_type: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
syndrome_type: [{ required: true, message: '请选择证型', trigger: 'change' }]
syndrome_type: [{ required: true, message: '请选择证型', trigger: 'change' }],
diabetes_type: [{ required: true, message: '请选择糖尿病期数', trigger: 'change' }]
}
// 获取字典选项
const diagnosisTypeOptions = ref<any[]>([])
const syndromeTypeOptions = ref<any[]>([])
const diabetesTypeOptions = ref<any[]>([])
const pastHistoryOptions = ref<any[]>([])
// 现病史字典选项
const appetiteOptions = ref<any[]>([])
@@ -562,7 +589,8 @@ const getDictOptions = async () => {
try {
const [
diagnosisType,
syndromeType,
syndromeType,
diabetesType,
pastHistory,
appetite,
waterIntake,
@@ -581,6 +609,7 @@ const getDictOptions = async () => {
] = await Promise.all([
getDictData({ type: 'diagnosis_type' }),
getDictData({ type: 'syndrome_type' }),
getDictData({ type: 'diabetes_type' }),
getDictData({ type: 'past_history' }),
getDictData({ type: 'appetite' }),
getDictData({ type: 'water_intake' }),
@@ -600,6 +629,7 @@ const getDictOptions = async () => {
diagnosisTypeOptions.value = diagnosisType?.diagnosis_type || []
syndromeTypeOptions.value = syndromeType?.syndrome_type || []
diabetesTypeOptions.value = diabetesType?.diabetes_type || []
pastHistoryOptions.value = pastHistory?.past_history || []
appetiteOptions.value = appetite?.appetite || []
waterIntakeOptions.value = waterIntake?.water_intake || []
@@ -720,6 +750,7 @@ const handleClose = () => {
diagnosis_date: '',
diagnosis_type: '',
syndrome_type: '',
diabetes_type: '',
appetite: '',
water_intake: '',
diet_condition: [],
@@ -761,24 +792,104 @@ defineExpose({
<style lang="scss" scoped>
.edit-drawer {
:deep(.el-checkbox-group) {
.el-checkbox {
margin-right: 20px;
margin-bottom: 10px;
:deep(.el-form) {
padding-bottom: 20px;
}
:deep(.el-divider) {
margin: 24px 0 20px;
.el-divider__text {
font-size: 15px;
font-weight: 600;
color: #303133;
background: #f5f7fa;
padding: 0 16px;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-form-item__label) {
font-weight: 500;
color: #606266;
}
// 紧凑型表单项(单选框)
.compact-form-item {
:deep(.el-radio-group) {
display: flex;
flex-wrap: wrap;
gap: 12px;
.el-radio {
margin-right: 0;
margin-bottom: 0;
}
}
}
// 复选框表单项
.checkbox-form-item {
:deep(.el-checkbox-group) {
display: flex;
flex-wrap: wrap;
gap: 12px 16px;
.el-checkbox {
margin-right: 0;
margin-bottom: 0;
}
}
}
// 通用单选框样式
:deep(.el-radio-group) {
.el-radio {
margin-right: 20px;
margin-bottom: 10px;
margin-right: 16px;
margin-bottom: 8px;
&:last-child {
margin-right: 0;
}
}
}
// 通用复选框样式
:deep(.el-checkbox-group) {
.el-checkbox {
margin-right: 16px;
margin-bottom: 8px;
}
}
.form-tips {
font-size: 12px;
color: #999;
margin-top: 5px;
color: #909399;
margin-top: 4px;
line-height: 1.5;
}
// 输入框样式优化
:deep(.el-input__inner),
:deep(.el-textarea__inner) {
border-radius: 4px;
}
// 选择器样式优化
:deep(.el-select) {
.el-input__inner {
border-radius: 4px;
}
}
// 日期选择器样式优化
:deep(.el-date-editor) {
.el-input__inner {
border-radius: 4px;
}
}
}
</style>
+98
View File
@@ -0,0 +1,98 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
REM 医生字段安装脚本 (Windows版本)
REM 用法: install_doctor_fields.bat
echo ==========================================
echo 医生字段安装脚本
echo ==========================================
echo.
REM 数据库配置
set DB_HOST=localhost
set DB_PORT=3306
set DB_NAME=zyt
set DB_USER=root
REM 提示输入数据库密码
set /p DB_PASS=请输入数据库密码:
echo.
REM 检查MySQL是否可用
echo 检查MySQL连接...
mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% -e "SELECT 1;" >nul 2>&1
if errorlevel 1 (
echo [错误] 无法连接到MySQL数据库
echo 请检查数据库配置和密码是否正确
pause
exit /b 1
)
echo [成功] 数据库连接成功
echo.
REM 备份数据库
set BACKUP_FILE=zyt_backup_%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%.sql
set BACKUP_FILE=%BACKUP_FILE: =0%
echo 正在备份数据库到: %BACKUP_FILE%
mysqldump -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% > %BACKUP_FILE% 2>nul
if errorlevel 1 (
echo [警告] 数据库备份失败
set /p continue=是否继续安装? (y/n):
if /i not "!continue!"=="y" exit /b 1
) else (
echo [成功] 数据库备份成功
)
echo.
REM 执行迁移脚本
echo 正在执行数据库迁移...
mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% < server\database\migrations\add_doctor_fields_to_admin.sql 2>&1
if errorlevel 1 (
echo [错误] 数据库迁移失败
echo 请检查错误信息,可能字段已经存在
pause
exit /b 1
)
echo [成功] 数据库迁移成功
echo.
REM 验证安装
echo 验证字段是否添加成功...
mysql -h%DB_HOST% -P%DB_PORT% -u%DB_USER% -p%DB_PASS% %DB_NAME% -e "SHOW COLUMNS FROM zyt_admin LIKE 'gender';" 2>nul | find "gender" >nul
if errorlevel 1 (
echo [错误] 字段验证失败
pause
exit /b 1
)
echo [成功] 字段验证成功
echo.
echo ==========================================
echo 安装完成!
echo ==========================================
echo.
echo 新增字段列表:
echo - gender (性别)
echo - age (年龄)
echo - phone (手机号)
echo - title (职称)
echo - department (所属科室)
echo - specialty (擅长领域)
echo - education (教育背景)
echo - experience (工作经历)
echo - honors (荣誉资质)
echo.
echo 备份文件: %BACKUP_FILE%
echo.
echo 现在可以使用医生管理功能了!
echo.
pause
+100
View File
@@ -0,0 +1,100 @@
#!/bin/bash
# 医生字段安装脚本
# 用法: ./install_doctor_fields.sh
echo "=========================================="
echo "医生字段安装脚本"
echo "=========================================="
echo ""
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 数据库配置
DB_HOST="localhost"
DB_PORT="3306"
DB_NAME="zyt"
DB_USER="root"
# 提示输入数据库密码
echo -e "${YELLOW}请输入数据库密码:${NC}"
read -s DB_PASS
echo ""
# 检查MySQL是否可用
echo "检查MySQL连接..."
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -e "SELECT 1;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}错误: 无法连接到MySQL数据库${NC}"
echo "请检查数据库配置和密码是否正确"
exit 1
fi
echo -e "${GREEN}✓ 数据库连接成功${NC}"
echo ""
# 备份数据库
BACKUP_FILE="zyt_backup_$(date +%Y%m%d_%H%M%S).sql"
echo "正在备份数据库到: $BACKUP_FILE"
mysqldump -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_FILE" 2>/dev/null
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ 数据库备份成功${NC}"
else
echo -e "${RED}✗ 数据库备份失败${NC}"
echo "是否继续安装? (y/n)"
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
echo ""
# 执行迁移脚本
echo "正在执行数据库迁移..."
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" < server/database/migrations/add_doctor_fields_to_admin.sql 2>&1
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ 数据库迁移成功${NC}"
else
echo -e "${RED}✗ 数据库迁移失败${NC}"
echo "请检查错误信息,可能字段已经存在"
exit 1
fi
echo ""
# 验证安装
echo "验证字段是否添加成功..."
RESULT=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SHOW COLUMNS FROM zyt_admin LIKE 'gender';" 2>/dev/null | grep -c "gender")
if [ "$RESULT" -eq 1 ]; then
echo -e "${GREEN}✓ 字段验证成功${NC}"
else
echo -e "${RED}✗ 字段验证失败${NC}"
exit 1
fi
echo ""
echo "=========================================="
echo -e "${GREEN}安装完成!${NC}"
echo "=========================================="
echo ""
echo "新增字段列表:"
echo " - gender (性别)"
echo " - age (年龄)"
echo " - phone (手机号)"
echo " - title (职称)"
echo " - department (所属科室)"
echo " - specialty (擅长领域)"
echo " - education (教育背景)"
echo " - experience (工作经历)"
echo " - honors (荣誉资质)"
echo ""
echo "备份文件: $BACKUP_FILE"
echo ""
echo "现在可以使用医生管理功能了!"
+5 -1
View File
@@ -6,4 +6,8 @@ admin Zyt123456
外网面板地址: https://139.196.37.14:30296/2a7ef99b
内网面板地址: https://172.27.15.98:30296/2a7ef99b
username: i6rjat7k
password: 0ccb31ec
password: 0ccb31ec
http://%E6%88%91%E6%8A%8A%E7%BB%84%E4%BB%B6%E6%94%B9%E6%88%90%E8%BF%99%E4%B8%AA%E4%BA%86@tencentcloud/call-uikit-vue%22:%20%22%5E4.0.12%22,
我把视频组件从"@tencentcloud/call-uikit-vue": "^4.0.12", 改成了最新版,现在需要你更新所有视频通话组件逻辑代码
@@ -135,7 +135,9 @@ class AdminLists extends BaseAdminDataLists implements ListsExtendInterface, Lis
{
$field = [
'id', 'name', 'account', 'create_time', 'disable', 'root',
'login_time', 'login_ip', 'multipoint_login', 'avatar'
'login_time', 'login_ip', 'multipoint_login', 'avatar',
'gender', 'age', 'phone', 'title', 'department',
'specialty', 'education', 'experience', 'honors'
];
$adminLists = Admin::field($field)
@@ -4,6 +4,7 @@ namespace app\adminapi\lists\doctor;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\model\doctor\Appointment;
use app\common\model\auth\AdminRole;
use app\common\lists\ListsSearchInterface;
/**
@@ -19,15 +20,18 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
*/
public function setSearch(): array
{
$allowSearch = [
'=' => ['a.patient_id', 'a.doctor_id', 'a.status', 'a.appointment_date'],
];
// 处理日期范围搜索
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
$this->searchWhere[] = ['a.appointment_date', 'between', [$this->params['start_date'], $this->params['end_date']]];
}
return [];
}
/**
* @notes 获取列表
* @return array
*/
public function lists(): array
{
// 获取当前管理员的角色ID
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
// 处理患者姓名搜索
if (!empty($this->params['patient_name'])) {
$this->searchWhere[] = ['u.patient_name', 'like', '%' . $this->params['patient_name'] . '%'];
@@ -38,22 +42,41 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
$this->searchWhere[] = ['ad.name', 'like', '%' . $this->params['doctor_name'] . '%'];
}
return $allowSearch;
}
// 处理状态搜索
if (isset($this->params['status']) && $this->params['status'] !== '') {
$this->searchWhere[] = ['a.status', '=', $this->params['status']];
}
/**
* @notes 获取列表
* @return array
*/
public function lists(): array
{
$lists = Appointment::alias('a')
// 处理日期范围搜索
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
$this->searchWhere[] = ['a.appointment_date', 'between', [$this->params['start_date'], $this->params['end_date']]];
} elseif (!empty($this->params['start_date'])) {
$this->searchWhere[] = ['a.appointment_date', '>=', $this->params['start_date']];
} elseif (!empty($this->params['end_date'])) {
$this->searchWhere[] = ['a.appointment_date', '<=', $this->params['end_date']];
}
// 构建查询
$query = Appointment::alias('a')
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
->leftJoin('admin ad', 'a.doctor_id = ad.id')
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, ad.name as doctor_name')
->where($this->searchWhere)
->order('a.id', 'desc')
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, ad.name as doctor_name, u.id as diagnosis_id')
->where($this->searchWhere);
// 如果是医生角色(role_id=1),只显示挂自己号的预约
if (in_array(1, $roleIds)) {
$query->where('a.doctor_id', $this->adminId);
}
// 如果是医助角色(role_id=2),只显示自己添加的患者的预约
if (in_array(2, $roleIds)) {
$query->where('u.assistant_id', $this->adminId);
}
$lists = $query
->order('a.status', 'asc')
->order('a.appointment_date', 'asc')
->order('a.appointment_time', 'asc')
->limit($this->limitOffset, $this->limitLength)
->select()
->toArray();
@@ -64,6 +87,7 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
1 => '已预约',
2 => '已取消',
3 => '已完成',
4 => '已过号',
];
$item['status_desc'] = $statusMap[$item['status']] ?? '未知';
@@ -92,10 +116,48 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
*/
public function count(): int
{
// 获取当前管理员的角色ID
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
// 处理患者姓名搜索
if (!empty($this->params['patient_name'])) {
$this->searchWhere[] = ['u.patient_name', 'like', '%' . $this->params['patient_name'] . '%'];
}
// 处理医生姓名搜索
if (!empty($this->params['doctor_name'])) {
$this->searchWhere[] = ['ad.name', 'like', '%' . $this->params['doctor_name'] . '%'];
}
// 处理状态搜索
if (isset($this->params['status']) && $this->params['status'] !== '') {
$this->searchWhere[] = ['a.status', '=', $this->params['status']];
}
// 处理日期范围搜索
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
$this->searchWhere[] = ['a.appointment_date', 'between', [$this->params['start_date'], $this->params['end_date']]];
} elseif (!empty($this->params['start_date'])) {
$this->searchWhere[] = ['a.appointment_date', '>=', $this->params['start_date']];
} elseif (!empty($this->params['end_date'])) {
$this->searchWhere[] = ['a.appointment_date', '<=', $this->params['end_date']];
}
// 构建查询
$query = Appointment::alias('a')
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
->leftJoin('admin ad', 'a.doctor_id = ad.id')
->where($this->searchWhere);
// 如果是医生角色(role_id=1),只显示挂自己号的预约
if (in_array(1, $roleIds)) {
$query->where('a.doctor_id', $this->adminId);
}
// 如果是医助角色(role_id=2),只显示自己添加的患者的预约
if (in_array(2, $roleIds)) {
$query->where('u.assistant_id', $this->adminId);
}
return $query->count();
}
@@ -16,6 +16,7 @@ namespace app\adminapi\lists\tcm;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\model\tcm\Diagnosis;
use app\common\model\auth\AdminRole;
use app\common\lists\ListsSearchInterface;
/**
@@ -47,7 +48,18 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
*/
public function lists(): array
{
$lists = Diagnosis::where($this->searchWhere)
// 获取当前管理员的角色ID
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
// 构建查询
$query = Diagnosis::where($this->searchWhere);
// 如果是医助角色(role_id=2),只显示自己添加的患者
if (in_array(2, $roleIds)) {
$query->where('assistant_id', $this->adminId);
}
$lists = $query
->field(['id', 'patient_id', 'patient_name', 'id_card', 'phone', 'gender', 'age', 'diagnosis_date', 'diagnosis_type', 'syndrome_type', 'assistant_id', 'status', 'create_time', 'update_time'])
->append(['gender_desc', 'status_desc', 'diagnosis_date_text'])
->order(['id' => 'desc'])
@@ -64,6 +76,17 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
*/
public function count(): int
{
return Diagnosis::where($this->searchWhere)->count();
// 获取当前管理员的角色ID
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
// 构建查询
$query = Diagnosis::where($this->searchWhere);
// 如果是医助角色(role_id=2),只显示自己添加的患者
if (in_array(2, $roleIds)) {
$query->where('assistant_id', $this->adminId);
}
return $query->count();
}
}
+23 -1
View File
@@ -60,6 +60,16 @@ class AdminLogic extends BaseLogic
'create_time' => time(),
'disable' => $params['disable'],
'multipoint_login' => $params['multipoint_login'],
// 新增字段
'gender' => $params['gender'] ?? 1,
'age' => $params['age'] ?? null,
'phone' => $params['phone'] ?? null,
'title' => $params['title'] ?? null,
'department' => $params['department'] ?? null,
'specialty' => $params['specialty'] ?? null,
'education' => $params['education'] ?? null,
'experience' => $params['experience'] ?? null,
'honors' => $params['honors'] ?? null,
]);
// 角色
@@ -99,7 +109,17 @@ class AdminLogic extends BaseLogic
'name' => $params['name'],
'account' => $params['account'],
'disable' => $params['disable'],
'multipoint_login' => $params['multipoint_login']
'multipoint_login' => $params['multipoint_login'],
// 新增字段
'gender' => $params['gender'] ?? 1,
'age' => $params['age'] ?? null,
'phone' => $params['phone'] ?? null,
'title' => $params['title'] ?? null,
'department' => $params['department'] ?? null,
'specialty' => $params['specialty'] ?? null,
'education' => $params['education'] ?? null,
'experience' => $params['experience'] ?? null,
'honors' => $params['honors'] ?? null,
];
// 头像
@@ -232,6 +252,8 @@ class AdminLogic extends BaseLogic
$admin = Admin::field([
'id', 'account', 'name', 'disable', 'root',
'multipoint_login', 'avatar',
'gender', 'age', 'phone', 'title', 'department',
'specialty', 'education', 'experience', 'honors'
])->findOrEmpty($params['id'])->toArray();
if ($action == 'detail') {
@@ -290,9 +290,9 @@ class AppointmentLogic extends BaseLogic
public static function detail(array $params)
{
$appointment = Appointment::alias('a')
->leftJoin('la_user u', 'a.patient_id = u.id')
->leftJoin('la_admin ad', 'a.doctor_id = ad.id')
->field('a.*, u.nickname as patient_name, u.mobile as patient_phone, ad.name as doctor_name')
->leftJoin('zyt_tcm_diagnosis u', 'a.patient_id = u.id')
->leftJoin('zyt_admin ad', 'a.doctor_id = ad.id')
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, ad.name as doctor_name')
->where('a.id', $params['id'])
->findOrEmpty()
->toArray();
@@ -0,0 +1,24 @@
-- 为管理员表添加医生相关字段
-- 执行时间:请根据实际情况修改
-- 添加基本信息字段
ALTER TABLE `zyt_admin`
ADD COLUMN `gender` tinyint(1) DEFAULT 1 COMMENT '性别:1=男,2=女' AFTER `name`,
ADD COLUMN `age` int(3) DEFAULT NULL COMMENT '年龄' AFTER `gender`,
ADD COLUMN `phone` varchar(20) DEFAULT NULL COMMENT '手机号' AFTER `age`;
-- 添加专业信息字段
ALTER TABLE `zyt_admin`
ADD COLUMN `title` varchar(50) DEFAULT NULL COMMENT '职称' AFTER `phone`,
ADD COLUMN `department` varchar(100) DEFAULT NULL COMMENT '所属科室' AFTER `title`,
ADD COLUMN `specialty` text DEFAULT NULL COMMENT '擅长领域' AFTER `department`;
-- 添加履历信息字段
ALTER TABLE `zyt_admin`
ADD COLUMN `education` text DEFAULT NULL COMMENT '教育背景' AFTER `specialty`,
ADD COLUMN `experience` text DEFAULT NULL COMMENT '工作经历' AFTER `education`,
ADD COLUMN `honors` text DEFAULT NULL COMMENT '荣誉资质' AFTER `experience`;
-- 添加索引
ALTER TABLE `zyt_admin` ADD INDEX `idx_phone` (`phone`);
ALTER TABLE `zyt_admin` ADD INDEX `idx_gender` (`gender`);
@@ -0,0 +1,18 @@
-- 回滚:删除管理员表的医生相关字段
-- 警告:执行此脚本将删除这些字段的所有数据
-- 删除索引
ALTER TABLE `zyt_admin` DROP INDEX `idx_phone`;
ALTER TABLE `zyt_admin` DROP INDEX `idx_gender`;
-- 删除字段
ALTER TABLE `zyt_admin`
DROP COLUMN `gender`,
DROP COLUMN `age`,
DROP COLUMN `phone`,
DROP COLUMN `title`,
DROP COLUMN `department`,
DROP COLUMN `specialty`,
DROP COLUMN `education`,
DROP COLUMN `experience`,
DROP COLUMN `honors`;