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

464 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>