iot-ui-vue/src/views/system/Department/components/LeftTree.vue

308 lines
9.2 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.

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