大屏页面初始化
This commit is contained in:
172
screen-vue/src/components/Table/proFilterTree.vue
Normal file
172
screen-vue/src/components/Table/proFilterTree.vue
Normal 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>
|
||||||
@@ -7,31 +7,113 @@
|
|||||||
>
|
>
|
||||||
<template #sidebar>
|
<template #sidebar>
|
||||||
<div class="sidebar-content">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<vMeun />
|
<vMeun />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ResizablePage>
|
</ResizablePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||||
import ResizablePage from '@/components/Layout/proResizable.vue'
|
import ResizablePage from '@/components/Layout/proResizable.vue'
|
||||||
|
import FilterTree, { type FilterTreeNode } from '@/components/Table/proFilterTree.vue'
|
||||||
import vMeun from './list.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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sidebar-content {
|
.sidebar-content, .main-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
:deep(.el-tree) {
|
||||||
padding: 0px;
|
|
||||||
height: 100%;
|
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>
|
</style>
|
||||||
Reference in New Issue
Block a user