update 优化 封装TreePanel树组件 简化页面代码
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<el-col
|
||||
:lg="modelCollapsed ? 1 : expandedSpan"
|
||||
:xs="24"
|
||||
class="tree-panel-col"
|
||||
:class="{ 'is-collapsed': modelCollapsed }"
|
||||
>
|
||||
<el-card shadow="hover" class="side-panel tree-panel-shell" :class="{ 'is-collapsed': modelCollapsed }">
|
||||
<template #header>
|
||||
<div
|
||||
class="panel-heading search-panel-toggle tree-panel-header"
|
||||
:class="{ 'is-collapsed': modelCollapsed }"
|
||||
@click.stop="toggleCollapsed"
|
||||
>
|
||||
<div v-show="!modelCollapsed">
|
||||
<h3>{{ title }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!modelCollapsed">
|
||||
<el-input v-model="filterText" :placeholder="placeholder" prefix-icon="Search" clearable />
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
class="mt-2 dept-tree"
|
||||
:node-key="nodeKey"
|
||||
:data="data"
|
||||
:props="(treeProps as any)"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="internalFilterNode"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="onNodeClick"
|
||||
/>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 面板标题 */
|
||||
title: string;
|
||||
/** 搜索输入框占位文字 */
|
||||
placeholder?: string;
|
||||
/** 树数据 */
|
||||
data: any[];
|
||||
/** el-tree node-key */
|
||||
nodeKey?: string;
|
||||
/** el-tree props 配置 */
|
||||
treeProps?: Record<string, string>;
|
||||
/** 展开时占的栅格列数 (lg) */
|
||||
expandedSpan?: number;
|
||||
/** 默认过滤字段名 (用于内置 filterNode) */
|
||||
filterField?: string;
|
||||
/** 自定义过滤方法,传入则覆盖默认 filterField 逻辑 */
|
||||
filterNodeMethod?: (value: string, data: any) => boolean;
|
||||
/** 折叠状态 (v-model:collapsed) */
|
||||
collapsed?: boolean;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请输入名称',
|
||||
nodeKey: 'id',
|
||||
treeProps: () => ({ label: 'label', children: 'children' }),
|
||||
expandedSpan: 5,
|
||||
filterField: 'label',
|
||||
collapsed: false
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:collapsed': [value: boolean];
|
||||
'node-click': [data: any, node: any, component: any];
|
||||
}>();
|
||||
|
||||
const modelCollapsed = computed({
|
||||
get: () => props.collapsed,
|
||||
set: (val: boolean) => emit('update:collapsed', val)
|
||||
});
|
||||
|
||||
const filterText = ref('');
|
||||
const treeRef = ref<ElTreeInstance>();
|
||||
|
||||
const toggleCollapsed = () => {
|
||||
modelCollapsed.value = !modelCollapsed.value;
|
||||
};
|
||||
|
||||
const internalFilterNode = (value: string, data: any) => {
|
||||
if (props.filterNodeMethod) {
|
||||
return props.filterNodeMethod(value, data);
|
||||
}
|
||||
if (!value) return true;
|
||||
return data[props.filterField]?.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
watchEffect(
|
||||
() => {
|
||||
treeRef.value?.filter(filterText.value);
|
||||
},
|
||||
{ flush: 'post' }
|
||||
);
|
||||
|
||||
const onNodeClick = (data: any, node: any, component: any) => {
|
||||
emit('node-click', data, node, component);
|
||||
};
|
||||
|
||||
const contentSpan = computed(() => (modelCollapsed.value ? 23 : 24 - props.expandedSpan));
|
||||
|
||||
const setCurrentKey = (key: any) => treeRef.value?.setCurrentKey(key);
|
||||
|
||||
defineExpose({
|
||||
treeRef,
|
||||
contentSpan,
|
||||
setCurrentKey
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$mobile-breakpoint: 900px;
|
||||
|
||||
.tree-panel-col {
|
||||
min-width: 0;
|
||||
transition:
|
||||
max-width 0.24s ease,
|
||||
flex-basis 0.24s ease;
|
||||
}
|
||||
|
||||
.tree-panel-col.is-collapsed {
|
||||
max-width: 56px;
|
||||
flex: 0 0 56px;
|
||||
}
|
||||
|
||||
.tree-panel-shell,
|
||||
.side-panel {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tree-panel-shell {
|
||||
--tree-panel-max-height: 620px;
|
||||
}
|
||||
|
||||
.tree-panel-shell :deep(.el-card__header) {
|
||||
display: block;
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
|
||||
.tree-panel-shell :deep(.el-card__body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: var(--tree-panel-max-height);
|
||||
min-height: 0;
|
||||
max-height: var(--tree-panel-max-height);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tree-panel-header {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-panel-header::after {
|
||||
display: block;
|
||||
width: 9px;
|
||||
min-width: 9px;
|
||||
height: 9px;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
transform-origin: center;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.tree-panel-header.is-collapsed {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.side-panel.is-collapsed :deep(.el-card__body) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-panel-shell.is-collapsed :deep(.el-card__header) {
|
||||
padding: 12px 0 !important;
|
||||
}
|
||||
|
||||
.tree-panel-shell.is-collapsed .tree-panel-header::after {
|
||||
margin-left: 0;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.dept-tree {
|
||||
padding-top: 6px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 180px;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.dept-tree::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.dept-tree::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: var(--app-text-muted);
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.dept-tree::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
.tree-panel-col {
|
||||
max-width: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
.tree-panel-shell {
|
||||
--tree-panel-max-height: 420px;
|
||||
}
|
||||
|
||||
.side-panel.is-collapsed {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user