157 lines
4.9 KiB
Vue
157 lines
4.9 KiB
Vue
<script lang="ts" setup>
|
|
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
|
import { computed, getCurrentInstance, ref, watch } from 'vue';
|
|
|
|
import Dept from '@iconify-icons/ri/git-branch-line';
|
|
// import Reset from "@iconify-icons/ri/restart-line";
|
|
import More2Fill from '@iconify-icons/ri/more-2-fill';
|
|
import OfficeBuilding from '@iconify-icons/ep/office-building';
|
|
import LocationCompany from '@iconify-icons/ep/add-location';
|
|
import ExpandIcon from './svg/expand.svg?component';
|
|
import UnExpandIcon from './svg/unexpand.svg?component';
|
|
|
|
interface Tree {
|
|
id: number;
|
|
name: string;
|
|
highlight?: boolean;
|
|
children?: Tree[];
|
|
}
|
|
|
|
defineProps({
|
|
treeLoading: Boolean,
|
|
treeData: Array,
|
|
});
|
|
|
|
const emit = defineEmits(['tree-select']);
|
|
|
|
const treeRef = ref();
|
|
const isExpand = ref(true);
|
|
const searchValue = ref('');
|
|
const highlightMap = ref({});
|
|
const { proxy } = getCurrentInstance();
|
|
const defaultProps = {
|
|
children: 'children',
|
|
label: 'name',
|
|
};
|
|
const buttonClass = computed(() => {
|
|
return ['!h-[20px]', '!text-sm', 'reset-margin', '!text-[var(--el-text-color-regular)]', 'dark:!text-white', 'dark:hover:!text-primary'];
|
|
});
|
|
|
|
const filterNode = (value: string, data: Tree) => {
|
|
if (!value) return true;
|
|
return data.name.includes(value);
|
|
};
|
|
|
|
function nodeClick(value) {
|
|
const nodeId = value.$treeNodeId;
|
|
highlightMap.value[nodeId] = highlightMap.value[nodeId]?.highlight
|
|
? Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
|
|
highlight: false,
|
|
})
|
|
: Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
|
|
highlight: true,
|
|
});
|
|
Object.values(highlightMap.value).forEach((v: Tree) => {
|
|
if (v.id !== nodeId) {
|
|
v.highlight = false;
|
|
}
|
|
});
|
|
emit('tree-select', highlightMap.value[nodeId]?.highlight ? Object.assign({ ...value, selected: true }) : Object.assign({ ...value, selected: false }));
|
|
}
|
|
|
|
function toggleRowExpansionAll(status) {
|
|
isExpand.value = status;
|
|
const nodes = (proxy.$refs['treeRef'] as any).store._getAllNodes();
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
nodes[i].expanded = status;
|
|
}
|
|
}
|
|
|
|
/** 重置部门树状态(选中状态、搜索框值、树初始化) */
|
|
function onTreeReset() {
|
|
highlightMap.value = {};
|
|
searchValue.value = '';
|
|
toggleRowExpansionAll(true);
|
|
}
|
|
|
|
watch(searchValue, val => {
|
|
treeRef.value!.filter(val);
|
|
});
|
|
|
|
defineExpose({ onTreeReset });
|
|
</script>
|
|
|
|
<template>
|
|
<div v-loading="treeLoading" :style="{ minHeight: `calc(100vh - 141px)` }" class="h-full bg-bg_color overflow-hidden relative">
|
|
<div class="flex items-center h-[34px]">
|
|
<el-input v-model="searchValue" class="ml-2" clearable placeholder="请输入部门名称" size="small">
|
|
<template #suffix>
|
|
<el-icon class="el-input__icon">
|
|
<IconifyIconOffline v-show="searchValue.length === 0" icon="ri:search-line" />
|
|
</el-icon>
|
|
</template>
|
|
</el-input>
|
|
<el-dropdown :hide-on-click="false">
|
|
<IconifyIconOffline :icon="More2Fill" class="w-[28px] cursor-pointer" width="18px" />
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item>
|
|
<el-button :class="buttonClass" :icon="useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)" link type="primary" @click="toggleRowExpansionAll(isExpand ? false : true)">
|
|
{{ isExpand ? '折叠全部' : '展开全部' }}
|
|
</el-button>
|
|
</el-dropdown-item>
|
|
<!-- <el-dropdown-item>
|
|
<el-button
|
|
:class="buttonClass"
|
|
link
|
|
type="primary"
|
|
:icon="useRenderIcon(Reset)"
|
|
@click="onTreeReset"
|
|
>
|
|
重置状态
|
|
</el-button>
|
|
</el-dropdown-item> -->
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</div>
|
|
<el-divider />
|
|
<el-scrollbar height="calc(90vh - 88px)">
|
|
<el-tree ref="treeRef" :data="treeData" :expand-on-click-node="false" :filter-node-method="filterNode" :props="defaultProps" default-expand-all node-key="id" size="small" @node-click="nodeClick">
|
|
<template #default="{ node, data }">
|
|
<div
|
|
:class="[
|
|
'rounded',
|
|
'flex',
|
|
'items-center',
|
|
'select-none',
|
|
'hover:text-primary',
|
|
searchValue.trim().length > 0 && node.label.includes(searchValue) && 'text-red-500',
|
|
highlightMap[node.id]?.highlight ? 'dark:text-primary' : '',
|
|
]"
|
|
:style="{
|
|
color: highlightMap[node.id]?.highlight ? 'var(--el-color-primary)' : '',
|
|
background: highlightMap[node.id]?.highlight ? 'var(--el-color-primary-light-7)' : 'transparent',
|
|
}"
|
|
>
|
|
<IconifyIconOffline :icon="data.type === 1 ? OfficeBuilding : data.type === 2 ? LocationCompany : Dept" />
|
|
<span :title="node.label" class="!w-[120px] !truncate">
|
|
{{ node.label }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</el-scrollbar>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
:deep(.el-divider) {
|
|
margin: 0;
|
|
}
|
|
|
|
:deep(.el-tree) {
|
|
--el-tree-node-hover-bg-color: transparent;
|
|
}
|
|
</style>
|