创新券项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
10 KiB

<template>
<a-card :bordered="false" style="height: 100%">
<div class="j-table-operator" style="width: 100%">
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddDepart">新增</a-button>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddChildDepart()">添加下级</a-button>
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls">
<a-button type="primary" preIcon="ant-design:import-outlined">导入</a-button>
</a-upload>
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
6 months ago
<!--<a-button type="primary" preIcon="ant-design:sync-outlined">同步企微?</a-button>
<a-button type="primary" preIcon="ant-design:sync-outlined">同步钉钉?</a-button>-->
<template v-if="checkedKeys.length > 0">
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="onDeleteBatch">
<icon icon="ant-design:delete-outlined" />
<span>删除</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作 </span>
<icon icon="akar-icons:chevron-down" />
</a-button>
</a-dropdown>
</template>
</div>
<a-alert type="info" show-icon class="alert" style="margin-bottom: 8px">
<template #message>
<template v-if="checkedKeys.length > 0">
<span>已选中 {{ checkedKeys.length }} 条记录</span>
<a-divider type="vertical" />
<a @click="checkedKeys = []">清空</a>
</template>
<template v-else>
<span>未选中任何数据</span>
</template>
</template>
</a-alert>
<a-spin :spinning="loading">
<a-input-search placeholder="按部门名称搜索…" style="margin-bottom: 10px" @search="onSearch" />
<!--组织机构树-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
checkable
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="checkStrictly"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
@check="onCheck"
@select="onSelect"
>
<template #title="{ key: treeKey, title, dataRef }">
<a-dropdown :trigger="['contextmenu']">
<Popconfirm
:open="visibleTreeKey === treeKey"
title="确定要删除吗?"
ok-text="确定"
cancel-text="取消"
placement="rightTop"
@confirm="onDelete(dataRef)"
@openChange="onVisibleChange"
>
<span>{{ title }}</span>
</Popconfirm>
<template #overlay>
<a-menu @click="">
<a-menu-item key="1" @click="onAddChildDepart(dataRef)">添加子级</a-menu-item>
<a-menu-item key="2" @click="visibleTreeKey = treeKey">
<span style="color: red">删除</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
<DepartFormModal :rootTreeData="treeData" @register="registerModal" @success="loadRootTreeData" />
</a-card>
</template>
<script lang="ts" setup>
import { inject, nextTick, ref, unref } from 'vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useMethods } from '/@/hooks/system/useMethods';
import { Api, deleteBatchDepart, queryDepartTreeSync } from '../depart.api';
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue';
import { Popconfirm } from 'ant-design-vue';
const prefixCls = inject('prefixCls');
const emit = defineEmits(['select', 'rootTreeData']);
const { createMessage } = useMessage();
const { handleImportXls, handleExportXls } = useMethods();
const loading = ref<boolean>(false);
// 部门树列表数据
const treeData = ref<any[]>([]);
// 当前选中的项
const checkedKeys = ref<any[]>([]);
// 当前展开的项
const expandedKeys = ref<any[]>([]);
// 当前选中的项
const selectedKeys = ref<any[]>([]);
// 树组件重新加载
const treeReloading = ref<boolean>(false);
// 树父子是否关联
const checkStrictly = ref<boolean>(true);
// 当前选中的部门
const currentDepart = ref<any>(null);
// 控制确认删除提示框是否显示
const visibleTreeKey = ref<any>(null);
// 搜索关键字
const searchKeyword = ref('');
// 注册 modal
const [registerModal, { openModal }] = useModal();
// 加载顶级部门信息
async function loadRootTreeData() {
try {
loading.value = true;
treeData.value = [];
const result = await queryDepartTreeSync();
if (Array.isArray(result)) {
treeData.value = result;
}
if (expandedKeys.value.length === 0) {
autoExpandParentNode();
} else {
if (selectedKeys.value.length === 0) {
let item = treeData.value[0];
if (item) {
// 默认选中第一个
setSelectedKey(item.id, item);
}
} else {
emit('select', currentDepart.value);
}
}
emit('rootTreeData', treeData.value);
} finally {
loading.value = false;
}
}
loadRootTreeData();
// 加载子级部门信息
async function loadChildrenTreeData(treeNode) {
try {
const result = await queryDepartTreeSync({
pid: treeNode.dataRef.id,
});
if (result.length == 0) {
treeNode.dataRef.isLeaf = true;
} else {
treeNode.dataRef.children = result;
if (expandedKeys.value.length > 0) {
// 判断获取的子级是否有当前展开的项
let subKeys: any[] = [];
for (let key of expandedKeys.value) {
if (result.findIndex((item) => item.id === key) !== -1) {
subKeys.push(key);
}
}
if (subKeys.length > 0) {
expandedKeys.value = [...expandedKeys.value];
}
}
}
treeData.value = [...treeData.value];
emit('rootTreeData', treeData.value);
} catch (e) {
console.error(e);
}
return Promise.resolve();
}
// 自动展开父节点,只展开一级
function autoExpandParentNode() {
let item = treeData.value[0];
if (item) {
if (!item.isLeaf) {
expandedKeys.value = [item.key];
}
// 默认选中第一个
setSelectedKey(item.id, item);
reloadTree();
} else {
emit('select', null);
}
}
// 重新加载树组件,防止无法默认展开数据
async function reloadTree() {
await nextTick();
treeReloading.value = true;
await nextTick();
treeReloading.value = false;
}
/**
* 设置当前选中的行
*/
function setSelectedKey(key: string, data?: object) {
selectedKeys.value = [key];
if (data) {
currentDepart.value = data;
emit('select', data);
}
}
// 添加一级部门
function onAddDepart() {
openModal(true, { isUpdate: false, isChild: false });
}
// 添加子级部门
function onAddChildDepart(data = currentDepart.value) {
if (data == null) {
createMessage.warning('请先选择一个部门');
return;
}
const record = { parentId: data.id };
openModal(true, { isUpdate: false, isChild: true, record });
}
// 搜索事件
async function onSearch(value: string) {
if (value) {
try {
loading.value = true;
treeData.value = [];
let result = await searchByKeywords({ keyWord: value });
if (Array.isArray(result)) {
treeData.value = result;
}
autoExpandParentNode();
} finally {
loading.value = false;
}
} else {
loadRootTreeData();
}
searchKeyword.value = value;
}
// 树复选框选择事件
function onCheck(e) {
if (Array.isArray(e)) {
checkedKeys.value = e;
} else {
checkedKeys.value = e.checked;
}
}
// 树选择事件
function onSelect(selKeys, event) {
console.log('select: ', selKeys, event);
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
setSelectedKey(selKeys[0], event.selectedNodes[0]);
} else {
// 这样可以防止用户取消选择
setSelectedKey(selectedKeys.value[0]);
}
}
/**
* 根据 ids 删除部门
* @param idListRef array
* @param confirm 是否显示确认提示框
*/
async function doDeleteDepart(idListRef, confirm = true) {
const idList = unref(idListRef);
if (idList.length > 0) {
try {
loading.value = true;
await deleteBatchDepart({ ids: idList.join(',') }, confirm);
await loadRootTreeData();
} finally {
loading.value = false;
}
}
}
// 删除单个部门
async function onDelete(data) {
if (data) {
onVisibleChange(false);
doDeleteDepart([data.id], false);
}
}
// 批量删除部门
async function onDeleteBatch() {
try {
await doDeleteDepart(checkedKeys);
checkedKeys.value = [];
} finally {
}
}
function onVisibleChange(visible) {
if (!visible) {
visibleTreeKey.value = null;
}
}
function onImportXls(d) {
handleImportXls(d, Api.importExcelUrl, () => {
loadRootTreeData();
});
}
function onExportXls() {
//update-begin---author:wangshuai---date:2024-07-05---for:【TV360X-1671】部门管理不支持选中的记录导出---
let params = {}
if(checkedKeys.value && checkedKeys.value.length > 0) {
params['selections'] = checkedKeys.value.join(',')
}
handleExportXls('部门信息', Api.exportXlsUrl,params);
//update-end---author:wangshuai---date:2024-07-05---for:【TV360X-1671】部门管理不支持选中的记录导出---
}
defineExpose({
loadRootTreeData,
});
</script>