大屏页面初始化

This commit is contained in:
2026-03-04 18:07:18 +08:00
parent 7668e97467
commit e23a1f3965
2 changed files with 261 additions and 7 deletions

View File

@@ -0,0 +1,172 @@
<template>
<div class="el-card el-card--border" ref="wrapperRef" style="height: 100%; border: none; box-shadow: none;">
<el-input
v-if="showSearch"
v-model="filterText"
:placeholder="searchPlaceholder"
clearable
class="filter-search-input"
@clear="handleClear"
size="default"
/>
<el-divider v-if="showSearch" direction="horizontal" class="filter-tree-divider" />
<el-tree
ref="treeRef"
:data="treeData"
:props="treeProps"
:default-expand-all="defaultExpandAll"
:filter-node-method="filterNode"
:node-key="nodeKey"
:accordion="accordion"
:height="treeHeight"
class="el-tree--highlight-current"
@node-click="handleNodeClick"
v-bind="$attrs"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, defineProps, defineEmits, withDefaults, onMounted, onUnmounted, nextTick, computed } from 'vue'
import type { ElTree } from 'element-plus'
export interface FilterTreeNode {
id: string | number
label: string
children?: FilterTreeNode[]
[key: string]: any
}
interface TreePropsCompat {
children?: string
label?: string
disabled?: string | boolean
isLeaf?: string | boolean
[key: string]: any
}
const emit = defineEmits(['node-click', 'search-change', 'search-clear'])
const props = withDefaults(
defineProps<{
treeData: FilterTreeNode[]
treeProps?: TreePropsCompat
showSearch?: boolean
searchPlaceholder?: string
defaultExpandAll?: boolean
nodeKey?: string | number
customFilter?: (value: string, data: FilterTreeNode) => boolean
autoHeight?: boolean
minHeight?: string | number
accordion?: boolean
}>(),
{
showSearch: true,
searchPlaceholder: '请输入关键词过滤',
defaultExpandAll: false,
nodeKey: 'id',
autoHeight: true,
minHeight: '100px',
treeProps: (): TreePropsCompat => ({
children: 'children',
label: 'label'
}),
accordion: true
}
)
const filterText = ref('')
const treeRef = ref<InstanceType<typeof ElTree>>()
const wrapperRef = ref<HTMLDivElement>(null)
const treeHeight = computed(() => {
if (!props.autoHeight || !wrapperRef.value) return props.minHeight as string
const wrapperHeight = wrapperRef.value.clientHeight
const searchHeight = props.showSearch ? 40 : 0
const dividerHeight = props.showSearch ? (16 + 2) : 0
const baseMargin = 8
return `${wrapperHeight - searchHeight - dividerHeight - baseMargin}px`
})
watch(
() => filterText.value,
(val) => {
emit('search-change', val)
treeRef.value?.filter(val)
},
{ immediate: false }
)
const filterNode = (value: string, data: FilterTreeNode): boolean => {
if (props.customFilter) return props.customFilter(value, data)
if (!value) return true
return data.label.includes(value)
}
const handleNodeClick = (...args: any[]) => {
const data = args[0] as FilterTreeNode
emit('node-click', data)
}
const handleClear = () => {
filterText.value = ''
emit('search-clear')
treeRef.value?.filter('')
}
onMounted(async () => {
await nextTick()
const resizeHandler = () => void treeHeight.value
window.addEventListener('resize', resizeHandler)
if (window.ResizeObserver && wrapperRef.value) {
const observer = new ResizeObserver(resizeHandler)
observer.observe(wrapperRef.value)
onUnmounted(() => {
observer.disconnect()
window.removeEventListener('resize', resizeHandler)
})
}
})
defineExpose({
doFilter: (value: string) => {
filterText.value = value
treeRef.value?.filter(value)
},
getTreeRef: () => treeRef.value,
clearSearch: handleClear
})
</script>
<style scoped>
:deep(.el-card) {
height: 100%;
border: none;
box-shadow: none;
padding: 0;
}
.filter-search-input {
margin: 0 2px !important;
width: calc(100% - 4px) !important;
}
.filter-tree-divider {
margin: 2px 2px 0 2px !important;
height: 1px;
}
:deep(.el-tree) {
width: 100%;
--el-tree-node-content-hover-bg-color: #f0f9ff;
--el-tree-node-selected-bg-color: #e6f7ff;
--el-tree-node-selected-color: #1890ff;
}
:deep(.el-tree-node__content) {
cursor: pointer;
padding: 4px 8px;
}
</style>

View File

@@ -7,31 +7,113 @@
>
<template #sidebar>
<div class="sidebar-content">
<FilterTree
ref="filterTreeRef"
:tree-data="treeData"
:default-expand-all="false"
:auto-height="true"
:min-height="'100%'"
@node-click="handleNodeClick"
@search-change="handleSearchChange"
/>
</div>
</template>
<template #main>
<div class="main-content">
<vMeun />
<vMeun />
</div>
</template>
</ResizablePage>
</template>
<script setup>
import { ref } from 'vue'
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from 'vue'
import ResizablePage from '@/components/Layout/proResizable.vue'
import FilterTree, { type FilterTreeNode } from '@/components/Table/proFilterTree.vue'
import vMeun from './list.vue'
const treeData = ref<FilterTreeNode[]>([
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
children: [
{ id: 9, label: 'Level three 1-1-1' },
{ id: 10, label: 'Level three 1-1-2' },
],
},
],
},
{
id: 2,
label: 'Level one 2',
children: [
{ id: 5, label: 'Level two 2-1' },
{ id: 6, label: 'Level two 2-2' },
],
},
{
id: 3,
label: 'Level one 3',
children: [
{ id: 7, label: 'Level two 3-1' },
{ id: 8, label: 'Level two 3-2' },
],
},
])
const filterTreeRef = ref<InstanceType<typeof FilterTree> | null>(null)
const handleNodeClick = (data: FilterTreeNode) => {
const nodeInfo = {
id: data.id,
label: data.label,
isLeaf: !data.children || data.children.length === 0,
}
console.log('=== 点击节点信息 ===', nodeInfo)
}
let searchDebounceTimer: number | null = null
const handleSearchChange = (value: string) => {
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
searchDebounceTimer = window.setTimeout(() => {
console.log('搜索关键词(防抖后):', value.trim())
}, 300)
}
onMounted(() => {
const cleanup = () => {
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
}
onUnmounted(cleanup)
})
</script>
<style scoped>
.sidebar-content {
.sidebar-content, .main-content {
width: 100%;
height: 100%;
box-sizing: border-box;
overflow: hidden;
padding: 0;
}
.main-content {
padding: 0px;
:deep(.el-tree) {
height: 100%;
overflow-y: auto;
}
:deep(.el-tree::-webkit-scrollbar) {
width: 6px;
}
:deep(.el-tree::-webkit-scrollbar-thumb) {
background-color: #e5e7eb;
border-radius: 3px;
}
:deep(.el-tree::-webkit-scrollbar-track) {
background-color: #f9fafb;
}
</style>