+
+
-
+
- {{ getTypeName(row.type) }}
+ {{
+ getTypeName(row.type)
+ }}
- 医院管理员:{{ row.adminDisplay || '未设置' }}
- 主任:{{ row.directorDisplay || '未设置' }}
- 组长:{{ row.leaderDisplay || '未设置' }}
+ 医院管理员:{{ row.adminDisplay || '未设置' }}
+ 主任:{{ row.directorDisplay || '未设置' }}
+ 组长:{{ row.leaderDisplay || '未设置' }}
-
- {{ getRoleName(row.role) }}
+ {{
+ getRoleName(row.role)
+ }}
-
-
+
{{ row.type === 'department' ? '设主任' : '设组长' }}
- 编辑
- 删除
+ 编辑
+ 删除
-
+
- {{ selectedUserDetail.name || '-' }}
- {{ getRoleName(selectedUserDetail.role) }}
- {{ selectedUserDetail.phone || '-' }}
- {{ selectedUserDetail.hospitalName || '-' }}
- {{ selectedUserDetail.departmentName || '-' }}
- {{ selectedUserDetail.groupName || '-' }}
+ {{
+ selectedUserDetail.name || '-'
+ }}
+ {{
+ getRoleName(selectedUserDetail.role)
+ }}
+ {{
+ selectedUserDetail.phone || '-'
+ }}
+ {{
+ selectedUserDetail.hospitalName || '-'
+ }}
+ {{
+ selectedUserDetail.departmentName || '-'
+ }}
+ {{
+ selectedUserDetail.groupName || '-'
+ }}
-
-
+
+
-
+
@@ -224,7 +353,7 @@
v-model="selectedOwnerUserId"
filterable
placeholder="请选择人员"
- style="width: 100%;"
+ style="width: 100%"
>
@@ -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;
@@ -623,14 +830,14 @@ const handleSetOwner = async () => {
const isDepartment = ownerTargetNode.value.type === 'department';
const payload = isDepartment
? {
- role: 'DIRECTOR',
- // 后端约束:非 DOCTOR 不允许“调整”科室/小组归属。
- // 这里仅做角色变更,并清空小组归属,避免触发该约束。
- groupId: null,
- }
+ role: 'DIRECTOR',
+ // 后端约束:非 DOCTOR 不允许“调整”科室/小组归属。
+ // 这里仅做角色变更,并清空小组归属,避免触发该约束。
+ groupId: null,
+ }
: {
- role: 'LEADER',
- };
+ role: 'LEADER',
+ };
ownerSubmitLoading.value = true;
try {
@@ -644,15 +851,16 @@ const handleSetOwner = async () => {
};
const goToAddUser = (nodeData) => {
- // We navigate to the Users list page. In a full implementation, you could
+ // We navigate to the Users list page. In a full implementation, you could
// pass query params to pre-fill a creation form or open a dialog directly.
router.push({
path: '/users',
query: {
action: 'create',
hospitalId: nodeData.hospitalId,
- departmentId: nodeData.type === 'department' ? nodeData.id : nodeData.departmentId,
- }
+ departmentId:
+ nodeData.type === 'department' ? nodeData.id : nodeData.departmentId,
+ },
});
};
@@ -660,23 +868,51 @@ const goToAddUser = (nodeData) => {
const dialogVisible = ref(false);
const submitLoading = ref(false);
const formRef = ref(null);
-const dialogType = ref('');
-const dialogMode = ref('');
+const dialogType = ref('');
+const dialogMode = ref('');
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,45 +921,78 @@ 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' })
- .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
+ 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);
+ 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();
+});