Files
cn-rdms-web/src/views/system/user-management-relation/index.vue

464 lines
12 KiB
Vue
Raw Normal View History

<script setup lang="ts">
/**
* 用户带人关系管理 - 主页面
*
* 功能说明
* - 展示用户带人关系的树形结构
* - 支持节点的展开/折叠
* - 支持单选/多选节点
* - 提供新增编辑删除单个/批量功能
* - 支持按管理者用户 ID 和被管理用户 ID 搜索
*
* 树形结构特点
* - 根节点最高领导没有上级
* - 中间节点有上级也有下级
* - 叶子节点基层员工没有下级
*/
import {nextTick, onMounted, reactive, ref} from 'vue';
import type {ElTree} from 'element-plus';
import {ElButton, ElPopconfirm, ElTag} from 'element-plus';
import {useBoolean} from '@sa/hooks';
import {
fetchBatchDeleteUserManagementRelation,
fetchDeleteUserManagementRelation, fetchGetUserListByDeptId,
fetchGetUserManagementRelationQuery,
fetchGetUserManagementRelationTree,
} from '@/service/api';
import RelationOperateDialog from './modules/relation-operate-dialog.vue';
import RelationSearch from './modules/relation-search.vue';
defineOptions({name: 'UserManagementRelation'});
/**
* 组件 userQuery 定义
*
* @param fromUserIndex 是否不是从带人关系 index 页面访问 user 页面访问时为 true
*/
interface userQuery {
fromUserIndex?: boolean;
deptId?: number | null;
}
//从user的index组件访问带人关系fromUserIndex为true否则false; dept=100是灿能电力的id
const {fromUserIndex = false, deptId = 100} = defineProps<userQuery>()
/**
* 初始化搜索参数
*
* @returns 搜索参数对象
*/
function getInitSearchParams(): Api.SystemManage.UserManagementRelationQueryReqVO {
return {
managerUserId: undefined,
subordinateUserId: undefined
};
}
// 搜索参数
const searchParams = reactive(getInitSearchParams());
// 用户列表(供搜索组件和对话框组件共享)
const userList = ref<Api.SystemManage.UserSimple[]>([]);
// 树形组件引用
const relationTreeRef = ref<InstanceType<typeof ElTree>>();
// 已选中的节点 ID 列表
const checkedNodeKeys = ref<number[]>([]);
// 树形数据
const treeData = ref<Api.SystemManage.UserManagementRelationTreeRespVO[]>([]);
// 加载状态
const loading = ref(false);
// 树形配置
const treeProps: any = {
children: 'children',
label: 'userNickname',
isLeaf: (data: Api.SystemManage.UserManagementRelationTreeRespVO) => !data.children || data.children.length === 0
};
/**
* 加载用户列表
*
* 获取用户简单列表供搜索组件和对话框组件共享使用
*/
async function loadUserList() {
const {data, error} = await fetchGetUserListByDeptId(deptId);
if (!error) {
userList.value = data || [];
}
}
/**
* 加载树形数据
*
* 调用后端接口获取完整的用户带人关系树
*/
async function loadTreeData() {
loading.value = true;
try {
// 默认不是来自user的index组件访问且deptId=100查询灿能电力及其以下所有部门的用户的带人关系
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
fromUserIndex: fromUserIndex,
deptId: deptId
};
const {data, error} = await fetchGetUserManagementRelationTree(query);
if (!error) {
treeData.value = data || [];
}
} finally {
loading.value = false;
}
}
/**
* 根据搜索条件查询树形数据
*
* 调用后端接口获取符合条件的用户带人关系树
*
* @param query 查询参数
*/
async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelationQueryReqVO) {
loading.value = true;
try {
const {data, error} = await fetchGetUserManagementRelationQuery(query);
if (!error) {
treeData.value = data || [];
}
} finally {
loading.value = false;
}
}
/**
* 刷新树形数据
*
* 清空选中状态并重新加载数据
*/
async function reloadTreeData() {
checkedNodeKeys.value = [];
await loadTreeData();
await nextTick();
relationTreeRef.value?.setCheckedKeys([]);
}
/**
* 处理搜索
*
* 根据搜索条件查询树形数据
* 如果有搜索条件调用 query 接口否则调用 tree 接口
*/
async function handleSearch() {
checkedNodeKeys.value = [];
// 判断是否有搜索条件
const hasSearchCondition = searchParams.subordinateUserId !== undefined && searchParams.subordinateUserId !== null;
if (hasSearchCondition) {
// 有搜索条件,调用查询接口
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
subordinateUserId: searchParams.subordinateUserId,
fromUserIndex: fromUserIndex,
deptId: deptId
};
await loadTreeDataByQuery(query);
} else {
// 无搜索条件,加载完整树
await loadTreeData();
}
await nextTick();
relationTreeRef.value?.setCheckedKeys([]);
}
/**
* 重置搜索
*
* 清空搜索条件并重新加载数据
*/
function resetSearchParams() {
Object.assign(searchParams, getInitSearchParams());
reloadTreeData();
}
// 对话框相关状态
const {bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal} = useBoolean();
const operateType = ref<UI.TableOperateType>('add');
const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null);
/**
* 打开新增对话框
*
* @param item 当前节点数据用于设置默认管理者为此节点用户
*/
function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
operateType.value = 'add';
// 如果是从某一行的新增按钮触发,则默认管理者为当前节点用户
// 否则默认管理者为当前登录用户(在对话框组件中处理)
editingData.value = item
? {
id: null,
managerUserId: item.userId,
subordinateUserId: null,
effectiveFrom: null,
effectiveUntil: null,
remark: null,
createTime: Date.now()
}
: null;
openOperateModal();
}
/**
* 打开编辑对话框
*
* @param item 要编辑的关系记录
*/
function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
operateType.value = 'edit';
// 构建树节点数据为编辑所需格式
editingData.value = item.id
? {
id: item.id,
managerUserId: item.managerUserId,
subordinateUserId: item.userId,
effectiveFrom: null,
effectiveUntil: null,
remark: null,
createTime: Date.now()
}
: null;
openOperateModal();
}
/**
* 删除单个节点的关系记录
*
* @param item 要删除的关系记录
*/
async function handleDelete(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
const {error} = await fetchDeleteUserManagementRelation(item.id);
if (error) {
return;
}
window.$message?.success('删除成功');
await reloadTreeData();
}
/**
* 批量删除关系记录
*
* 删除所有选中的节点
*/
async function handleBatchDelete() {
if (!checkedNodeKeys.value.length) {
return;
}
const {error} = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
if (error) {
return;
}
window.$message?.success('删除成功');
await reloadTreeData();
}
/**
* 处理树节点勾选变化
*
* @param checkedData 当前选中的节点数据
* @param checkedInfo 包含 checkedKeys halfCheckedKeys 的对象
*/
function handleNodeCheck(checkedData: any, checkedInfo: any) {
checkedNodeKeys.value = checkedInfo.checkedNodes.map((node: any) => node.id);
}
/**
* 处理对话框提交事件
*
* @param relationId 提交后的关系 ID
*/
function handleSubmitted(relationId: number) {
closeOperateModal();
reloadTreeData();
}
/**
* 判断节点是否有子节点
*
* 有子节点的节点不允许删除
*
* @param node 树节点
*/
function hasChildren(node: Api.SystemManage.UserManagementRelationTreeRespVO): boolean {
return Boolean(node.children && node.children.length > 0);
}
/**
* 计算树形数据中所有节点的数量
*
* 递归遍历树形结构统计所有节点总数
*
* @param nodes 树形数据数组
* @returns 节点总数
*/
function countTreeNodes(nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]): number {
let count = 0;
for (const node of nodes) {
count += 1;
if (node.children && node.children.length > 0) {
count += countTreeNodes(node.children);
}
}
return count;
}
onMounted(async () => {
await loadUserList();
await reloadTreeData();
});
</script>
<template>
<div class="flex-col-stretch gap-16px overflow-hidden">
<!-- 搜索区域 -->
<RelationSearch
v-model:model="searchParams"
:user-list="userList"
@reset="resetSearchParams"
@search="handleSearch"
/>
<!-- 树形卡片区域 -->
<ElCard class="flex-1-hidden card-wrapper">
<template #header>
<div class="flex items-center justify-between gap-12px">
<div class="flex items-center gap-10px">
<p>用户带人关系树</p>
<ElTag effect="plain">{{ countTreeNodes(treeData) }}</ElTag>
</div>
<div class="flex items-center gap-10px">
<ElButton plain type="primary" @click="openAdd()">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
</template>
新增
</ElButton>
<!-- <ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete">-->
<!-- <template #reference>-->
<!-- <ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0">-->
<!-- <template #icon>-->
<!-- <icon-ic-round-delete class="text-icon"/>-->
<!-- </template>-->
<!-- 批量删除-->
<!-- </ElButton>-->
<!-- </template>-->
<!-- </ElPopconfirm>-->
<ElButton @click="reloadTreeData">
<template #icon>
<icon-ic-round-refresh class="text-icon"/>
</template>
刷新
</ElButton>
</div>
</div>
</template>
<!-- 树形组件 -->
<div class="flex-1 overflow-auto">
<ElTree
ref="relationTreeRef"
v-loading="loading"
:data="treeData"
:props="treeProps"
node-key="userId"
default-expand-all
:expand-on-click-node="false"
@check="handleNodeCheck"
>
<template #default="{ node, data }">
<div class="flex flex-1 items-center justify-between">
<span class="flex items-center gap-8px">
<span>{{ node.label }}</span>
<!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级{{ data.managerNickname }}</ElTag>-->
</span>
<div class="flex items-center">
<ElButton link type="primary" size="default" @click.stop="openAdd(data)">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
</template>
新增
</ElButton>
<ElButton link type="primary" size="small" @click.stop="openEdit(data)">
<template #icon>
<icon-ic-round-edit class="text-icon"/>
</template>
编辑
</ElButton>
<ElPopconfirm
v-if="!hasChildren(data) && data.id"
title="确认删除当前关系吗?"
@confirm="handleDelete(data)"
>
<template #reference>
<ElButton link type="danger" size="small">
<template #icon>
<icon-ic-round-delete class="text-icon"/>
</template>
删除
</ElButton>
</template>
</ElPopconfirm>
<span v-else-if="hasChildren(data)" style="margin-left: 56px"></span>
</div>
</div>
</template>
</ElTree>
</div>
</ElCard>
<!-- 操作对话框 -->
<RelationOperateDialog
v-model:visible="operateVisible"
:operate-type="operateType"
:row-data="editingData"
:user-list="userList"
@submitted="handleSubmitted"
/>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-card__body) {
height: calc(100% - 56px);
display: flex;
flex-direction: column;
}
:deep(.el-tree-node__content) {
height: auto;
padding: 10px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
&:hover {
background-color: var(--el-fill-color-light);
}
}
:deep(.el-tree-node__label) {
flex: 1;
display: flex;
align-items: center;
}
</style>