医院管理页新增医院管理员列并支持任命医院管理员
组织架构树展示医院管理员信息 科室与小组弹窗支持设置主任/组长并限制候选角色 患者页优化归属医生选择与字段文案 统一“小组组长”角色文案
This commit is contained in:
parent
b527256874
commit
5fdf4c80e6
@ -4,15 +4,34 @@
|
||||
<template #header>
|
||||
<h2 class="login-title">调压通管理后台</h2>
|
||||
</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-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 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 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="HOSPITAL_ADMIN" />
|
||||
<el-option label="科室主任" value="DIRECTOR" />
|
||||
@ -27,17 +46,23 @@
|
||||
:min="1"
|
||||
:controls="false"
|
||||
placeholder="医院 ID(多账号场景建议填写)"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
title="若同一手机号在多个医院有同角色账号,请填写医院 ID。"
|
||||
style="margin-bottom: 16px;"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<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>
|
||||
</el-card>
|
||||
@ -68,13 +93,13 @@ const loginForm = reactive({
|
||||
const rules = {
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' },
|
||||
],
|
||||
password: [
|
||||
{ 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 () => {
|
||||
|
||||
@ -163,7 +163,7 @@
|
||||
placeholder="可选:选择后将任命为科室主任"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in directorOptions"
|
||||
@ -416,7 +416,9 @@ const handleSubmit = async () => {
|
||||
let targetHospitalId = form.hospitalId;
|
||||
if (isEdit.value) {
|
||||
// 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;
|
||||
targetHospitalId = updated?.hospitalId ?? form.hospitalId;
|
||||
ElMessage.success('更新成功');
|
||||
@ -431,10 +433,10 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
if (
|
||||
canAssignDirectorInDialog.value
|
||||
&& form.directorUserId
|
||||
&& targetDepartmentId
|
||||
&& targetHospitalId
|
||||
canAssignDirectorInDialog.value &&
|
||||
form.directorUserId &&
|
||||
targetDepartmentId &&
|
||||
targetHospitalId
|
||||
) {
|
||||
await updateUser(form.directorUserId, {
|
||||
role: 'DIRECTOR',
|
||||
|
||||
@ -200,7 +200,7 @@
|
||||
placeholder="可选:选择后将任命为小组组长"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in leaderOptions"
|
||||
@ -497,7 +497,9 @@ const handleSubmit = async () => {
|
||||
let targetHospitalId = form.hospitalId;
|
||||
if (isEdit.value) {
|
||||
// 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;
|
||||
targetDepartmentId = updated?.departmentId ?? form.departmentId;
|
||||
targetHospitalId = updated?.department?.hospitalId ?? form.hospitalId;
|
||||
@ -513,11 +515,11 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
if (
|
||||
canAssignLeaderInDialog.value
|
||||
&& form.leaderUserId
|
||||
&& targetGroupId
|
||||
&& targetDepartmentId
|
||||
&& targetHospitalId
|
||||
canAssignLeaderInDialog.value &&
|
||||
form.leaderUserId &&
|
||||
targetGroupId &&
|
||||
targetDepartmentId &&
|
||||
targetHospitalId
|
||||
) {
|
||||
await updateUser(form.leaderUserId, {
|
||||
role: 'LEADER',
|
||||
@ -586,7 +588,11 @@ watch(
|
||||
if (!dialogVisible.value) {
|
||||
return;
|
||||
}
|
||||
await loadLeaderOptions(hospitalId, departmentId, isEdit.value ? currentId.value : null);
|
||||
await loadLeaderOptions(
|
||||
hospitalId,
|
||||
departmentId,
|
||||
isEdit.value ? currentId.value : null,
|
||||
);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@ -5,18 +5,36 @@
|
||||
<div class="header-actions">
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<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-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 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>
|
||||
</div>
|
||||
|
||||
<!-- 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="name" label="医院名称" min-width="200" />
|
||||
<el-table-column prop="adminDisplay" label="医院管理员" min-width="220">
|
||||
@ -31,9 +49,19 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="goToDepartments(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>
|
||||
<el-button size="small" @click="goToDepartments(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>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -54,18 +82,26 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 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-item label="医院名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入医院名称" />
|
||||
</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
|
||||
v-model="form.adminUserId"
|
||||
placeholder="可选:选择后将任命为医院管理员"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in hospitalAdminOptions"
|
||||
@ -79,7 +115,12 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -90,7 +131,12 @@
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
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 { useUserStore } from '../../store/user';
|
||||
|
||||
@ -117,7 +163,7 @@ const roleMap = {
|
||||
const getRoleName = (role) => roleMap[role] || role;
|
||||
|
||||
const searchForm = reactive({
|
||||
keyword: ''
|
||||
keyword: '',
|
||||
});
|
||||
|
||||
// Dialog State
|
||||
@ -133,7 +179,7 @@ const form = reactive({
|
||||
});
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入医院名称', trigger: 'blur' }]
|
||||
name: [{ required: true, message: '请输入医院名称', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
// --- Methods ---
|
||||
@ -143,7 +189,7 @@ const fetchData = async () => {
|
||||
const res = await getHospitals({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
keyword: searchForm.keyword || undefined
|
||||
keyword: searchForm.keyword || undefined,
|
||||
});
|
||||
|
||||
let hospitalAdminNameMap = {};
|
||||
@ -166,7 +212,8 @@ const fetchData = async () => {
|
||||
|
||||
tableData.value = (res.list || []).map((hospital) => ({
|
||||
...hospital,
|
||||
adminDisplay: (hospitalAdminNameMap[hospital.id] || []).join('、') || '未设置',
|
||||
adminDisplay:
|
||||
(hospitalAdminNameMap[hospital.id] || []).join('、') || '未设置',
|
||||
}));
|
||||
total.value = res.total || 0;
|
||||
} catch (error) {
|
||||
@ -233,7 +280,9 @@ const handleSubmit = async () => {
|
||||
try {
|
||||
let targetHospitalId = null;
|
||||
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;
|
||||
ElMessage.success('更新成功');
|
||||
} else {
|
||||
@ -272,15 +321,12 @@ const handleSubmit = async () => {
|
||||
};
|
||||
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除医院 "${row.name}" 吗?`,
|
||||
'警告',
|
||||
{
|
||||
ElMessageBox.confirm(`确定要删除医院 "${row.name}" 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(async () => {
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await deleteHospital(row.id);
|
||||
ElMessage.success('删除成功');
|
||||
@ -288,13 +334,14 @@ const handleDelete = (row) => {
|
||||
} catch (error) {
|
||||
console.error('Delete failed', error);
|
||||
}
|
||||
}).catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const goToDepartments = (row) => {
|
||||
router.push({
|
||||
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">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="header-title"><el-icon><Connection /></el-icon> 组织架构全景图</span>
|
||||
<el-button type="primary" @click="fetchTreeData" icon="Refresh" round size="small">刷新</el-button>
|
||||
<span class="header-title"
|
||||
><el-icon><Connection /></el-icon> 组织架构全景图</span
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="fetchTreeData"
|
||||
icon="Refresh"
|
||||
round
|
||||
size="small"
|
||||
>刷新</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -26,20 +35,37 @@
|
||||
<div class="custom-tree-node" :class="`node-${data.type}`">
|
||||
<div class="node-main">
|
||||
<div class="node-icon-wrapper">
|
||||
<el-icon v-if="data.type === 'hospital'"><OfficeBuilding /></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>
|
||||
<el-icon v-if="data.type === 'hospital'"
|
||||
><OfficeBuilding
|
||||
/></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 class="node-text">
|
||||
<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 || '未设置' }}
|
||||
</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 || '未设置' }}
|
||||
</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 || '未设置' }}
|
||||
</span>
|
||||
</div>
|
||||
@ -57,7 +83,10 @@
|
||||
|
||||
<div class="node-actions" v-if="data.type !== 'user'">
|
||||
<el-button
|
||||
v-if="canAssignOwner && (data.type === 'department' || data.type === 'group')"
|
||||
v-if="
|
||||
canAssignOwner &&
|
||||
(data.type === 'department' || data.type === 'group')
|
||||
"
|
||||
type="warning"
|
||||
link
|
||||
size="small"
|
||||
@ -65,17 +94,34 @@
|
||||
>
|
||||
{{ data.type === 'department' ? '设主任' : '设组长' }}
|
||||
</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 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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
<el-empty v-if="!loading && treeData.length === 0" description="暂无组织架构数据" />
|
||||
<el-empty
|
||||
v-if="!loading && treeData.length === 0"
|
||||
description="暂无组织架构数据"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
@ -89,7 +135,10 @@
|
||||
<el-icon><Menu /></el-icon>
|
||||
{{ activePanelTitle }}
|
||||
</span>
|
||||
<div v-if="activeNode && activeNode.type !== 'user'" class="header-actions">
|
||||
<div
|
||||
v-if="activeNode && activeNode.type !== 'user'"
|
||||
class="header-actions"
|
||||
>
|
||||
<el-button
|
||||
v-if="canCreateDepartment(activeNode)"
|
||||
type="primary"
|
||||
@ -118,18 +167,27 @@
|
||||
新增人员
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canAssignOwner && (activeNode.type === 'department' || activeNode.type === 'group')"
|
||||
v-if="
|
||||
canAssignOwner &&
|
||||
(activeNode.type === 'department' ||
|
||||
activeNode.type === 'group')
|
||||
"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openSetOwnerDialog(activeNode)"
|
||||
>
|
||||
{{ activeNode.type === 'department' ? '设置主任' : '设置组长' }}
|
||||
{{
|
||||
activeNode.type === 'department' ? '设置主任' : '设置组长'
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</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
|
||||
v-if="activeNodeMeta"
|
||||
:title="activeNodeMeta"
|
||||
@ -137,31 +195,55 @@
|
||||
:closable="false"
|
||||
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 label="类型" width="100" align="center">
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column label="负责人" width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.type === 'hospital'">医院管理员:{{ row.adminDisplay || '未设置' }}</span>
|
||||
<span v-else-if="row.type === 'department'">主任:{{ row.directorDisplay || '未设置' }}</span>
|
||||
<span v-else-if="row.type === 'group'">组长:{{ row.leaderDisplay || '未设置' }}</span>
|
||||
<span v-if="row.type === 'hospital'"
|
||||
>医院管理员:{{ row.adminDisplay || '未设置' }}</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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="角色" width="120" align="center">
|
||||
<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>
|
||||
</template>
|
||||
</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 }">
|
||||
<el-button
|
||||
v-if="canAssignOwner && (row.type === 'department' || row.type === 'group')"
|
||||
v-if="
|
||||
canAssignOwner &&
|
||||
(row.type === 'department' || row.type === 'group')
|
||||
"
|
||||
type="warning"
|
||||
link
|
||||
size="small"
|
||||
@ -169,8 +251,22 @@
|
||||
>
|
||||
{{ row.type === 'department' ? '设主任' : '设组长' }}
|
||||
</el-button>
|
||||
<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>
|
||||
<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>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -178,12 +274,24 @@
|
||||
|
||||
<div v-else-if="selectedUserDetail" class="user-detail-panel">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="姓名">{{ selectedUserDetail.name || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ getRoleName(selectedUserDetail.role) }}</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-item label="姓名">{{
|
||||
selectedUserDetail.name || '-'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{
|
||||
getRoleName(selectedUserDetail.role)
|
||||
}}</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-alert
|
||||
title="如需修改人员角色或组织归属,请前往“用户管理”页面操作。"
|
||||
@ -198,16 +306,37 @@
|
||||
</el-row>
|
||||
|
||||
<!-- Dialog for Create / Edit -->
|
||||
<el-dialog :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-dialog
|
||||
: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-input v-model="form.name" :placeholder="`请输入${formLabel}`" clearable />
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:placeholder="`请输入${formLabel}`"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -224,7 +353,7 @@
|
||||
v-model="selectedOwnerUserId"
|
||||
filterable
|
||||
placeholder="请选择人员"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in ownerCandidates"
|
||||
@ -243,7 +372,11 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
</div>
|
||||
@ -256,10 +389,33 @@
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
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 { 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 userStore = useUserStore();
|
||||
@ -288,7 +444,7 @@ const roleMap = {
|
||||
DIRECTOR: '科室主任',
|
||||
LEADER: '小组组长',
|
||||
DOCTOR: '医生',
|
||||
ENGINEER: '工程师'
|
||||
ENGINEER: '工程师',
|
||||
};
|
||||
|
||||
const getRoleName = (role) => roleMap[role] || role;
|
||||
@ -302,12 +458,22 @@ const getRoleTagType = (role) => {
|
||||
};
|
||||
|
||||
const getTypeName = (type) => {
|
||||
const map = { hospital: '医院', department: '科室', group: '小组', user: '人员' };
|
||||
const map = {
|
||||
hospital: '医院',
|
||||
department: '科室',
|
||||
group: '小组',
|
||||
user: '人员',
|
||||
};
|
||||
return map[type] || 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';
|
||||
};
|
||||
|
||||
@ -326,16 +492,16 @@ const canCreateDepartment = (node) =>
|
||||
|
||||
const canCreateGroup = (node) =>
|
||||
Boolean(
|
||||
node
|
||||
&& node.type === 'department'
|
||||
&& (isOrgAdmin.value || isDirector.value),
|
||||
node &&
|
||||
node.type === 'department' &&
|
||||
(isOrgAdmin.value || isDirector.value),
|
||||
);
|
||||
|
||||
const canAddUser = (node) =>
|
||||
Boolean(
|
||||
node
|
||||
&& (node.type === 'department' || node.type === 'group')
|
||||
&& isOrgAdmin.value,
|
||||
node &&
|
||||
(node.type === 'department' || node.type === 'group') &&
|
||||
isOrgAdmin.value,
|
||||
);
|
||||
|
||||
const canEditNode = (node) => {
|
||||
@ -382,9 +548,9 @@ const activePanelTitle = computed(() => {
|
||||
|
||||
const activeNodeChildren = computed(() => {
|
||||
if (
|
||||
!activeNode.value
|
||||
|| activeNode.value.type === 'user'
|
||||
|| !Array.isArray(activeNode.value.children)
|
||||
!activeNode.value ||
|
||||
activeNode.value.type === 'user' ||
|
||||
!Array.isArray(activeNode.value.children)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
@ -410,7 +576,9 @@ const selectedUserDetail = computed(() => {
|
||||
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 hospitalId = userData.hospitalId || null;
|
||||
const departmentId = userData.departmentId || null;
|
||||
@ -466,7 +634,7 @@ const fetchTreeData = async () => {
|
||||
const [deptRes, groupRes, userRes] = await Promise.all([
|
||||
getDepartments({ pageSize: 100 }),
|
||||
getGroups({ pageSize: 100 }),
|
||||
getUsers()
|
||||
getUsers(),
|
||||
]);
|
||||
|
||||
const departments = deptRes.list || [];
|
||||
@ -507,51 +675,86 @@ const fetchTreeData = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
const tree = hospitals.map(h => {
|
||||
const hDepts = departments.filter(d => d.hospitalId === h.id);
|
||||
const tree = hospitals.map((h) => {
|
||||
const hDepts = departments.filter((d) => d.hospitalId === h.id);
|
||||
const adminDisplay =
|
||||
(hospitalAdminNameMap[h.id] || []).join('、') || '未设置';
|
||||
|
||||
const deptNodes = hDepts.map(d => {
|
||||
const dGroups = groups.filter(g => g.departmentId === d.id);
|
||||
const deptNodes = hDepts.map((d) => {
|
||||
const dGroups = groups.filter((g) => g.departmentId === d.id);
|
||||
const directorDisplay =
|
||||
(directorNameMap[d.id] || []).join('、') || '未设置';
|
||||
|
||||
const groupNodes = dGroups.map(g => {
|
||||
const gUsers = users.filter(u => u.groupId === g.id);
|
||||
const groupNodes = dGroups.map((g) => {
|
||||
const gUsers = users.filter((u) => u.groupId === g.id);
|
||||
const leaderDisplay =
|
||||
(leaderNameMap[g.id] || []).join('、') || '未设置';
|
||||
const userNodes = gUsers.map(u => ({
|
||||
key: `u_${u.id}`, id: u.id, name: u.name, type: 'user', role: u.role,
|
||||
hospitalId: h.id, departmentId: d.id, groupId: g.id
|
||||
const userNodes = gUsers.map((u) => ({
|
||||
key: `u_${u.id}`,
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
type: 'user',
|
||||
role: u.role,
|
||||
hospitalId: h.id,
|
||||
departmentId: d.id,
|
||||
groupId: g.id,
|
||||
}));
|
||||
|
||||
return {
|
||||
key: `g_${g.id}`, id: g.id, name: g.name, type: 'group',
|
||||
departmentId: d.id, hospitalId: h.id, leaderDisplay, children: userNodes
|
||||
key: `g_${g.id}`,
|
||||
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 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
|
||||
const dUsers = users.filter(
|
||||
(u) => u.departmentId === d.id && !u.groupId,
|
||||
);
|
||||
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 {
|
||||
key: `d_${d.id}`, id: d.id, name: d.name, type: 'department',
|
||||
hospitalId: h.id, directorDisplay, children: [...groupNodes, ...dUserNodes]
|
||||
key: `d_${d.id}`,
|
||||
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 hUserNodes = hUsers.map(u => ({
|
||||
key: `u_${u.id}`, id: u.id, name: u.name, type: 'user', role: u.role,
|
||||
hospitalId: h.id
|
||||
const hUsers = users.filter(
|
||||
(u) => u.hospitalId === h.id && !u.departmentId,
|
||||
);
|
||||
const hUserNodes = hUsers.map((u) => ({
|
||||
key: `u_${u.id}`,
|
||||
id: u.id,
|
||||
name: u.name,
|
||||
type: 'user',
|
||||
role: u.role,
|
||||
hospitalId: h.id,
|
||||
}));
|
||||
|
||||
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;
|
||||
|
||||
if (node.type === 'department') {
|
||||
ownerCandidates.value = allUsers.value.filter((user) =>
|
||||
user.hospitalId === node.hospitalId
|
||||
&& user.departmentId === node.id
|
||||
&& user.role === 'DIRECTOR',
|
||||
ownerCandidates.value = allUsers.value.filter(
|
||||
(user) =>
|
||||
user.hospitalId === node.hospitalId &&
|
||||
user.departmentId === node.id &&
|
||||
user.role === 'DIRECTOR',
|
||||
);
|
||||
} else {
|
||||
ownerCandidates.value = allUsers.value.filter((user) =>
|
||||
user.hospitalId === node.hospitalId
|
||||
&& user.departmentId === node.departmentId
|
||||
&& user.groupId === node.id
|
||||
&& user.role === 'LEADER',
|
||||
ownerCandidates.value = allUsers.value.filter(
|
||||
(user) =>
|
||||
user.hospitalId === node.hospitalId &&
|
||||
user.departmentId === node.departmentId &&
|
||||
user.groupId === node.id &&
|
||||
user.role === 'LEADER',
|
||||
);
|
||||
}
|
||||
|
||||
@ -600,7 +805,9 @@ const openSetOwnerDialog = (node) => {
|
||||
}
|
||||
|
||||
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;
|
||||
ownerDialogVisible.value = true;
|
||||
@ -651,8 +858,9 @@ const goToAddUser = (nodeData) => {
|
||||
query: {
|
||||
action: 'create',
|
||||
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 form = reactive({ name: '' });
|
||||
const rules = { name: [{ required: true, message: '请输入名称', trigger: 'blur' }] };
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
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}`;
|
||||
});
|
||||
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 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 openCreateDialog = (type, pId) => {
|
||||
dialogType.value = type;
|
||||
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 () => {
|
||||
if (!formRef.value) return;
|
||||
@ -685,30 +921,58 @@ const handleSubmit = async () => {
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
if (dialogMode.value === 'create') {
|
||||
if (dialogType.value === 'department') await createDepartment({ name: form.name, hospitalId: parentId.value });
|
||||
else if (dialogType.value === 'group') await createGroup({ name: form.name, departmentId: parentId.value });
|
||||
if (dialogType.value === 'department')
|
||||
await createDepartment({
|
||||
name: form.name,
|
||||
hospitalId: parentId.value,
|
||||
});
|
||||
else if (dialogType.value === 'group')
|
||||
await createGroup({
|
||||
name: form.name,
|
||||
departmentId: parentId.value,
|
||||
});
|
||||
ElMessage.success('创建成功');
|
||||
} else {
|
||||
if (dialogType.value === 'hospital') await updateHospital(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 });
|
||||
if (dialogType.value === 'hospital')
|
||||
await updateHospital(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('更新成功');
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
fetchTreeData();
|
||||
} catch (error) { console.error(error); } finally { submitLoading.value = false; }
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (data) => {
|
||||
const typeName = data.type === 'hospital' ? '医院' : (data.type === 'department' ? '科室' : '小组');
|
||||
ElMessageBox.confirm(`确定要删除${typeName} "${data.name}" 吗?`, '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
||||
const typeName =
|
||||
data.type === 'hospital'
|
||||
? '医院'
|
||||
: data.type === 'department'
|
||||
? '科室'
|
||||
: '小组';
|
||||
ElMessageBox.confirm(`确定要删除${typeName} "${data.name}" 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
if (data.type === 'hospital') await deleteHospital(data.id);
|
||||
@ -719,11 +983,16 @@ const handleDelete = (data) => {
|
||||
activeNode.value = null; // Clear active node if deleted
|
||||
}
|
||||
fetchTreeData();
|
||||
} catch (error) { console.error(error); }
|
||||
}).catch(() => {});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
onMounted(() => { fetchTreeData(); });
|
||||
onMounted(() => {
|
||||
fetchTreeData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -731,7 +1000,8 @@ onMounted(() => { fetchTreeData(); });
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tree-card, .detail-card {
|
||||
.tree-card,
|
||||
.detail-card {
|
||||
border-radius: 8px;
|
||||
height: calc(100vh - 120px);
|
||||
display: flex;
|
||||
@ -846,10 +1116,19 @@ onMounted(() => { fetchTreeData(); });
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.node-hospital .node-icon-wrapper { color: #409EFF; }
|
||||
.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-hospital .node-icon-wrapper {
|
||||
color: #409eff;
|
||||
}
|
||||
.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 {
|
||||
font-size: 14px;
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
v-model="searchForm.hospitalId"
|
||||
placeholder="系统管理员必须选择医院"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
style="width: 240px"
|
||||
@change="handleSearchHospitalChange"
|
||||
>
|
||||
<el-option
|
||||
@ -30,9 +30,13 @@
|
||||
/>
|
||||
</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 type="success" @click="openCreateDialog" icon="Plus">新增患者</el-button>
|
||||
<el-button type="success" @click="openCreateDialog" icon="Plus"
|
||||
>新增患者</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@ -42,10 +46,16 @@
|
||||
type="warning"
|
||||
:closable="false"
|
||||
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="name" label="姓名" min-width="120" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="140" />
|
||||
@ -62,7 +72,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||
<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 size="small" @click="openEditDialog(row)">
|
||||
@ -114,7 +128,7 @@
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择归属医生(按科室/小组)"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
:disabled="userStore.role === 'DOCTOR'"
|
||||
/>
|
||||
</el-form-item>
|
||||
@ -122,7 +136,11 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
</div>
|
||||
@ -131,13 +149,27 @@
|
||||
|
||||
<el-dialog title="调压记录详情" v-model="recordDialogVisible" width="860px">
|
||||
<el-descriptions :column="4" border class="mb-16">
|
||||
<el-descriptions-item label="患者">{{ currentPatientName || '-' }}</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-item label="患者">{{
|
||||
currentPatientName || '-'
|
||||
}}</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-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">
|
||||
<template #default="{ row }">
|
||||
{{ new Date(row.occurredAt).toLocaleString() }}
|
||||
@ -155,7 +187,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="压力变更" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ row.taskItem?.oldPressure ?? '-' }} -> {{ row.taskItem?.targetPressure ?? '-' }}
|
||||
{{ row.taskItem?.oldPressure ?? '-' }} ->
|
||||
{{ row.taskItem?.targetPressure ?? '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="医院" min-width="140">
|
||||
@ -260,11 +293,15 @@ const doctorTreeProps = {
|
||||
};
|
||||
|
||||
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(() => {
|
||||
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(() => {
|
||||
@ -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 keyword = searchForm.keyword.trim();
|
||||
|
||||
const filtered = allPatients.value.filter((patient) => {
|
||||
const hitKeyword = !keyword
|
||||
|| patient.name?.includes(keyword)
|
||||
|| patient.phone?.includes(keyword);
|
||||
const hitKeyword =
|
||||
!keyword ||
|
||||
patient.name?.includes(keyword) ||
|
||||
patient.phone?.includes(keyword);
|
||||
|
||||
return hitKeyword;
|
||||
});
|
||||
@ -481,15 +521,11 @@ const handleSubmit = async () => {
|
||||
};
|
||||
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除患者 "${row.name}" 吗?`,
|
||||
'警告',
|
||||
{
|
||||
ElMessageBox.confirm(`确定要删除患者 "${row.name}" 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
},
|
||||
)
|
||||
})
|
||||
.then(async () => {
|
||||
await deletePatient(row.id);
|
||||
ElMessage.success('删除成功');
|
||||
|
||||
@ -36,7 +36,13 @@
|
||||
</el-form>
|
||||
</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="name" label="姓名" min-width="120" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="150" />
|
||||
@ -65,7 +71,9 @@
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.role === 'ENGINEER' && userStore.role === 'SYSTEM_ADMIN'"
|
||||
v-if="
|
||||
row.role === 'ENGINEER' && userStore.role === 'SYSTEM_ADMIN'
|
||||
"
|
||||
size="small"
|
||||
type="warning"
|
||||
@click="openAssignDialog(row)"
|
||||
@ -123,7 +131,11 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<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="HOSPITAL_ADMIN" />
|
||||
<el-option label="科室主任" value="DIRECTOR" />
|
||||
@ -137,7 +149,7 @@
|
||||
<el-select
|
||||
v-model="form.hospitalId"
|
||||
placeholder="请选择医院"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="hospital in hospitals"
|
||||
@ -148,11 +160,15 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="所属科室" prop="departmentId" v-if="needDepartment">
|
||||
<el-form-item
|
||||
label="所属科室"
|
||||
prop="departmentId"
|
||||
v-if="needDepartment"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.departmentId"
|
||||
placeholder="请选择科室"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="department in formDepartments"
|
||||
@ -167,7 +183,7 @@
|
||||
<el-select
|
||||
v-model="form.groupId"
|
||||
placeholder="请选择小组"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="group in formGroups"
|
||||
@ -181,7 +197,11 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
</div>
|
||||
@ -201,7 +221,7 @@
|
||||
<el-select
|
||||
v-model="assignHospitalId"
|
||||
placeholder="请选择医院"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="hospital in hospitals"
|
||||
@ -215,7 +235,11 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
</div>
|
||||
@ -286,9 +310,7 @@ const needHospital = computed(() => form.role && form.role !== 'SYSTEM_ADMIN');
|
||||
const needDepartment = computed(() =>
|
||||
['DIRECTOR', 'LEADER', 'DOCTOR'].includes(form.role),
|
||||
);
|
||||
const needGroup = computed(() =>
|
||||
['LEADER', 'DOCTOR'].includes(form.role),
|
||||
);
|
||||
const needGroup = computed(() => ['LEADER', 'DOCTOR'].includes(form.role));
|
||||
|
||||
const rules = computed(() => ({
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
@ -332,14 +354,14 @@ const getRoleTagType = (role) => {
|
||||
return 'info';
|
||||
};
|
||||
|
||||
const hospitalMap = computed(() =>
|
||||
new Map(hospitals.value.map((item) => [item.id, item.name])),
|
||||
const hospitalMap = computed(
|
||||
() => new Map(hospitals.value.map((item) => [item.id, item.name])),
|
||||
);
|
||||
const departmentMap = computed(() =>
|
||||
new Map(departments.value.map((item) => [item.id, item.name])),
|
||||
const departmentMap = computed(
|
||||
() => new Map(departments.value.map((item) => [item.id, item.name])),
|
||||
);
|
||||
const groupMap = computed(() =>
|
||||
new Map(groups.value.map((item) => [item.id, item.name])),
|
||||
const groupMap = computed(
|
||||
() => new Map(groups.value.map((item) => [item.id, item.name])),
|
||||
);
|
||||
|
||||
const resolveHospitalName = (id) => {
|
||||
@ -570,15 +592,11 @@ const handleSubmit = async () => {
|
||||
};
|
||||
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除用户 "${row.name}" 吗?`,
|
||||
'警告',
|
||||
{
|
||||
ElMessageBox.confirm(`确定要删除用户 "${row.name}" 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
},
|
||||
)
|
||||
})
|
||||
.then(async () => {
|
||||
await deleteUser(row.id);
|
||||
ElMessage.success('删除成功');
|
||||
@ -601,7 +619,10 @@ const handleAssignSubmit = async () => {
|
||||
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
await assignEngineerHospital(currentAssignUser.value.id, assignHospitalId.value);
|
||||
await assignEngineerHospital(
|
||||
currentAssignUser.value.id,
|
||||
assignHospitalId.value,
|
||||
);
|
||||
ElMessage.success('分配成功');
|
||||
assignDialogVisible.value = false;
|
||||
await fetchCommonData();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user