308 lines
9.2 KiB
Vue
308 lines
9.2 KiB
Vue
<template>
|
||
<div class="left-tree-container">
|
||
<j-input
|
||
v-model:value="searchValue"
|
||
@change="search"
|
||
placeholder="请输入组织名称"
|
||
class="search-input"
|
||
allowClear
|
||
>
|
||
<template #suffix>
|
||
<AIcon type="SearchOutlined" />
|
||
</template>
|
||
</j-input>
|
||
<div class="add-btn">
|
||
<PermissionButton
|
||
type="primary"
|
||
class="add-btn"
|
||
:hasPermission="`${permission}:add`"
|
||
@click="openDialog()"
|
||
>
|
||
新增
|
||
</PermissionButton>
|
||
</div>
|
||
|
||
<div class="tree">
|
||
<j-spin :spinning='loading'>
|
||
<jTree
|
||
v-if="treeData.length > 0"
|
||
:tree-data="treeData"
|
||
v-model:selected-keys="selectedKeys"
|
||
v-model:expandedKeys="expandedKeys"
|
||
:fieldNames="{ key: 'id' }"
|
||
:showLine="{ showLeafIcon: false }"
|
||
:show-icon="true"
|
||
>
|
||
<template #title="{ name, data }">
|
||
<div class='department-tree-item-content'>
|
||
<span class='title'>
|
||
<j-ellipsis>
|
||
{{ name }}
|
||
</j-ellipsis>
|
||
</span>
|
||
<span class="func-btns" @click="(e) => e.stopPropagation()">
|
||
<PermissionButton
|
||
:hasPermission="`${permission}:update`"
|
||
type="link"
|
||
:tooltip="{
|
||
title: '编辑',
|
||
}"
|
||
@click="openDialog(data)"
|
||
>
|
||
<AIcon type="EditOutlined" />
|
||
</PermissionButton>
|
||
<PermissionButton
|
||
:hasPermission="`${permission}:add`"
|
||
type="link"
|
||
:tooltip="{
|
||
title: '新增子组织',
|
||
}"
|
||
@click="
|
||
openDialog({
|
||
...data,
|
||
id: '',
|
||
parentId: data.id,
|
||
})
|
||
"
|
||
>
|
||
<AIcon type="PlusCircleOutlined" />
|
||
</PermissionButton>
|
||
<PermissionButton
|
||
type="link"
|
||
:hasPermission="`${permission}:delete`"
|
||
:tooltip="{ title: '删除' }"
|
||
:popConfirm="{
|
||
title: `确定要删除吗`,
|
||
onConfirm: () => delDepartment(data.id),
|
||
}"
|
||
>
|
||
<AIcon type="DeleteOutlined" />
|
||
</PermissionButton>
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</jTree>
|
||
<j-empty v-else description="暂无数据" />
|
||
</j-spin>
|
||
</div>
|
||
<!-- 编辑弹窗 -->
|
||
<EditDepartmentDialog
|
||
v-if="dialog.visible"
|
||
v-model:visible="dialog.visible"
|
||
:tree-data="sourceTree"
|
||
:data="dialog.selectItem"
|
||
@refresh="refresh"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||
import { getTreeData_api, delDepartment_api } from '@/api/system/department';
|
||
import { debounce, cloneDeep, omit } from 'lodash-es';
|
||
import { ArrayToTree } from '@/utils/utils';
|
||
import EditDepartmentDialog from './EditDepartmentDialog.vue';
|
||
import { onlyMessage } from '@/utils/comm';
|
||
|
||
const permission = 'system/Department';
|
||
|
||
const save = useRoute().query.save;
|
||
const emits = defineEmits(['change']);
|
||
const searchValue = ref(''); // 搜索内容
|
||
const loading = ref<boolean>(false); // 数据加载状态
|
||
const sourceTree = ref<any[]>([]); // 源数据
|
||
const treeMap = new Map(); // 数据的map版本
|
||
const treeData = ref<any[]>([]); // 展示的数据
|
||
const selectedKeys = ref<string[]>([]); // 当前选中的项
|
||
const expandedKeys = ref<string[] | number[]>([]);
|
||
|
||
function getTree(cb?: Function) {
|
||
loading.value = true;
|
||
const params = {
|
||
paging: false,
|
||
sorts: [{ name: 'sortIndex', order: 'asc' }],
|
||
} as any;
|
||
if (searchValue.value) {
|
||
params.terms = [
|
||
{ column: 'name$LIKE', value: `%${searchValue.value}%` },
|
||
];
|
||
}
|
||
treeMap.clear()
|
||
getTreeData_api(params)
|
||
.then((resp: any) => {
|
||
sourceTree.value = resp.result.sort((a: any, b: any) =>
|
||
a.sortIndex === b.sortIndex
|
||
? b.createTime - a.createTime
|
||
: a.sortIndex - b.sortIndex,
|
||
); // 报存源数据
|
||
selectedKeys.value = [resp.result[0]?.id];
|
||
handleTreeMap(resp.result); // 将树形结构转换为map结构
|
||
treeData.value = resp.result; // 第一次不用进行过滤
|
||
cb && cb();
|
||
})
|
||
.finally(() => {
|
||
loading.value = false;
|
||
});
|
||
}
|
||
const search = debounce(() => {
|
||
const key = searchValue.value;
|
||
const treeArray = new Map();
|
||
if (key) {
|
||
const searchTree: string[] = [];
|
||
treeMap.forEach((item) => {
|
||
if (item.name.includes(key)) {
|
||
searchTree.push(item.parentId);
|
||
treeArray.set(item.id, item);
|
||
}
|
||
});
|
||
expandedKeys.value = []
|
||
dig(searchTree);
|
||
treeData.value = ArrayToTree(cloneDeep([...treeArray.values()]));
|
||
} else {
|
||
treeData.value = ArrayToTree(cloneDeep([...treeMap.values()]));
|
||
expandedKeys.value = [];
|
||
}
|
||
|
||
function dig(_data: any[]): any {
|
||
const pIds: string[] = [];
|
||
if (!_data.length) return;
|
||
_data.forEach((item) => {
|
||
if (treeMap.has(item)) {
|
||
const _item = treeMap.get(item);
|
||
pIds.push(_item.parentId);
|
||
treeArray.set(item, _item);
|
||
expandedKeys.value.push(_item.id)
|
||
if(pIds.length > 0){
|
||
dig(pIds)
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}, 500);
|
||
// 将树形数组转化为map形式,以便筛选时操作
|
||
function handleTreeMap(_data: any[]) {
|
||
if (_data) {
|
||
_data.map((item) => {
|
||
treeMap.set(item.id, omit(cloneDeep(item), ['children']));
|
||
if (item.children) {
|
||
handleTreeMap(item.children);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
// 删除部门
|
||
function delDepartment(id: string) {
|
||
delDepartment_api(id).then(() => {
|
||
onlyMessage('操作成功');
|
||
getTree();
|
||
});
|
||
}
|
||
function refresh(id: string) {
|
||
// @ts-ignore
|
||
if(window?.onTabSaveSuccess){
|
||
window.onTabSaveSuccess(id);
|
||
setTimeout(() => window.close(), 300);
|
||
}
|
||
getTree();
|
||
}
|
||
|
||
// 弹窗
|
||
const dialog = reactive({
|
||
visible: false,
|
||
selectItem: {},
|
||
});
|
||
const openDialog = (row: any = {}) => {
|
||
// 计算默认排序值,为子列表中最大的排序值+1
|
||
let sortIndex = row.sortIndex || 1;
|
||
if (!row.id) {
|
||
let children = [] as any[];
|
||
if (row.parentId) {
|
||
children = row.children;
|
||
} else children = treeData.value;
|
||
const index =
|
||
children?.length > 0
|
||
? children?.map((item) => item.sortIndex)
|
||
: [0];
|
||
sortIndex = Math.max(...index) + 1;
|
||
}
|
||
|
||
dialog.selectItem = { ...row, sortIndex };
|
||
dialog.visible = true;
|
||
};
|
||
|
||
const init = () => {
|
||
getTree(save ? openDialog : undefined);
|
||
watch(selectedKeys, (n) => {
|
||
emits('change', n[0]);
|
||
});
|
||
}
|
||
|
||
init();
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.left-tree-container {
|
||
padding-right: 24px;
|
||
border-right: 1px solid #f0f0f0;
|
||
display: flex;
|
||
height: 100%;
|
||
flex-direction: column;
|
||
|
||
.add-btn {
|
||
margin: 24px 0;
|
||
|
||
:deep(.ant-btn-primary) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
:deep(.ant-tree-treenode) {
|
||
width: 100%;
|
||
.ant-tree-node-content-wrapper {
|
||
flex: 1 1 auto;
|
||
|
||
.ant-tree-title {
|
||
&:hover {
|
||
.func-btns {
|
||
display: block;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
.tree {
|
||
overflow-y: auto;
|
||
overflow-x: auto;
|
||
flex: 1 1 auto;
|
||
.department-tree-item-content {
|
||
display: flex;
|
||
align-items: stretch;
|
||
|
||
.title {
|
||
flex: 1;
|
||
min-width: 80px;
|
||
margin-right: 80px;
|
||
}
|
||
.func-btns {
|
||
display: none;
|
||
font-size: 14px;
|
||
width: 80px;
|
||
margin-left: -80px;
|
||
:deep(.ant-btn-link) {
|
||
padding: 0 4px;
|
||
height: 24px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
width: 100%;
|
||
justify-content: center;
|
||
margin-top: 20px;
|
||
}
|
||
}
|
||
}
|
||
</style>
|