医院管理页新增医院管理员列并支持任命医院管理员
组织架构树展示医院管理员信息 科室与小组弹窗支持设置主任/组长并限制候选角色 患者页优化归属医生选择与字段文案 统一“小组组长”角色文案
This commit is contained in:
parent
b527256874
commit
5fdf4c80e6
@ -4,15 +4,34 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<h2 class="login-title">调压通管理后台</h2>
|
<h2 class="login-title">调压通管理后台</h2>
|
||||||
</template>
|
</template>
|
||||||
<el-form :model="loginForm" :rules="rules" ref="loginFormRef" @keyup.enter="handleLogin">
|
<el-form
|
||||||
|
:model="loginForm"
|
||||||
|
:rules="rules"
|
||||||
|
ref="loginFormRef"
|
||||||
|
@keyup.enter="handleLogin"
|
||||||
|
>
|
||||||
<el-form-item prop="phone">
|
<el-form-item prop="phone">
|
||||||
<el-input v-model="loginForm.phone" placeholder="请输入手机号" :prefix-icon="User" />
|
<el-input
|
||||||
|
v-model="loginForm.phone"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
:prefix-icon="User"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码" show-password :prefix-icon="Lock" />
|
<el-input
|
||||||
|
v-model="loginForm.password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
show-password
|
||||||
|
:prefix-icon="Lock"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="role">
|
<el-form-item prop="role">
|
||||||
<el-select v-model="loginForm.role" placeholder="请选择登录角色" style="width: 100%;">
|
<el-select
|
||||||
|
v-model="loginForm.role"
|
||||||
|
placeholder="请选择登录角色"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-option label="系统管理员" value="SYSTEM_ADMIN" />
|
<el-option label="系统管理员" value="SYSTEM_ADMIN" />
|
||||||
<el-option label="医院管理员" value="HOSPITAL_ADMIN" />
|
<el-option label="医院管理员" value="HOSPITAL_ADMIN" />
|
||||||
<el-option label="科室主任" value="DIRECTOR" />
|
<el-option label="科室主任" value="DIRECTOR" />
|
||||||
@ -27,17 +46,23 @@
|
|||||||
:min="1"
|
:min="1"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
placeholder="医院 ID(多账号场景建议填写)"
|
placeholder="医院 ID(多账号场景建议填写)"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-alert
|
<el-alert
|
||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
title="若同一手机号在多个医院有同角色账号,请填写医院 ID。"
|
title="若同一手机号在多个医院有同角色账号,请填写医院 ID。"
|
||||||
style="margin-bottom: 16px;"
|
style="margin-bottom: 16px"
|
||||||
/>
|
/>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" class="login-btn" :loading="loading" @click="handleLogin">登录</el-button>
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
class="login-btn"
|
||||||
|
:loading="loading"
|
||||||
|
@click="handleLogin"
|
||||||
|
>登录</el-button
|
||||||
|
>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -68,13 +93,13 @@ const loginForm = reactive({
|
|||||||
const rules = {
|
const rules = {
|
||||||
phone: [
|
phone: [
|
||||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||||
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
{ min: 8, message: '密码长度至少为 8 位', trigger: 'blur' }
|
{ min: 8, message: '密码长度至少为 8 位', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
role: [{ required: true, message: '请选择角色', trigger: 'change' }]
|
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
|
|||||||
@ -163,7 +163,7 @@
|
|||||||
placeholder="可选:选择后将任命为科室主任"
|
placeholder="可选:选择后将任命为科室主任"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="user in directorOptions"
|
v-for="user in directorOptions"
|
||||||
@ -416,7 +416,9 @@ const handleSubmit = async () => {
|
|||||||
let targetHospitalId = form.hospitalId;
|
let targetHospitalId = form.hospitalId;
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
// Some backend update APIs don't allow changing hospitalId, but we'll send it if needed, or just name
|
// Some backend update APIs don't allow changing hospitalId, but we'll send it if needed, or just name
|
||||||
const updated = await updateDepartment(currentId.value, { name: form.name });
|
const updated = await updateDepartment(currentId.value, {
|
||||||
|
name: form.name,
|
||||||
|
});
|
||||||
targetDepartmentId = updated?.id ?? currentId.value;
|
targetDepartmentId = updated?.id ?? currentId.value;
|
||||||
targetHospitalId = updated?.hospitalId ?? form.hospitalId;
|
targetHospitalId = updated?.hospitalId ?? form.hospitalId;
|
||||||
ElMessage.success('更新成功');
|
ElMessage.success('更新成功');
|
||||||
@ -431,10 +433,10 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
canAssignDirectorInDialog.value
|
canAssignDirectorInDialog.value &&
|
||||||
&& form.directorUserId
|
form.directorUserId &&
|
||||||
&& targetDepartmentId
|
targetDepartmentId &&
|
||||||
&& targetHospitalId
|
targetHospitalId
|
||||||
) {
|
) {
|
||||||
await updateUser(form.directorUserId, {
|
await updateUser(form.directorUserId, {
|
||||||
role: 'DIRECTOR',
|
role: 'DIRECTOR',
|
||||||
|
|||||||
@ -200,7 +200,7 @@
|
|||||||
placeholder="可选:选择后将任命为小组组长"
|
placeholder="可选:选择后将任命为小组组长"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="user in leaderOptions"
|
v-for="user in leaderOptions"
|
||||||
@ -497,7 +497,9 @@ const handleSubmit = async () => {
|
|||||||
let targetHospitalId = form.hospitalId;
|
let targetHospitalId = form.hospitalId;
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
// Backend patch dto might just accept name. Sending departmentId may not be allowed or needed.
|
// Backend patch dto might just accept name. Sending departmentId may not be allowed or needed.
|
||||||
const updated = await updateGroup(currentId.value, { name: form.name });
|
const updated = await updateGroup(currentId.value, {
|
||||||
|
name: form.name,
|
||||||
|
});
|
||||||
targetGroupId = updated?.id ?? currentId.value;
|
targetGroupId = updated?.id ?? currentId.value;
|
||||||
targetDepartmentId = updated?.departmentId ?? form.departmentId;
|
targetDepartmentId = updated?.departmentId ?? form.departmentId;
|
||||||
targetHospitalId = updated?.department?.hospitalId ?? form.hospitalId;
|
targetHospitalId = updated?.department?.hospitalId ?? form.hospitalId;
|
||||||
@ -513,11 +515,11 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
canAssignLeaderInDialog.value
|
canAssignLeaderInDialog.value &&
|
||||||
&& form.leaderUserId
|
form.leaderUserId &&
|
||||||
&& targetGroupId
|
targetGroupId &&
|
||||||
&& targetDepartmentId
|
targetDepartmentId &&
|
||||||
&& targetHospitalId
|
targetHospitalId
|
||||||
) {
|
) {
|
||||||
await updateUser(form.leaderUserId, {
|
await updateUser(form.leaderUserId, {
|
||||||
role: 'LEADER',
|
role: 'LEADER',
|
||||||
@ -586,7 +588,11 @@ watch(
|
|||||||
if (!dialogVisible.value) {
|
if (!dialogVisible.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await loadLeaderOptions(hospitalId, departmentId, isEdit.value ? currentId.value : null);
|
await loadLeaderOptions(
|
||||||
|
hospitalId,
|
||||||
|
departmentId,
|
||||||
|
isEdit.value ? currentId.value : null,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -5,18 +5,36 @@
|
|||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||||
<el-form-item label="医院名称">
|
<el-form-item label="医院名称">
|
||||||
<el-input v-model="searchForm.keyword" placeholder="请输入关键词" clearable />
|
<el-input
|
||||||
|
v-model="searchForm.keyword"
|
||||||
|
placeholder="请输入关键词"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="fetchData" icon="Search">查询</el-button>
|
<el-button type="primary" @click="fetchData" icon="Search"
|
||||||
|
>查询</el-button
|
||||||
|
>
|
||||||
<el-button @click="resetSearch" icon="Refresh">重置</el-button>
|
<el-button @click="resetSearch" icon="Refresh">重置</el-button>
|
||||||
<el-button v-if="userStore.role === 'SYSTEM_ADMIN'" type="success" @click="openCreateDialog" icon="Plus">新增医院</el-button>
|
<el-button
|
||||||
|
v-if="userStore.role === 'SYSTEM_ADMIN'"
|
||||||
|
type="success"
|
||||||
|
@click="openCreateDialog"
|
||||||
|
icon="Plus"
|
||||||
|
>新增医院</el-button
|
||||||
|
>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<el-table :data="tableData" v-loading="loading" border stripe style="width: 100%">
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
v-loading="loading"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||||
<el-table-column prop="name" label="医院名称" min-width="200" />
|
<el-table-column prop="name" label="医院名称" min-width="200" />
|
||||||
<el-table-column prop="adminDisplay" label="医院管理员" min-width="220">
|
<el-table-column prop="adminDisplay" label="医院管理员" min-width="220">
|
||||||
@ -31,9 +49,19 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="250" fixed="right" align="center">
|
<el-table-column label="操作" width="250" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button size="small" @click="goToDepartments(row)">管理科室</el-button>
|
<el-button size="small" @click="goToDepartments(row)"
|
||||||
<el-button size="small" type="primary" @click="openEditDialog(row)">编辑</el-button>
|
>管理科室</el-button
|
||||||
<el-button v-if="userStore.role === 'SYSTEM_ADMIN'" size="small" type="danger" @click="handleDelete(row)">删除</el-button>
|
>
|
||||||
|
<el-button size="small" type="primary" @click="openEditDialog(row)"
|
||||||
|
>编辑</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="userStore.role === 'SYSTEM_ADMIN'"
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(row)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -54,18 +82,26 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- Dialog for Create / Edit -->
|
<!-- Dialog for Create / Edit -->
|
||||||
<el-dialog :title="isEdit ? '编辑医院' : '新增医院'" v-model="dialogVisible" width="500px" @close="resetForm">
|
<el-dialog
|
||||||
|
:title="isEdit ? '编辑医院' : '新增医院'"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="500px"
|
||||||
|
@close="resetForm"
|
||||||
|
>
|
||||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||||
<el-form-item label="医院名称" prop="name">
|
<el-form-item label="医院名称" prop="name">
|
||||||
<el-input v-model="form.name" placeholder="请输入医院名称" />
|
<el-input v-model="form.name" placeholder="请输入医院名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="医院管理员" v-if="userStore.role === 'SYSTEM_ADMIN'">
|
<el-form-item
|
||||||
|
label="医院管理员"
|
||||||
|
v-if="userStore.role === 'SYSTEM_ADMIN'"
|
||||||
|
>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.adminUserId"
|
v-model="form.adminUserId"
|
||||||
placeholder="可选:选择后将任命为医院管理员"
|
placeholder="可选:选择后将任命为医院管理员"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="user in hospitalAdminOptions"
|
v-for="user in hospitalAdminOptions"
|
||||||
@ -79,7 +115,12 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
:loading="submitLoading"
|
||||||
|
>确定</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -90,7 +131,12 @@
|
|||||||
import { ref, reactive, onMounted } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { getHospitals, createHospital, updateHospital, deleteHospital } from '../../api/organization';
|
import {
|
||||||
|
getHospitals,
|
||||||
|
createHospital,
|
||||||
|
updateHospital,
|
||||||
|
deleteHospital,
|
||||||
|
} from '../../api/organization';
|
||||||
import { getUsers, updateUser } from '../../api/users';
|
import { getUsers, updateUser } from '../../api/users';
|
||||||
import { useUserStore } from '../../store/user';
|
import { useUserStore } from '../../store/user';
|
||||||
|
|
||||||
@ -117,7 +163,7 @@ const roleMap = {
|
|||||||
const getRoleName = (role) => roleMap[role] || role;
|
const getRoleName = (role) => roleMap[role] || role;
|
||||||
|
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
keyword: ''
|
keyword: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dialog State
|
// Dialog State
|
||||||
@ -133,7 +179,7 @@ const form = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
name: [{ required: true, message: '请输入医院名称', trigger: 'blur' }]
|
name: [{ required: true, message: '请输入医院名称', trigger: 'blur' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Methods ---
|
// --- Methods ---
|
||||||
@ -143,7 +189,7 @@ const fetchData = async () => {
|
|||||||
const res = await getHospitals({
|
const res = await getHospitals({
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
keyword: searchForm.keyword || undefined
|
keyword: searchForm.keyword || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
let hospitalAdminNameMap = {};
|
let hospitalAdminNameMap = {};
|
||||||
@ -166,7 +212,8 @@ const fetchData = async () => {
|
|||||||
|
|
||||||
tableData.value = (res.list || []).map((hospital) => ({
|
tableData.value = (res.list || []).map((hospital) => ({
|
||||||
...hospital,
|
...hospital,
|
||||||
adminDisplay: (hospitalAdminNameMap[hospital.id] || []).join('、') || '未设置',
|
adminDisplay:
|
||||||
|
(hospitalAdminNameMap[hospital.id] || []).join('、') || '未设置',
|
||||||
}));
|
}));
|
||||||
total.value = res.total || 0;
|
total.value = res.total || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -233,7 +280,9 @@ const handleSubmit = async () => {
|
|||||||
try {
|
try {
|
||||||
let targetHospitalId = null;
|
let targetHospitalId = null;
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
const updated = await updateHospital(currentId.value, { name: form.name });
|
const updated = await updateHospital(currentId.value, {
|
||||||
|
name: form.name,
|
||||||
|
});
|
||||||
targetHospitalId = updated?.id ?? currentId.value;
|
targetHospitalId = updated?.id ?? currentId.value;
|
||||||
ElMessage.success('更新成功');
|
ElMessage.success('更新成功');
|
||||||
} else {
|
} else {
|
||||||
@ -272,29 +321,27 @@ const handleSubmit = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(`确定要删除医院 "${row.name}" 吗?`, '警告', {
|
||||||
`确定要删除医院 "${row.name}" 吗?`,
|
confirmButtonText: '确定',
|
||||||
'警告',
|
cancelButtonText: '取消',
|
||||||
{
|
type: 'warning',
|
||||||
confirmButtonText: '确定',
|
})
|
||||||
cancelButtonText: '取消',
|
.then(async () => {
|
||||||
type: 'warning',
|
try {
|
||||||
}
|
await deleteHospital(row.id);
|
||||||
).then(async () => {
|
ElMessage.success('删除成功');
|
||||||
try {
|
fetchData();
|
||||||
await deleteHospital(row.id);
|
} catch (error) {
|
||||||
ElMessage.success('删除成功');
|
console.error('Delete failed', error);
|
||||||
fetchData();
|
}
|
||||||
} catch (error) {
|
})
|
||||||
console.error('Delete failed', error);
|
.catch(() => {});
|
||||||
}
|
|
||||||
}).catch(() => {});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToDepartments = (row) => {
|
const goToDepartments = (row) => {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/organization/departments',
|
path: '/organization/departments',
|
||||||
query: { hospitalId: row.id, hospitalName: row.name }
|
query: { hospitalId: row.id, hospitalName: row.name },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,17 @@
|
|||||||
<el-card shadow="never" class="tree-card">
|
<el-card shadow="never" class="tree-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="header-title"><el-icon><Connection /></el-icon> 组织架构全景图</span>
|
<span class="header-title"
|
||||||
<el-button type="primary" @click="fetchTreeData" icon="Refresh" round size="small">刷新</el-button>
|
><el-icon><Connection /></el-icon> 组织架构全景图</span
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="fetchTreeData"
|
||||||
|
icon="Refresh"
|
||||||
|
round
|
||||||
|
size="small"
|
||||||
|
>刷新</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -26,20 +35,37 @@
|
|||||||
<div class="custom-tree-node" :class="`node-${data.type}`">
|
<div class="custom-tree-node" :class="`node-${data.type}`">
|
||||||
<div class="node-main">
|
<div class="node-main">
|
||||||
<div class="node-icon-wrapper">
|
<div class="node-icon-wrapper">
|
||||||
<el-icon v-if="data.type === 'hospital'"><OfficeBuilding /></el-icon>
|
<el-icon v-if="data.type === 'hospital'"
|
||||||
<el-icon v-else-if="data.type === 'department'"><Filter /></el-icon>
|
><OfficeBuilding
|
||||||
<el-icon v-else-if="data.type === 'group'"><Connection /></el-icon>
|
/></el-icon>
|
||||||
<el-icon v-else-if="data.type === 'user'"><UserFilled /></el-icon>
|
<el-icon v-else-if="data.type === 'department'"
|
||||||
|
><Filter
|
||||||
|
/></el-icon>
|
||||||
|
<el-icon v-else-if="data.type === 'group'"
|
||||||
|
><Connection
|
||||||
|
/></el-icon>
|
||||||
|
<el-icon v-else-if="data.type === 'user'"
|
||||||
|
><UserFilled
|
||||||
|
/></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-text">
|
<div class="node-text">
|
||||||
<span class="node-label">{{ node.label }}</span>
|
<span class="node-label">{{ node.label }}</span>
|
||||||
<span v-if="data.type === 'hospital'" class="node-sub-label">
|
<span
|
||||||
|
v-if="data.type === 'hospital'"
|
||||||
|
class="node-sub-label"
|
||||||
|
>
|
||||||
医院管理员:{{ data.adminDisplay || '未设置' }}
|
医院管理员:{{ data.adminDisplay || '未设置' }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="data.type === 'department'" class="node-sub-label">
|
<span
|
||||||
|
v-else-if="data.type === 'department'"
|
||||||
|
class="node-sub-label"
|
||||||
|
>
|
||||||
主任:{{ data.directorDisplay || '未设置' }}
|
主任:{{ data.directorDisplay || '未设置' }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="data.type === 'group'" class="node-sub-label">
|
<span
|
||||||
|
v-else-if="data.type === 'group'"
|
||||||
|
class="node-sub-label"
|
||||||
|
>
|
||||||
组长:{{ data.leaderDisplay || '未设置' }}
|
组长:{{ data.leaderDisplay || '未设置' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +83,10 @@
|
|||||||
|
|
||||||
<div class="node-actions" v-if="data.type !== 'user'">
|
<div class="node-actions" v-if="data.type !== 'user'">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canAssignOwner && (data.type === 'department' || data.type === 'group')"
|
v-if="
|
||||||
|
canAssignOwner &&
|
||||||
|
(data.type === 'department' || data.type === 'group')
|
||||||
|
"
|
||||||
type="warning"
|
type="warning"
|
||||||
link
|
link
|
||||||
size="small"
|
size="small"
|
||||||
@ -65,17 +94,34 @@
|
|||||||
>
|
>
|
||||||
{{ data.type === 'department' ? '设主任' : '设组长' }}
|
{{ data.type === 'department' ? '设主任' : '设组长' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="canEditNode(data)" type="info" link size="small" @click.stop="openEditDialog(data)" icon="EditPen">
|
<el-button
|
||||||
|
v-if="canEditNode(data)"
|
||||||
|
type="info"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click.stop="openEditDialog(data)"
|
||||||
|
icon="EditPen"
|
||||||
|
>
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="canDeleteNode(data)" type="danger" link size="small" @click.stop="handleDelete(data)" icon="Delete">
|
<el-button
|
||||||
|
v-if="canDeleteNode(data)"
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click.stop="handleDelete(data)"
|
||||||
|
icon="Delete"
|
||||||
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
<el-empty v-if="!loading && treeData.length === 0" description="暂无组织架构数据" />
|
<el-empty
|
||||||
|
v-if="!loading && treeData.length === 0"
|
||||||
|
description="暂无组织架构数据"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -89,7 +135,10 @@
|
|||||||
<el-icon><Menu /></el-icon>
|
<el-icon><Menu /></el-icon>
|
||||||
{{ activePanelTitle }}
|
{{ activePanelTitle }}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="activeNode && activeNode.type !== 'user'" class="header-actions">
|
<div
|
||||||
|
v-if="activeNode && activeNode.type !== 'user'"
|
||||||
|
class="header-actions"
|
||||||
|
>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canCreateDepartment(activeNode)"
|
v-if="canCreateDepartment(activeNode)"
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -118,18 +167,27 @@
|
|||||||
新增人员
|
新增人员
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canAssignOwner && (activeNode.type === 'department' || activeNode.type === 'group')"
|
v-if="
|
||||||
|
canAssignOwner &&
|
||||||
|
(activeNode.type === 'department' ||
|
||||||
|
activeNode.type === 'group')
|
||||||
|
"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@click="openSetOwnerDialog(activeNode)"
|
@click="openSetOwnerDialog(activeNode)"
|
||||||
>
|
>
|
||||||
{{ activeNode.type === 'department' ? '设置主任' : '设置组长' }}
|
{{
|
||||||
|
activeNode.type === 'department' ? '设置主任' : '设置组长'
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="activeNode && activeNode.type !== 'user'" class="node-detail-panel">
|
<div
|
||||||
|
v-if="activeNode && activeNode.type !== 'user'"
|
||||||
|
class="node-detail-panel"
|
||||||
|
>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="activeNodeMeta"
|
v-if="activeNodeMeta"
|
||||||
:title="activeNodeMeta"
|
:title="activeNodeMeta"
|
||||||
@ -137,31 +195,55 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
class="mb-12"
|
class="mb-12"
|
||||||
/>
|
/>
|
||||||
<el-table :data="activeNodeChildren" border stripe style="width: 100%" max-height="600">
|
<el-table
|
||||||
|
:data="activeNodeChildren"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
max-height="600"
|
||||||
|
>
|
||||||
<el-table-column prop="name" label="名称" />
|
<el-table-column prop="name" label="名称" />
|
||||||
<el-table-column label="类型" width="100" align="center">
|
<el-table-column label="类型" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small" :type="getNodeTypeTag(row.type)">{{ getTypeName(row.type) }}</el-tag>
|
<el-tag size="small" :type="getNodeTypeTag(row.type)">{{
|
||||||
|
getTypeName(row.type)
|
||||||
|
}}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="负责人" width="180" align="center">
|
<el-table-column label="负责人" width="180" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.type === 'hospital'">医院管理员:{{ row.adminDisplay || '未设置' }}</span>
|
<span v-if="row.type === 'hospital'"
|
||||||
<span v-else-if="row.type === 'department'">主任:{{ row.directorDisplay || '未设置' }}</span>
|
>医院管理员:{{ row.adminDisplay || '未设置' }}</span
|
||||||
<span v-else-if="row.type === 'group'">组长:{{ row.leaderDisplay || '未设置' }}</span>
|
>
|
||||||
|
<span v-else-if="row.type === 'department'"
|
||||||
|
>主任:{{ row.directorDisplay || '未设置' }}</span
|
||||||
|
>
|
||||||
|
<span v-else-if="row.type === 'group'"
|
||||||
|
>组长:{{ row.leaderDisplay || '未设置' }}</span
|
||||||
|
>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="角色" width="120" align="center">
|
<el-table-column label="角色" width="120" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.type === 'user'">{{ getRoleName(row.role) }}</span>
|
<span v-if="row.type === 'user'">{{
|
||||||
|
getRoleName(row.role)
|
||||||
|
}}</span>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
width="220"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canAssignOwner && (row.type === 'department' || row.type === 'group')"
|
v-if="
|
||||||
|
canAssignOwner &&
|
||||||
|
(row.type === 'department' || row.type === 'group')
|
||||||
|
"
|
||||||
type="warning"
|
type="warning"
|
||||||
link
|
link
|
||||||
size="small"
|
size="small"
|
||||||
@ -169,8 +251,22 @@
|
|||||||
>
|
>
|
||||||
{{ row.type === 'department' ? '设主任' : '设组长' }}
|
{{ row.type === 'department' ? '设主任' : '设组长' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="canEditNode(row)" type="primary" link size="small" @click="openEditDialog(row)">编辑</el-button>
|
<el-button
|
||||||
<el-button v-if="canDeleteNode(row)" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
v-if="canEditNode(row)"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="openEditDialog(row)"
|
||||||
|
>编辑</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="canDeleteNode(row)"
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete(row)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -178,12 +274,24 @@
|
|||||||
|
|
||||||
<div v-else-if="selectedUserDetail" class="user-detail-panel">
|
<div v-else-if="selectedUserDetail" class="user-detail-panel">
|
||||||
<el-descriptions :column="2" border>
|
<el-descriptions :column="2" border>
|
||||||
<el-descriptions-item label="姓名">{{ selectedUserDetail.name || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="姓名">{{
|
||||||
<el-descriptions-item label="角色">{{ getRoleName(selectedUserDetail.role) }}</el-descriptions-item>
|
selectedUserDetail.name || '-'
|
||||||
<el-descriptions-item label="手机号">{{ selectedUserDetail.phone || '-' }}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="医院">{{ selectedUserDetail.hospitalName || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="角色">{{
|
||||||
<el-descriptions-item label="科室">{{ selectedUserDetail.departmentName || '-' }}</el-descriptions-item>
|
getRoleName(selectedUserDetail.role)
|
||||||
<el-descriptions-item label="小组">{{ selectedUserDetail.groupName || '-' }}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="手机号">{{
|
||||||
|
selectedUserDetail.phone || '-'
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="医院">{{
|
||||||
|
selectedUserDetail.hospitalName || '-'
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="科室">{{
|
||||||
|
selectedUserDetail.departmentName || '-'
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="小组">{{
|
||||||
|
selectedUserDetail.groupName || '-'
|
||||||
|
}}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
<el-alert
|
<el-alert
|
||||||
title="如需修改人员角色或组织归属,请前往“用户管理”页面操作。"
|
title="如需修改人员角色或组织归属,请前往“用户管理”页面操作。"
|
||||||
@ -198,16 +306,37 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- Dialog for Create / Edit -->
|
<!-- Dialog for Create / Edit -->
|
||||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="450px" @close="resetForm" destroy-on-close>
|
<el-dialog
|
||||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px" @submit.prevent>
|
:title="dialogTitle"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="450px"
|
||||||
|
@close="resetForm"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
ref="formRef"
|
||||||
|
label-width="100px"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
<el-form-item :label="formLabel" prop="name">
|
<el-form-item :label="formLabel" prop="name">
|
||||||
<el-input v-model="form.name" :placeholder="`请输入${formLabel}`" clearable />
|
<el-input
|
||||||
|
v-model="form.name"
|
||||||
|
:placeholder="`请输入${formLabel}`"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
:loading="submitLoading"
|
||||||
|
>确定</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -224,7 +353,7 @@
|
|||||||
v-model="selectedOwnerUserId"
|
v-model="selectedOwnerUserId"
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择人员"
|
placeholder="请选择人员"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="user in ownerCandidates"
|
v-for="user in ownerCandidates"
|
||||||
@ -243,7 +372,11 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="ownerDialogVisible = false">取消</el-button>
|
<el-button @click="ownerDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" :loading="ownerSubmitLoading" @click="handleSetOwner">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="ownerSubmitLoading"
|
||||||
|
@click="handleSetOwner"
|
||||||
|
>
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -256,10 +389,33 @@
|
|||||||
import { ref, reactive, onMounted, computed } from 'vue';
|
import { ref, reactive, onMounted, computed } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { getHospitals, getDepartments, getGroups, createDepartment, updateDepartment, deleteDepartment, createGroup, updateGroup, deleteGroup, updateHospital, deleteHospital } from '../../api/organization';
|
import {
|
||||||
|
getHospitals,
|
||||||
|
getDepartments,
|
||||||
|
getGroups,
|
||||||
|
createDepartment,
|
||||||
|
updateDepartment,
|
||||||
|
deleteDepartment,
|
||||||
|
createGroup,
|
||||||
|
updateGroup,
|
||||||
|
deleteGroup,
|
||||||
|
updateHospital,
|
||||||
|
deleteHospital,
|
||||||
|
} from '../../api/organization';
|
||||||
import { getUsers, updateUser } from '../../api/users';
|
import { getUsers, updateUser } from '../../api/users';
|
||||||
import { useUserStore } from '../../store/user';
|
import { useUserStore } from '../../store/user';
|
||||||
import { OfficeBuilding, Filter, Connection, UserFilled, Refresh, Plus, EditPen, Delete, Menu, User } from '@element-plus/icons-vue';
|
import {
|
||||||
|
OfficeBuilding,
|
||||||
|
Filter,
|
||||||
|
Connection,
|
||||||
|
UserFilled,
|
||||||
|
Refresh,
|
||||||
|
Plus,
|
||||||
|
EditPen,
|
||||||
|
Delete,
|
||||||
|
Menu,
|
||||||
|
User,
|
||||||
|
} from '@element-plus/icons-vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@ -288,7 +444,7 @@ const roleMap = {
|
|||||||
DIRECTOR: '科室主任',
|
DIRECTOR: '科室主任',
|
||||||
LEADER: '小组组长',
|
LEADER: '小组组长',
|
||||||
DOCTOR: '医生',
|
DOCTOR: '医生',
|
||||||
ENGINEER: '工程师'
|
ENGINEER: '工程师',
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRoleName = (role) => roleMap[role] || role;
|
const getRoleName = (role) => roleMap[role] || role;
|
||||||
@ -302,12 +458,22 @@ const getRoleTagType = (role) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getTypeName = (type) => {
|
const getTypeName = (type) => {
|
||||||
const map = { hospital: '医院', department: '科室', group: '小组', user: '人员' };
|
const map = {
|
||||||
|
hospital: '医院',
|
||||||
|
department: '科室',
|
||||||
|
group: '小组',
|
||||||
|
user: '人员',
|
||||||
|
};
|
||||||
return map[type] || type;
|
return map[type] || type;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNodeTypeTag = (type) => {
|
const getNodeTypeTag = (type) => {
|
||||||
const map = { hospital: 'primary', department: 'success', group: 'warning', user: 'info' };
|
const map = {
|
||||||
|
hospital: 'primary',
|
||||||
|
department: 'success',
|
||||||
|
group: 'warning',
|
||||||
|
user: 'info',
|
||||||
|
};
|
||||||
return map[type] || 'info';
|
return map[type] || 'info';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -326,16 +492,16 @@ const canCreateDepartment = (node) =>
|
|||||||
|
|
||||||
const canCreateGroup = (node) =>
|
const canCreateGroup = (node) =>
|
||||||
Boolean(
|
Boolean(
|
||||||
node
|
node &&
|
||||||
&& node.type === 'department'
|
node.type === 'department' &&
|
||||||
&& (isOrgAdmin.value || isDirector.value),
|
(isOrgAdmin.value || isDirector.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
const canAddUser = (node) =>
|
const canAddUser = (node) =>
|
||||||
Boolean(
|
Boolean(
|
||||||
node
|
node &&
|
||||||
&& (node.type === 'department' || node.type === 'group')
|
(node.type === 'department' || node.type === 'group') &&
|
||||||
&& isOrgAdmin.value,
|
isOrgAdmin.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEditNode = (node) => {
|
const canEditNode = (node) => {
|
||||||
@ -382,9 +548,9 @@ const activePanelTitle = computed(() => {
|
|||||||
|
|
||||||
const activeNodeChildren = computed(() => {
|
const activeNodeChildren = computed(() => {
|
||||||
if (
|
if (
|
||||||
!activeNode.value
|
!activeNode.value ||
|
||||||
|| activeNode.value.type === 'user'
|
activeNode.value.type === 'user' ||
|
||||||
|| !Array.isArray(activeNode.value.children)
|
!Array.isArray(activeNode.value.children)
|
||||||
) {
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -410,7 +576,9 @@ const selectedUserDetail = computed(() => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = allUsers.value.find((user) => user.id === activeNode.value.id);
|
const current = allUsers.value.find(
|
||||||
|
(user) => user.id === activeNode.value.id,
|
||||||
|
);
|
||||||
const userData = current || activeNode.value;
|
const userData = current || activeNode.value;
|
||||||
const hospitalId = userData.hospitalId || null;
|
const hospitalId = userData.hospitalId || null;
|
||||||
const departmentId = userData.departmentId || null;
|
const departmentId = userData.departmentId || null;
|
||||||
@ -466,7 +634,7 @@ const fetchTreeData = async () => {
|
|||||||
const [deptRes, groupRes, userRes] = await Promise.all([
|
const [deptRes, groupRes, userRes] = await Promise.all([
|
||||||
getDepartments({ pageSize: 100 }),
|
getDepartments({ pageSize: 100 }),
|
||||||
getGroups({ pageSize: 100 }),
|
getGroups({ pageSize: 100 }),
|
||||||
getUsers()
|
getUsers(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const departments = deptRes.list || [];
|
const departments = deptRes.list || [];
|
||||||
@ -507,51 +675,86 @@ const fetchTreeData = async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const tree = hospitals.map(h => {
|
const tree = hospitals.map((h) => {
|
||||||
const hDepts = departments.filter(d => d.hospitalId === h.id);
|
const hDepts = departments.filter((d) => d.hospitalId === h.id);
|
||||||
const adminDisplay =
|
const adminDisplay =
|
||||||
(hospitalAdminNameMap[h.id] || []).join('、') || '未设置';
|
(hospitalAdminNameMap[h.id] || []).join('、') || '未设置';
|
||||||
|
|
||||||
const deptNodes = hDepts.map(d => {
|
const deptNodes = hDepts.map((d) => {
|
||||||
const dGroups = groups.filter(g => g.departmentId === d.id);
|
const dGroups = groups.filter((g) => g.departmentId === d.id);
|
||||||
const directorDisplay =
|
const directorDisplay =
|
||||||
(directorNameMap[d.id] || []).join('、') || '未设置';
|
(directorNameMap[d.id] || []).join('、') || '未设置';
|
||||||
|
|
||||||
const groupNodes = dGroups.map(g => {
|
const groupNodes = dGroups.map((g) => {
|
||||||
const gUsers = users.filter(u => u.groupId === g.id);
|
const gUsers = users.filter((u) => u.groupId === g.id);
|
||||||
const leaderDisplay =
|
const leaderDisplay =
|
||||||
(leaderNameMap[g.id] || []).join('、') || '未设置';
|
(leaderNameMap[g.id] || []).join('、') || '未设置';
|
||||||
const userNodes = gUsers.map(u => ({
|
const userNodes = gUsers.map((u) => ({
|
||||||
key: `u_${u.id}`, id: u.id, name: u.name, type: 'user', role: u.role,
|
key: `u_${u.id}`,
|
||||||
hospitalId: h.id, departmentId: d.id, groupId: g.id
|
id: u.id,
|
||||||
|
name: u.name,
|
||||||
|
type: 'user',
|
||||||
|
role: u.role,
|
||||||
|
hospitalId: h.id,
|
||||||
|
departmentId: d.id,
|
||||||
|
groupId: g.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: `g_${g.id}`, id: g.id, name: g.name, type: 'group',
|
key: `g_${g.id}`,
|
||||||
departmentId: d.id, hospitalId: h.id, leaderDisplay, children: userNodes
|
id: g.id,
|
||||||
|
name: g.name,
|
||||||
|
type: 'group',
|
||||||
|
departmentId: d.id,
|
||||||
|
hospitalId: h.id,
|
||||||
|
leaderDisplay,
|
||||||
|
children: userNodes,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const dUsers = users.filter(u => u.departmentId === d.id && !u.groupId);
|
const dUsers = users.filter(
|
||||||
const dUserNodes = dUsers.map(u => ({
|
(u) => u.departmentId === d.id && !u.groupId,
|
||||||
key: `u_${u.id}`, id: u.id, name: u.name, type: 'user', role: u.role,
|
);
|
||||||
hospitalId: h.id, departmentId: d.id
|
const dUserNodes = dUsers.map((u) => ({
|
||||||
|
key: `u_${u.id}`,
|
||||||
|
id: u.id,
|
||||||
|
name: u.name,
|
||||||
|
type: 'user',
|
||||||
|
role: u.role,
|
||||||
|
hospitalId: h.id,
|
||||||
|
departmentId: d.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: `d_${d.id}`, id: d.id, name: d.name, type: 'department',
|
key: `d_${d.id}`,
|
||||||
hospitalId: h.id, directorDisplay, children: [...groupNodes, ...dUserNodes]
|
id: d.id,
|
||||||
|
name: d.name,
|
||||||
|
type: 'department',
|
||||||
|
hospitalId: h.id,
|
||||||
|
directorDisplay,
|
||||||
|
children: [...groupNodes, ...dUserNodes],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const hUsers = users.filter(u => u.hospitalId === h.id && !u.departmentId);
|
const hUsers = users.filter(
|
||||||
const hUserNodes = hUsers.map(u => ({
|
(u) => u.hospitalId === h.id && !u.departmentId,
|
||||||
key: `u_${u.id}`, id: u.id, name: u.name, type: 'user', role: u.role,
|
);
|
||||||
hospitalId: h.id
|
const hUserNodes = hUsers.map((u) => ({
|
||||||
|
key: `u_${u.id}`,
|
||||||
|
id: u.id,
|
||||||
|
name: u.name,
|
||||||
|
type: 'user',
|
||||||
|
role: u.role,
|
||||||
|
hospitalId: h.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: `h_${h.id}`, id: h.id, name: h.name, type: 'hospital', adminDisplay, children: [...deptNodes, ...hUserNodes]
|
key: `h_${h.id}`,
|
||||||
|
id: h.id,
|
||||||
|
name: h.name,
|
||||||
|
type: 'hospital',
|
||||||
|
adminDisplay,
|
||||||
|
children: [...deptNodes, ...hUserNodes],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -580,17 +783,19 @@ const openSetOwnerDialog = (node) => {
|
|||||||
selectedOwnerUserId.value = null;
|
selectedOwnerUserId.value = null;
|
||||||
|
|
||||||
if (node.type === 'department') {
|
if (node.type === 'department') {
|
||||||
ownerCandidates.value = allUsers.value.filter((user) =>
|
ownerCandidates.value = allUsers.value.filter(
|
||||||
user.hospitalId === node.hospitalId
|
(user) =>
|
||||||
&& user.departmentId === node.id
|
user.hospitalId === node.hospitalId &&
|
||||||
&& user.role === 'DIRECTOR',
|
user.departmentId === node.id &&
|
||||||
|
user.role === 'DIRECTOR',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ownerCandidates.value = allUsers.value.filter((user) =>
|
ownerCandidates.value = allUsers.value.filter(
|
||||||
user.hospitalId === node.hospitalId
|
(user) =>
|
||||||
&& user.departmentId === node.departmentId
|
user.hospitalId === node.hospitalId &&
|
||||||
&& user.groupId === node.id
|
user.departmentId === node.departmentId &&
|
||||||
&& user.role === 'LEADER',
|
user.groupId === node.id &&
|
||||||
|
user.role === 'LEADER',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +805,9 @@ const openSetOwnerDialog = (node) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentOwner = ownerCandidates.value.find((user) =>
|
const currentOwner = ownerCandidates.value.find((user) =>
|
||||||
node.type === 'department' ? user.role === 'DIRECTOR' : user.role === 'LEADER',
|
node.type === 'department'
|
||||||
|
? user.role === 'DIRECTOR'
|
||||||
|
: user.role === 'LEADER',
|
||||||
);
|
);
|
||||||
selectedOwnerUserId.value = currentOwner?.id ?? ownerCandidates.value[0].id;
|
selectedOwnerUserId.value = currentOwner?.id ?? ownerCandidates.value[0].id;
|
||||||
ownerDialogVisible.value = true;
|
ownerDialogVisible.value = true;
|
||||||
@ -623,14 +830,14 @@ const handleSetOwner = async () => {
|
|||||||
const isDepartment = ownerTargetNode.value.type === 'department';
|
const isDepartment = ownerTargetNode.value.type === 'department';
|
||||||
const payload = isDepartment
|
const payload = isDepartment
|
||||||
? {
|
? {
|
||||||
role: 'DIRECTOR',
|
role: 'DIRECTOR',
|
||||||
// 后端约束:非 DOCTOR 不允许“调整”科室/小组归属。
|
// 后端约束:非 DOCTOR 不允许“调整”科室/小组归属。
|
||||||
// 这里仅做角色变更,并清空小组归属,避免触发该约束。
|
// 这里仅做角色变更,并清空小组归属,避免触发该约束。
|
||||||
groupId: null,
|
groupId: null,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
role: 'LEADER',
|
role: 'LEADER',
|
||||||
};
|
};
|
||||||
|
|
||||||
ownerSubmitLoading.value = true;
|
ownerSubmitLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -651,8 +858,9 @@ const goToAddUser = (nodeData) => {
|
|||||||
query: {
|
query: {
|
||||||
action: 'create',
|
action: 'create',
|
||||||
hospitalId: nodeData.hospitalId,
|
hospitalId: nodeData.hospitalId,
|
||||||
departmentId: nodeData.type === 'department' ? nodeData.id : nodeData.departmentId,
|
departmentId:
|
||||||
}
|
nodeData.type === 'department' ? nodeData.id : nodeData.departmentId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -666,17 +874,45 @@ const parentId = ref(null);
|
|||||||
const currentId = ref(null);
|
const currentId = ref(null);
|
||||||
|
|
||||||
const form = reactive({ name: '' });
|
const form = reactive({ name: '' });
|
||||||
const rules = { name: [{ required: true, message: '请输入名称', trigger: 'blur' }] };
|
const rules = {
|
||||||
|
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||||
|
};
|
||||||
|
|
||||||
const dialogTitle = computed(() => {
|
const dialogTitle = computed(() => {
|
||||||
const typeName = dialogType.value === 'hospital' ? '医院' : (dialogType.value === 'department' ? '科室' : '小组');
|
const typeName =
|
||||||
|
dialogType.value === 'hospital'
|
||||||
|
? '医院'
|
||||||
|
: dialogType.value === 'department'
|
||||||
|
? '科室'
|
||||||
|
: '小组';
|
||||||
return dialogMode.value === 'create' ? `新增${typeName}` : `编辑${typeName}`;
|
return dialogMode.value === 'create' ? `新增${typeName}` : `编辑${typeName}`;
|
||||||
});
|
});
|
||||||
const formLabel = computed(() => dialogType.value === 'hospital' ? '医院名称' : (dialogType.value === 'department' ? '科室名称' : '小组名称'));
|
const formLabel = computed(() =>
|
||||||
|
dialogType.value === 'hospital'
|
||||||
|
? '医院名称'
|
||||||
|
: dialogType.value === 'department'
|
||||||
|
? '科室名称'
|
||||||
|
: '小组名称',
|
||||||
|
);
|
||||||
|
|
||||||
const openCreateDialog = (type, pId) => { dialogType.value = type; dialogMode.value = 'create'; parentId.value = pId; currentId.value = null; dialogVisible.value = true; };
|
const openCreateDialog = (type, pId) => {
|
||||||
const openEditDialog = (data) => { dialogType.value = data.type; dialogMode.value = 'edit'; currentId.value = data.id; form.name = data.name; dialogVisible.value = true; };
|
dialogType.value = type;
|
||||||
const resetForm = () => { if (formRef.value) formRef.value.resetFields(); form.name = ''; };
|
dialogMode.value = 'create';
|
||||||
|
parentId.value = pId;
|
||||||
|
currentId.value = null;
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
const openEditDialog = (data) => {
|
||||||
|
dialogType.value = data.type;
|
||||||
|
dialogMode.value = 'edit';
|
||||||
|
currentId.value = data.id;
|
||||||
|
form.name = data.name;
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
const resetForm = () => {
|
||||||
|
if (formRef.value) formRef.value.resetFields();
|
||||||
|
form.name = '';
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!formRef.value) return;
|
if (!formRef.value) return;
|
||||||
@ -685,45 +921,78 @@ const handleSubmit = async () => {
|
|||||||
submitLoading.value = true;
|
submitLoading.value = true;
|
||||||
try {
|
try {
|
||||||
if (dialogMode.value === 'create') {
|
if (dialogMode.value === 'create') {
|
||||||
if (dialogType.value === 'department') await createDepartment({ name: form.name, hospitalId: parentId.value });
|
if (dialogType.value === 'department')
|
||||||
else if (dialogType.value === 'group') await createGroup({ name: form.name, departmentId: parentId.value });
|
await createDepartment({
|
||||||
|
name: form.name,
|
||||||
|
hospitalId: parentId.value,
|
||||||
|
});
|
||||||
|
else if (dialogType.value === 'group')
|
||||||
|
await createGroup({
|
||||||
|
name: form.name,
|
||||||
|
departmentId: parentId.value,
|
||||||
|
});
|
||||||
ElMessage.success('创建成功');
|
ElMessage.success('创建成功');
|
||||||
} else {
|
} else {
|
||||||
if (dialogType.value === 'hospital') await updateHospital(currentId.value, { name: form.name });
|
if (dialogType.value === 'hospital')
|
||||||
else if (dialogType.value === 'department') await updateDepartment(currentId.value, { name: form.name });
|
await updateHospital(currentId.value, { name: form.name });
|
||||||
else if (dialogType.value === 'group') await updateGroup(currentId.value, { name: form.name });
|
else if (dialogType.value === 'department')
|
||||||
|
await updateDepartment(currentId.value, { name: form.name });
|
||||||
|
else if (dialogType.value === 'group')
|
||||||
|
await updateGroup(currentId.value, { name: form.name });
|
||||||
ElMessage.success('更新成功');
|
ElMessage.success('更新成功');
|
||||||
|
|
||||||
// Update activeNode locally if it's the one edited
|
// Update activeNode locally if it's the one edited
|
||||||
if (activeNode.value && activeNode.value.id === currentId.value && activeNode.value.type === dialogType.value) {
|
if (
|
||||||
|
activeNode.value &&
|
||||||
|
activeNode.value.id === currentId.value &&
|
||||||
|
activeNode.value.type === dialogType.value
|
||||||
|
) {
|
||||||
activeNode.value.name = form.name;
|
activeNode.value.name = form.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
fetchTreeData();
|
fetchTreeData();
|
||||||
} catch (error) { console.error(error); } finally { submitLoading.value = false; }
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (data) => {
|
const handleDelete = (data) => {
|
||||||
const typeName = data.type === 'hospital' ? '医院' : (data.type === 'department' ? '科室' : '小组');
|
const typeName =
|
||||||
ElMessageBox.confirm(`确定要删除${typeName} "${data.name}" 吗?`, '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
data.type === 'hospital'
|
||||||
.then(async () => {
|
? '医院'
|
||||||
try {
|
: data.type === 'department'
|
||||||
if (data.type === 'hospital') await deleteHospital(data.id);
|
? '科室'
|
||||||
else if (data.type === 'department') await deleteDepartment(data.id);
|
: '小组';
|
||||||
else if (data.type === 'group') await deleteGroup(data.id);
|
ElMessageBox.confirm(`确定要删除${typeName} "${data.name}" 吗?`, '警告', {
|
||||||
ElMessage.success('删除成功');
|
confirmButtonText: '确定',
|
||||||
if (activeNode.value && activeNode.value.key === data.key) {
|
cancelButtonText: '取消',
|
||||||
activeNode.value = null; // Clear active node if deleted
|
type: 'warning',
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
if (data.type === 'hospital') await deleteHospital(data.id);
|
||||||
|
else if (data.type === 'department') await deleteDepartment(data.id);
|
||||||
|
else if (data.type === 'group') await deleteGroup(data.id);
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
if (activeNode.value && activeNode.value.key === data.key) {
|
||||||
|
activeNode.value = null; // Clear active node if deleted
|
||||||
|
}
|
||||||
|
fetchTreeData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
fetchTreeData();
|
})
|
||||||
} catch (error) { console.error(error); }
|
.catch(() => {});
|
||||||
}).catch(() => {});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => { fetchTreeData(); });
|
onMounted(() => {
|
||||||
|
fetchTreeData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -731,7 +1000,8 @@ onMounted(() => { fetchTreeData(); });
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-card, .detail-card {
|
.tree-card,
|
||||||
|
.detail-card {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
height: calc(100vh - 120px);
|
height: calc(100vh - 120px);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -846,10 +1116,19 @@ onMounted(() => { fetchTreeData(); });
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-hospital .node-icon-wrapper { color: #409EFF; }
|
.node-hospital .node-icon-wrapper {
|
||||||
.node-department .node-icon-wrapper { color: #67C23A; }
|
color: #409eff;
|
||||||
.node-group .node-icon-wrapper { color: #E6A23C; }
|
}
|
||||||
.node-user .node-icon-wrapper { color: #909399; font-size: 14px; }
|
.node-department .node-icon-wrapper {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
.node-group .node-icon-wrapper {
|
||||||
|
color: #e6a23c;
|
||||||
|
}
|
||||||
|
.node-user .node-icon-wrapper {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.node-label {
|
.node-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
v-model="searchForm.hospitalId"
|
v-model="searchForm.hospitalId"
|
||||||
placeholder="系统管理员必须选择医院"
|
placeholder="系统管理员必须选择医院"
|
||||||
clearable
|
clearable
|
||||||
style="width: 240px;"
|
style="width: 240px"
|
||||||
@change="handleSearchHospitalChange"
|
@change="handleSearchHospitalChange"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@ -30,9 +30,13 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="handleSearch" icon="Search">查询</el-button>
|
<el-button type="primary" @click="handleSearch" icon="Search"
|
||||||
|
>查询</el-button
|
||||||
|
>
|
||||||
<el-button @click="resetSearch" icon="Refresh">重置</el-button>
|
<el-button @click="resetSearch" icon="Refresh">重置</el-button>
|
||||||
<el-button type="success" @click="openCreateDialog" icon="Plus">新增患者</el-button>
|
<el-button type="success" @click="openCreateDialog" icon="Plus"
|
||||||
|
>新增患者</el-button
|
||||||
|
>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
@ -42,10 +46,16 @@
|
|||||||
type="warning"
|
type="warning"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
title="系统管理员查询患者时必须先选择医院。"
|
title="系统管理员查询患者时必须先选择医院。"
|
||||||
style="margin-bottom: 16px;"
|
style="margin-bottom: 16px"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<el-table :data="tableData" v-loading="loading" border stripe style="width: 100%">
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
v-loading="loading"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||||
<el-table-column prop="name" label="姓名" min-width="120" />
|
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||||
<el-table-column prop="phone" label="手机号" min-width="140" />
|
<el-table-column prop="phone" label="手机号" min-width="140" />
|
||||||
@ -62,7 +72,11 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button size="small" type="primary" @click="openRecordDialog(row)">
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="openRecordDialog(row)"
|
||||||
|
>
|
||||||
详情
|
详情
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" @click="openEditDialog(row)">
|
<el-button size="small" @click="openEditDialog(row)">
|
||||||
@ -114,7 +128,7 @@
|
|||||||
filterable
|
filterable
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择归属医生(按科室/小组)"
|
placeholder="请选择归属医生(按科室/小组)"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
:disabled="userStore.role === 'DOCTOR'"
|
:disabled="userStore.role === 'DOCTOR'"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -122,7 +136,11 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="submitLoading"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -131,13 +149,27 @@
|
|||||||
|
|
||||||
<el-dialog title="调压记录详情" v-model="recordDialogVisible" width="860px">
|
<el-dialog title="调压记录详情" v-model="recordDialogVisible" width="860px">
|
||||||
<el-descriptions :column="4" border class="mb-16">
|
<el-descriptions :column="4" border class="mb-16">
|
||||||
<el-descriptions-item label="患者">{{ currentPatientName || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="患者">{{
|
||||||
<el-descriptions-item label="手机号">{{ recordSummary.phone || '-' }}</el-descriptions-item>
|
currentPatientName || '-'
|
||||||
<el-descriptions-item label="身份证">{{ recordSummary.idCardHash || '-' }}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="记录数">{{ recordList.length }}</el-descriptions-item>
|
<el-descriptions-item label="手机号">{{
|
||||||
|
recordSummary.phone || '-'
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="身份证">{{
|
||||||
|
recordSummary.idCardHash || '-'
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="记录数">{{
|
||||||
|
recordList.length
|
||||||
|
}}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-table :data="recordList" v-loading="recordLoading" border stripe max-height="520">
|
<el-table
|
||||||
|
:data="recordList"
|
||||||
|
v-loading="recordLoading"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
max-height="520"
|
||||||
|
>
|
||||||
<el-table-column label="时间" width="180">
|
<el-table-column label="时间" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ new Date(row.occurredAt).toLocaleString() }}
|
{{ new Date(row.occurredAt).toLocaleString() }}
|
||||||
@ -155,7 +187,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="压力变更" min-width="140">
|
<el-table-column label="压力变更" min-width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.taskItem?.oldPressure ?? '-' }} -> {{ row.taskItem?.targetPressure ?? '-' }}
|
{{ row.taskItem?.oldPressure ?? '-' }} ->
|
||||||
|
{{ row.taskItem?.targetPressure ?? '-' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="医院" min-width="140">
|
<el-table-column label="医院" min-width="140">
|
||||||
@ -260,11 +293,15 @@ const doctorTreeProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const departmentNameMap = computed(() => {
|
const departmentNameMap = computed(() => {
|
||||||
return Object.fromEntries((departments.value || []).map((item) => [item.id, item.name]));
|
return Object.fromEntries(
|
||||||
|
(departments.value || []).map((item) => [item.id, item.name]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupNameMap = computed(() => {
|
const groupNameMap = computed(() => {
|
||||||
return Object.fromEntries((groups.value || []).map((item) => [item.id, item.name]));
|
return Object.fromEntries(
|
||||||
|
(groups.value || []).map((item) => [item.id, item.name]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const doctorTreeOptions = computed(() => {
|
const doctorTreeOptions = computed(() => {
|
||||||
@ -316,16 +353,19 @@ const doctorTreeOptions = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(deptMap.values()).sort((a, b) => String(a.label).localeCompare(String(b.label), 'zh-Hans-CN'));
|
return Array.from(deptMap.values()).sort((a, b) =>
|
||||||
|
String(a.label).localeCompare(String(b.label), 'zh-Hans-CN'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const applyFiltersAndPagination = () => {
|
const applyFiltersAndPagination = () => {
|
||||||
const keyword = searchForm.keyword.trim();
|
const keyword = searchForm.keyword.trim();
|
||||||
|
|
||||||
const filtered = allPatients.value.filter((patient) => {
|
const filtered = allPatients.value.filter((patient) => {
|
||||||
const hitKeyword = !keyword
|
const hitKeyword =
|
||||||
|| patient.name?.includes(keyword)
|
!keyword ||
|
||||||
|| patient.phone?.includes(keyword);
|
patient.name?.includes(keyword) ||
|
||||||
|
patient.phone?.includes(keyword);
|
||||||
|
|
||||||
return hitKeyword;
|
return hitKeyword;
|
||||||
});
|
});
|
||||||
@ -481,15 +521,11 @@ const handleSubmit = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(`确定要删除患者 "${row.name}" 吗?`, '警告', {
|
||||||
`确定要删除患者 "${row.name}" 吗?`,
|
confirmButtonText: '确定',
|
||||||
'警告',
|
cancelButtonText: '取消',
|
||||||
{
|
type: 'warning',
|
||||||
confirmButtonText: '确定',
|
})
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await deletePatient(row.id);
|
await deletePatient(row.id);
|
||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
|
|||||||
@ -36,7 +36,13 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="tableData" v-loading="loading" border stripe style="width: 100%">
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
v-loading="loading"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||||
<el-table-column prop="name" label="姓名" min-width="120" />
|
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||||
<el-table-column prop="phone" label="手机号" min-width="150" />
|
<el-table-column prop="phone" label="手机号" min-width="150" />
|
||||||
@ -65,7 +71,9 @@
|
|||||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.role === 'ENGINEER' && userStore.role === 'SYSTEM_ADMIN'"
|
v-if="
|
||||||
|
row.role === 'ENGINEER' && userStore.role === 'SYSTEM_ADMIN'
|
||||||
|
"
|
||||||
size="small"
|
size="small"
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="openAssignDialog(row)"
|
@click="openAssignDialog(row)"
|
||||||
@ -123,7 +131,11 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="角色" prop="role">
|
<el-form-item label="角色" prop="role">
|
||||||
<el-select v-model="form.role" placeholder="请选择角色" style="width: 100%;">
|
<el-select
|
||||||
|
v-model="form.role"
|
||||||
|
placeholder="请选择角色"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-option label="系统管理员" value="SYSTEM_ADMIN" />
|
<el-option label="系统管理员" value="SYSTEM_ADMIN" />
|
||||||
<el-option label="医院管理员" value="HOSPITAL_ADMIN" />
|
<el-option label="医院管理员" value="HOSPITAL_ADMIN" />
|
||||||
<el-option label="科室主任" value="DIRECTOR" />
|
<el-option label="科室主任" value="DIRECTOR" />
|
||||||
@ -137,7 +149,7 @@
|
|||||||
<el-select
|
<el-select
|
||||||
v-model="form.hospitalId"
|
v-model="form.hospitalId"
|
||||||
placeholder="请选择医院"
|
placeholder="请选择医院"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="hospital in hospitals"
|
v-for="hospital in hospitals"
|
||||||
@ -148,11 +160,15 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="所属科室" prop="departmentId" v-if="needDepartment">
|
<el-form-item
|
||||||
|
label="所属科室"
|
||||||
|
prop="departmentId"
|
||||||
|
v-if="needDepartment"
|
||||||
|
>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.departmentId"
|
v-model="form.departmentId"
|
||||||
placeholder="请选择科室"
|
placeholder="请选择科室"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="department in formDepartments"
|
v-for="department in formDepartments"
|
||||||
@ -167,7 +183,7 @@
|
|||||||
<el-select
|
<el-select
|
||||||
v-model="form.groupId"
|
v-model="form.groupId"
|
||||||
placeholder="请选择小组"
|
placeholder="请选择小组"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="group in formGroups"
|
v-for="group in formGroups"
|
||||||
@ -181,7 +197,11 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
:loading="submitLoading"
|
||||||
|
>
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -201,7 +221,7 @@
|
|||||||
<el-select
|
<el-select
|
||||||
v-model="assignHospitalId"
|
v-model="assignHospitalId"
|
||||||
placeholder="请选择医院"
|
placeholder="请选择医院"
|
||||||
style="width: 100%;"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="hospital in hospitals"
|
v-for="hospital in hospitals"
|
||||||
@ -215,7 +235,11 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="assignDialogVisible = false">取消</el-button>
|
<el-button @click="assignDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleAssignSubmit" :loading="submitLoading">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleAssignSubmit"
|
||||||
|
:loading="submitLoading"
|
||||||
|
>
|
||||||
确定
|
确定
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -286,9 +310,7 @@ const needHospital = computed(() => form.role && form.role !== 'SYSTEM_ADMIN');
|
|||||||
const needDepartment = computed(() =>
|
const needDepartment = computed(() =>
|
||||||
['DIRECTOR', 'LEADER', 'DOCTOR'].includes(form.role),
|
['DIRECTOR', 'LEADER', 'DOCTOR'].includes(form.role),
|
||||||
);
|
);
|
||||||
const needGroup = computed(() =>
|
const needGroup = computed(() => ['LEADER', 'DOCTOR'].includes(form.role));
|
||||||
['LEADER', 'DOCTOR'].includes(form.role),
|
|
||||||
);
|
|
||||||
|
|
||||||
const rules = computed(() => ({
|
const rules = computed(() => ({
|
||||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||||
@ -299,9 +321,9 @@ const rules = computed(() => ({
|
|||||||
password: isEdit.value
|
password: isEdit.value
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
{ min: 8, message: '密码长度至少为 8 位', trigger: 'blur' },
|
{ min: 8, message: '密码长度至少为 8 位', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
||||||
hospitalId: needHospital.value
|
hospitalId: needHospital.value
|
||||||
? [{ required: true, message: '请选择所属医院', trigger: 'change' }]
|
? [{ required: true, message: '请选择所属医院', trigger: 'change' }]
|
||||||
@ -332,14 +354,14 @@ const getRoleTagType = (role) => {
|
|||||||
return 'info';
|
return 'info';
|
||||||
};
|
};
|
||||||
|
|
||||||
const hospitalMap = computed(() =>
|
const hospitalMap = computed(
|
||||||
new Map(hospitals.value.map((item) => [item.id, item.name])),
|
() => new Map(hospitals.value.map((item) => [item.id, item.name])),
|
||||||
);
|
);
|
||||||
const departmentMap = computed(() =>
|
const departmentMap = computed(
|
||||||
new Map(departments.value.map((item) => [item.id, item.name])),
|
() => new Map(departments.value.map((item) => [item.id, item.name])),
|
||||||
);
|
);
|
||||||
const groupMap = computed(() =>
|
const groupMap = computed(
|
||||||
new Map(groups.value.map((item) => [item.id, item.name])),
|
() => new Map(groups.value.map((item) => [item.id, item.name])),
|
||||||
);
|
);
|
||||||
|
|
||||||
const resolveHospitalName = (id) => {
|
const resolveHospitalName = (id) => {
|
||||||
@ -570,15 +592,11 @@ const handleSubmit = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(`确定要删除用户 "${row.name}" 吗?`, '警告', {
|
||||||
`确定要删除用户 "${row.name}" 吗?`,
|
confirmButtonText: '确定',
|
||||||
'警告',
|
cancelButtonText: '取消',
|
||||||
{
|
type: 'warning',
|
||||||
confirmButtonText: '确定',
|
})
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await deleteUser(row.id);
|
await deleteUser(row.id);
|
||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
@ -601,7 +619,10 @@ const handleAssignSubmit = async () => {
|
|||||||
|
|
||||||
submitLoading.value = true;
|
submitLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await assignEngineerHospital(currentAssignUser.value.id, assignHospitalId.value);
|
await assignEngineerHospital(
|
||||||
|
currentAssignUser.value.id,
|
||||||
|
assignHospitalId.value,
|
||||||
|
);
|
||||||
ElMessage.success('分配成功');
|
ElMessage.success('分配成功');
|
||||||
assignDialogVisible.value = false;
|
assignDialogVisible.value = false;
|
||||||
await fetchCommonData();
|
await fetchCommonData();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user