[更新]:树形组件基础版
This commit is contained in:
parent
a1824b2da4
commit
6934b11c46
@ -5,50 +5,153 @@
|
|||||||
:data="data"
|
:data="data"
|
||||||
>
|
>
|
||||||
</lay-tree>
|
</lay-tree>
|
||||||
</template>
|
</template>i
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const data = ref([{
|
const data = ref([{
|
||||||
title: '江西'
|
title: '一级1'
|
||||||
,id: 1
|
,id: 1
|
||||||
|
,field: 'name1'
|
||||||
|
,checked: true
|
||||||
|
,spread: true
|
||||||
,children: [{
|
,children: [{
|
||||||
title: '南昌'
|
title: '二级1-1 可允许跳转'
|
||||||
,id: 1000
|
,id: 3
|
||||||
|
,field: 'name11'
|
||||||
|
,href: 'https://www.layui.com/'
|
||||||
,children: [{
|
,children: [{
|
||||||
title: '青山湖区'
|
title: '三级1-1-3'
|
||||||
,id: 10001
|
,id: 23
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '四级1-1-3-1'
|
||||||
|
,id: 24
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '五级1-1-3-1-1'
|
||||||
|
,id: 30
|
||||||
|
,field: ''
|
||||||
|
},{
|
||||||
|
title: '五级1-1-3-1-2'
|
||||||
|
,id: 31
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
|
}]
|
||||||
},{
|
},{
|
||||||
title: '高新区'
|
title: '三级1-1-1'
|
||||||
,id: 10002
|
,id: 7
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '四级1-1-1-1 可允许跳转'
|
||||||
|
,id: 15
|
||||||
|
,field: ''
|
||||||
|
,href: 'https://www.layui.com/doc/'
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
title: '三级1-1-2'
|
||||||
|
,id: 8
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '四级1-1-2-1'
|
||||||
|
,id: 32
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
},{
|
},{
|
||||||
title: '九江'
|
title: '二级1-2'
|
||||||
,id: 1001
|
,id: 4
|
||||||
|
,spread: true
|
||||||
|
,children: [{
|
||||||
|
title: '三级1-2-1'
|
||||||
|
,id: 9
|
||||||
|
,field: ''
|
||||||
|
,disabled: true
|
||||||
|
},{
|
||||||
|
title: '三级1-2-2'
|
||||||
|
,id: 10
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
},{
|
},{
|
||||||
title: '赣州'
|
title: '二级1-3'
|
||||||
,id: 1002
|
,id: 20
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '三级1-3-1'
|
||||||
|
,id: 21
|
||||||
|
,field: ''
|
||||||
|
},{
|
||||||
|
title: '三级1-3-2'
|
||||||
|
,id: 22
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
},{
|
},{
|
||||||
title: '广西'
|
title: '一级2'
|
||||||
,id: 2
|
,id: 2
|
||||||
|
,field: ''
|
||||||
|
,spread: true
|
||||||
,children: [{
|
,children: [{
|
||||||
title: '南宁'
|
title: '二级2-1'
|
||||||
,id: 2000
|
,id: 5
|
||||||
|
,field: ''
|
||||||
|
,spread: true
|
||||||
|
,children: [{
|
||||||
|
title: '三级2-1-1'
|
||||||
|
,id: 11
|
||||||
|
,field: ''
|
||||||
|
},{
|
||||||
|
title: '三级2-1-2'
|
||||||
|
,id: 12
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
},{
|
},{
|
||||||
title: '桂林'
|
title: '二级2-2'
|
||||||
,id: 2001
|
,id: 6
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '三级2-2-1'
|
||||||
|
,id: 13
|
||||||
|
,field: ''
|
||||||
|
},{
|
||||||
|
title: '三级2-2-2'
|
||||||
|
,id: 14
|
||||||
|
,field: ''
|
||||||
|
,disabled: true
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
},{
|
},{
|
||||||
title: '陕西'
|
title: '一级3'
|
||||||
,id: 3
|
,id: 16
|
||||||
|
,field: ''
|
||||||
,children: [{
|
,children: [{
|
||||||
title: '西安'
|
title: '二级3-1'
|
||||||
,id: 3000
|
,id: 17
|
||||||
|
,field: ''
|
||||||
|
,fixed: true
|
||||||
|
,children: [{
|
||||||
|
title: '三级3-1-1'
|
||||||
|
,id: 18
|
||||||
|
,field: ''
|
||||||
|
},{
|
||||||
|
title: '三级3-1-2'
|
||||||
|
,id: 19
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
},{
|
},{
|
||||||
title: '延安'
|
title: '二级3-2'
|
||||||
,id: 3001
|
,id: 27
|
||||||
|
,field: ''
|
||||||
|
,children: [{
|
||||||
|
title: '三级3-2-1'
|
||||||
|
,id: 28
|
||||||
|
,field: ''
|
||||||
|
},{
|
||||||
|
title: '三级3-2-2'
|
||||||
|
,id: 29
|
||||||
|
,field: ''
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
}])
|
}])
|
||||||
</script>
|
</script>
|
||||||
|
@ -4,101 +4,92 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, unref, VNode, VNodeChild } from 'vue'
|
|
||||||
import LayIcon from '../icon'
|
import LayIcon from '../icon'
|
||||||
|
import { TreeNode } from '/@src/module/tree/tree.type'
|
||||||
interface TreeData {
|
|
||||||
/**
|
|
||||||
* 节点唯一索引值,用于对指定节点进行各类操作
|
|
||||||
*/
|
|
||||||
id: string | number
|
|
||||||
/**
|
|
||||||
* 节点标题
|
|
||||||
*/
|
|
||||||
title: string | (() => string)
|
|
||||||
/**
|
|
||||||
* 节点字段名
|
|
||||||
*/
|
|
||||||
field: string | (() => string)
|
|
||||||
/**
|
|
||||||
* 子节点。支持设定选项同父节点
|
|
||||||
*/
|
|
||||||
children: TreeData[]
|
|
||||||
/**
|
|
||||||
* 点击节点弹出新窗口对应的 url。需开启 isJump 参数
|
|
||||||
* 废弃,通过 on-click事件用户控制
|
|
||||||
*/
|
|
||||||
href: string | URL
|
|
||||||
/**
|
|
||||||
* 节点是否初始展开,默认 false
|
|
||||||
* 废弃:设置 v-model:spreadKeys
|
|
||||||
*/
|
|
||||||
spread: boolean
|
|
||||||
/**
|
|
||||||
* 节点是否初始为选中状态(如果开启复选框的话),默认 false
|
|
||||||
* 废弃:设置 v-model:checkedKeys
|
|
||||||
*/
|
|
||||||
checked: boolean
|
|
||||||
/**
|
|
||||||
* 节点是否为禁用状态。默认 false
|
|
||||||
*/
|
|
||||||
disabled: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TreeEntityProps {
|
interface TreeEntityProps {
|
||||||
node: TreeData
|
node: TreeNode
|
||||||
id: string | number
|
}
|
||||||
|
|
||||||
|
interface EmitEvent {
|
||||||
|
(e: 'node-click', node: TreeNode): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<TreeEntityProps>()
|
const props = defineProps<TreeEntityProps>()
|
||||||
|
const emit = defineEmits<EmitEvent>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否设置短线
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
|
const renderLineShort = (node: TreeNode): boolean => {
|
||||||
|
return (
|
||||||
|
// 没有下一个节点
|
||||||
|
node._nextSibling === null &&
|
||||||
|
node._parentNode &&
|
||||||
|
// 外层最后一个
|
||||||
|
(node._parentNode._nextSibling === null ||
|
||||||
|
//上一层父级有延伸线
|
||||||
|
(node._parentNode._nextSibling && !node._parentNode._nextSibling.children))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 展开收起 icon样式
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
|
const nodeIconType = (node: TreeNode): string => {
|
||||||
|
return !node.spread ? 'layui-icon-addition' : 'layui-icon-subtraction'
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNodeClick (node: TreeNode) {
|
||||||
|
emit('node-click', node)
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<template v-if="node.children && node.children.length > 0">
|
<template v-if="node.children && node.children.length > 0">
|
||||||
<div
|
<div
|
||||||
class="layui-tree-set"
|
class="layui-tree-set"
|
||||||
:class="{
|
:class="{
|
||||||
// 'layui-tree-setLineShort': (index + 1) === data.length,
|
'layui-tree-setLineShort': renderLineShort(node),
|
||||||
'layui-tree-setLineShort': false,
|
'layui-tree-setHide': !node.parentId,
|
||||||
'layui-tree-setHide': node._isRoot,
|
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<!-- {{index}}, {{ data.length }}-->
|
|
||||||
<div class="layui-tree-entry">
|
<div class="layui-tree-entry">
|
||||||
<div class="layui-tree-main">
|
<div class="layui-tree-main" @click="handleNodeClick(node)">
|
||||||
<span
|
<span class="layui-tree-iconClick layui-tree-icon">
|
||||||
class="layui-tree-iconClick layui-tree-icon"
|
<LayIcon :type="nodeIconType(node)"></LayIcon>
|
||||||
>
|
|
||||||
<LayIcon
|
|
||||||
type="layui-icon-subtraction"
|
|
||||||
></LayIcon>
|
|
||||||
</span>
|
</span>
|
||||||
<span class="layui-tree-txt">{{ node.title }}---{{node._hasChild}}</span>
|
<span class="layui-tree-txt">{{ node.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="layui-tree-pack layui-tree-lineExtend layui-tree-showLine"
|
class="layui-tree-pack layui-tree-showLine"
|
||||||
style="display: block"
|
style="display: block"
|
||||||
|
v-show="node.spread"
|
||||||
>
|
>
|
||||||
<LayTreeEntity
|
<LayTreeEntity
|
||||||
v-for="(item, index) in node.children"
|
v-for="(item, index) in node.children"
|
||||||
:key="index"
|
:key="index"
|
||||||
:node="item"
|
:node="item"
|
||||||
:id="item.id"
|
@node-click="handleNodeClick"
|
||||||
></LayTreeEntity>
|
></LayTreeEntity>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="layui-tree-set" :class="{'layui-tree-setLineShort': false}"><!-- :class="{'layui-tree-setLineShort': isLast}"-->
|
<div
|
||||||
|
class="layui-tree-set"
|
||||||
|
:class="{
|
||||||
|
'layui-tree-setLineShort': renderLineShort(node),
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div class="layui-tree-entry">
|
<div class="layui-tree-entry">
|
||||||
<div class="layui-tree-main">
|
<div class="layui-tree-main">
|
||||||
<span
|
<span class="layui-tree-iconClick">
|
||||||
class="layui-tree-iconClick">
|
<LayIcon type="layui-icon-file"></LayIcon>
|
||||||
<LayIcon
|
|
||||||
type="layui-icon-file"
|
|
||||||
></LayIcon>
|
|
||||||
</span>
|
</span>
|
||||||
<span class="layui-tree-txt">{{ node.title }}, {{node._hasChild}},{{node._parentExtend}}</span>
|
<span class="layui-tree-txt">{{ node.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, unref, VNode, VNodeChild } from 'vue'
|
import { VNode, VNodeChild } from 'vue'
|
||||||
import TreeEntity from './TreeEntity.vue'
|
import TreeEntity from './TreeEntity.vue'
|
||||||
|
import { useTreeData } from '/@src/module/tree/useTreeData'
|
||||||
|
import { TreeNode } from '/@src/module/tree/tree.type'
|
||||||
|
|
||||||
interface TreeData {
|
type EditAction = 'add' | 'update' | 'del'
|
||||||
|
|
||||||
|
type EditType = boolean | EditAction[]
|
||||||
|
|
||||||
|
export declare interface TreeData {
|
||||||
/**
|
/**
|
||||||
* 节点唯一索引值,用于对指定节点进行各类操作
|
* 节点唯一索引值,用于对指定节点进行各类操作
|
||||||
*/
|
*/
|
||||||
@ -40,27 +46,23 @@ interface TreeData {
|
|||||||
disabled: boolean
|
disabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditAction = 'add' | 'update' | 'del'
|
export declare interface TreeProps {
|
||||||
|
|
||||||
type EditType = boolean | EditAction[]
|
|
||||||
|
|
||||||
interface TreeProps {
|
|
||||||
/**
|
/**
|
||||||
* 指定唯一的id
|
* 指定唯一的id
|
||||||
*/
|
*/
|
||||||
id?: string
|
key?: string
|
||||||
/**
|
/**
|
||||||
* 选中的节点
|
* 选中的节点
|
||||||
*/
|
*/
|
||||||
checkedKeys?: any[]
|
checkedKeys?: (string | number)[]
|
||||||
/**
|
/**
|
||||||
* 展开的节点
|
* 展开的节点
|
||||||
*/
|
*/
|
||||||
spreadKeys?: any[]
|
spreadKeys?: (string | number)[]
|
||||||
/**
|
/**
|
||||||
* 数据源
|
* 数据源
|
||||||
*/
|
*/
|
||||||
data?: TreeData
|
data?: TreeData[]
|
||||||
/**
|
/**
|
||||||
* 是否显示复选框 默认 false
|
* 是否显示复选框 默认 false
|
||||||
*/
|
*/
|
||||||
@ -100,11 +102,12 @@ interface TreeProps {
|
|||||||
/**
|
/**
|
||||||
* 数据为空时的提示文本
|
* 数据为空时的提示文本
|
||||||
*/
|
*/
|
||||||
none?: () => string | string | VNode | Element
|
none?: (() => string) | string | VNode | Element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeNode {
|
|
||||||
|
export interface EmitData {
|
||||||
/**
|
/**
|
||||||
* 当前点击的节点数据
|
* 当前点击的节点数据
|
||||||
*/
|
*/
|
||||||
@ -119,27 +122,26 @@ interface TreeNode {
|
|||||||
elem: Element | VNode | VNodeChild
|
elem: Element | VNode | VNodeChild
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeEmits {
|
export interface TreeEmits {
|
||||||
/**
|
/**
|
||||||
* 节点被点击后触发
|
* 节点被点击后触发
|
||||||
* @param e 事件
|
* @param e 事件
|
||||||
* @param treeNode
|
* @param treeNode
|
||||||
*/
|
*/
|
||||||
(e: 'on-click', treeNode: TreeNode): void
|
(e: 'on-click', treeNode: EmitData): void
|
||||||
/**
|
/**
|
||||||
* 点击复选框时触发
|
* 点击复选框时触发
|
||||||
* @param e 事件
|
* @param e 事件
|
||||||
* @param treeNode
|
* @param treeNode
|
||||||
*/
|
*/
|
||||||
(e: 'on-check', treeNode: TreeNode): void
|
(e: 'on-check', treeNode: EmitData): void
|
||||||
/**
|
/**
|
||||||
* 操作节点的回调
|
* 操作节点的回调
|
||||||
* @param e 事件
|
* @param e 事件
|
||||||
* @param treeNode
|
* @param treeNode
|
||||||
*/
|
*/
|
||||||
(e: 'on-operate', treeNode: TreeNode): void
|
(e: 'on-operate', treeNode: EmitData): void
|
||||||
(e: 'update:checkedKeys', keys: any[]): void
|
(e: 'update:spreadKeys', spreadKeys: string[]): void
|
||||||
(e: 'update:spreadKeys', keys: any[]): void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TreeProps>(), {
|
const props = withDefaults(defineProps<TreeProps>(), {
|
||||||
@ -148,48 +150,21 @@ const props = withDefaults(defineProps<TreeProps>(), {
|
|||||||
onlyIconControl: false,
|
onlyIconControl: false,
|
||||||
isJump: false,
|
isJump: false,
|
||||||
showLine: true,
|
showLine: true,
|
||||||
edit: true,
|
edit: () => true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// tree wrapper style
|
const emit = defineEmits<TreeEmits>()
|
||||||
const treeWrapperClass = computed(() => {
|
|
||||||
const { showCheckbox, showLine } = unref(props)
|
|
||||||
return {
|
|
||||||
'layui-tree': true,
|
|
||||||
'layui-form': showCheckbox,
|
|
||||||
'layui-tree-line': showLine,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
const {
|
||||||
* 按 layui的思路,这里改变数据
|
innerTreeData,
|
||||||
* @param data
|
updateInnerTreeData,
|
||||||
* @param isRootNode
|
treeWrapperClass
|
||||||
*/
|
} = useTreeData(props, emit)
|
||||||
function transformTreeData (data: TreeData[], parentExtend = false, isRootNode = true) {
|
|
||||||
data.forEach((item, index) => {
|
function handleNodeClick(node: TreeNode) {
|
||||||
item._parentExtend = !parentExtend ? (index + 1) !== data.length: parentExtend
|
updateInnerTreeData(innerTreeData.value, node)
|
||||||
if(isRootNode) {
|
|
||||||
item._isRoot = true
|
|
||||||
}
|
|
||||||
if (item.children) {
|
|
||||||
item._hasChild = true
|
|
||||||
transformTreeData(item.children, item._parentExtend, false)
|
|
||||||
} else {
|
|
||||||
item._hasChild = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderData = computed(() => {
|
|
||||||
const { data } = unref(props)
|
|
||||||
transformTreeData(data)
|
|
||||||
console.log(data)
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(renderData)
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
@ -199,148 +174,11 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div :class="treeWrapperClass">
|
<div :class="treeWrapperClass">
|
||||||
<TreeEntity
|
<TreeEntity
|
||||||
v-for="(node) in renderData"
|
v-for="(node) in innerTreeData"
|
||||||
:key="node.id"
|
:key="node.id"
|
||||||
:id="node.id"
|
|
||||||
:node="node"
|
:node="node"
|
||||||
|
@node-click="handleNodeClick"
|
||||||
></TreeEntity>
|
></TreeEntity>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
<div class="layui-tree layui-tree-line" lay-filter="LAY-tree-2">
|
|
||||||
<div
|
|
||||||
data-id="1"
|
|
||||||
class="layui-tree-set layui-tree-setHide layui-tree-spread"
|
|
||||||
>
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick layui-tree-icon"
|
|
||||||
><i class="layui-icon layui-icon-subtraction"></i></span
|
|
||||||
><span class="layui-tree-txt">江西</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="layui-tree-pack layui-tree-lineExtend layui-tree-showLine"
|
|
||||||
style="display: block"
|
|
||||||
>
|
|
||||||
<div data-id="1000" class="layui-tree-set layui-tree-spread">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick layui-tree-icon"
|
|
||||||
><i class="layui-icon layui-icon-subtraction"></i></span
|
|
||||||
><span class="layui-tree-txt">南昌</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="layui-tree-pack layui-tree-lineExtend layui-tree-showLine"
|
|
||||||
style="display: block"
|
|
||||||
>
|
|
||||||
<div data-id="10001" class="layui-tree-set">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">青山湖区</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-id="10002" class="layui-tree-set layui-tree-setLineShort">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">高新区</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-id="1001" class="layui-tree-set">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">九江</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-id="1002" class="layui-tree-set">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">赣州</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-id="2"
|
|
||||||
class="layui-tree-set layui-tree-setHide layui-tree-spread"
|
|
||||||
>
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick layui-tree-icon"
|
|
||||||
><i class="layui-icon layui-icon-subtraction"></i></span
|
|
||||||
><span class="layui-tree-txt">广西</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="layui-tree-pack layui-tree-lineExtend layui-tree-showLine"
|
|
||||||
style="display: block"
|
|
||||||
>
|
|
||||||
<div data-id="2000" class="layui-tree-set">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">南宁</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-id="2001" class="layui-tree-set">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">桂林</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-id="3"
|
|
||||||
class="layui-tree-set layui-tree-setHide layui-tree-setLineShort layui-tree-spread"
|
|
||||||
>
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick layui-tree-icon"
|
|
||||||
><i class="layui-icon layui-icon-subtraction"></i></span
|
|
||||||
><span class="layui-tree-txt">陕西</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="layui-tree-pack layui-tree-lineExtend" style="display: block">
|
|
||||||
<div data-id="3000" class="layui-tree-set">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">西安</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-id="3001" class="layui-tree-set layui-tree-setLineShort">
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick"
|
|
||||||
><i class="layui-icon layui-icon-file"></i></span
|
|
||||||
><span class="layui-tree-txt">延安</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { TreeEmits, TreeProps } from '/@src/module/tree/type'
|
|
||||||
|
|
||||||
export const treeProps = withDefaults(defineProps<TreeProps>(), {
|
|
||||||
showCheckbox: false,
|
|
||||||
accordion: false,
|
|
||||||
onlyIconControl: false,
|
|
||||||
isJump: false,
|
|
||||||
showLine: true,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
edit: true
|
|
||||||
})
|
|
||||||
|
|
||||||
export const emit = defineEmits<TreeEmits>()
|
|
@ -1,36 +1,45 @@
|
|||||||
/**
|
/**
|
||||||
* defineProps 暂不支付外部导入的复杂类型,这里备用后续迁移。
|
* defineProps 暂不支持外部导入的复杂类型,这里备用后续迁移。
|
||||||
|
* @see https://github.com/vuejs/vue-next/issues/4294
|
||||||
* 暂时 在index.vue内部单独实现一份
|
* 暂时 在index.vue内部单独实现一份
|
||||||
*/
|
*/
|
||||||
import { VNode, VNodeChild } from 'vue'
|
import { ComputedRef, CSSProperties, Ref, UnwrapRef, VNode, VNodeChild, WritableComputedRef } from 'vue'
|
||||||
|
import { Nullable, Recordable } from '/@src/module/type'
|
||||||
|
|
||||||
export interface TreeData {
|
type EditAction = 'add' | 'update' | 'del'
|
||||||
|
|
||||||
|
type EditType = boolean | EditAction[]
|
||||||
|
|
||||||
|
export declare interface TreeData {
|
||||||
/**
|
/**
|
||||||
* 节点唯一索引值,用于对指定节点进行各类操作
|
* 节点唯一索引值,用于对指定节点进行各类操作
|
||||||
*/
|
*/
|
||||||
id: () => (string | number) | string | number
|
id: string | number
|
||||||
/**
|
/**
|
||||||
* 节点标题
|
* 节点标题
|
||||||
*/
|
*/
|
||||||
title: (() => string) | string
|
title: string | (() => string)
|
||||||
/**
|
/**
|
||||||
* 节点字段名
|
* 节点字段名
|
||||||
*/
|
*/
|
||||||
field: (() => string) | string
|
field: string | (() => string)
|
||||||
/**
|
/**
|
||||||
* 子节点。支持设定选项同父节点
|
* 子节点。支持设定选项同父节点
|
||||||
*/
|
*/
|
||||||
children: TreeData[]
|
children: TreeData[]
|
||||||
/**
|
/**
|
||||||
* 点击节点弹出新窗口对应的 url。需开启 isJump 参数
|
* 点击节点弹出新窗口对应的 url。需开启 isJump 参数
|
||||||
|
* 废弃,通过 on-click事件用户控制
|
||||||
*/
|
*/
|
||||||
href: string | URL
|
href: string | URL
|
||||||
/**
|
/**
|
||||||
* 节点是否初始展开,默认 false
|
* 节点是否初始展开,默认 false
|
||||||
|
* 废弃:设置 v-model:spreadKeys
|
||||||
*/
|
*/
|
||||||
spread: boolean
|
spread: boolean
|
||||||
/**
|
/**
|
||||||
* 节点是否初始为选中状态(如果开启复选框的话),默认 false
|
* 节点是否初始为选中状态(如果开启复选框的话),默认 false
|
||||||
|
* 废弃:设置 v-model:checkedKeys
|
||||||
*/
|
*/
|
||||||
checked: boolean
|
checked: boolean
|
||||||
/**
|
/**
|
||||||
@ -39,15 +48,23 @@ export interface TreeData {
|
|||||||
disabled: boolean
|
disabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditAction = 'add' | 'update' | 'del'
|
export declare interface TreeProps {
|
||||||
|
/**
|
||||||
type EditType = boolean | EditAction[]
|
* 指定唯一的id
|
||||||
|
*/
|
||||||
export interface TreeProps {
|
key?: string
|
||||||
|
/**
|
||||||
|
* 选中的节点
|
||||||
|
*/
|
||||||
|
checkedKeys?: (string | number)[]
|
||||||
|
/**
|
||||||
|
* 展开的节点
|
||||||
|
*/
|
||||||
|
spreadKeys?: (string | number)[]
|
||||||
/**
|
/**
|
||||||
* 数据源
|
* 数据源
|
||||||
*/
|
*/
|
||||||
data?: TreeData
|
data?: TreeData[]
|
||||||
/**
|
/**
|
||||||
* 是否显示复选框 默认 false
|
* 是否显示复选框 默认 false
|
||||||
*/
|
*/
|
||||||
@ -69,6 +86,7 @@ export interface TreeProps {
|
|||||||
onlyIconControl?: boolean
|
onlyIconControl?: boolean
|
||||||
/**
|
/**
|
||||||
* 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式
|
* 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式
|
||||||
|
* 废弃:能过事件用户自行控制
|
||||||
*/
|
*/
|
||||||
isJump?: boolean
|
isJump?: boolean
|
||||||
/**
|
/**
|
||||||
@ -86,11 +104,11 @@ export interface TreeProps {
|
|||||||
/**
|
/**
|
||||||
* 数据为空时的提示文本
|
* 数据为空时的提示文本
|
||||||
*/
|
*/
|
||||||
none?: () => string | string | VNode | Element
|
none?: (() => string) | string | VNode | Element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TreeNode {
|
export interface EmitData {
|
||||||
/**
|
/**
|
||||||
* 当前点击的节点数据
|
* 当前点击的节点数据
|
||||||
*/
|
*/
|
||||||
@ -111,22 +129,22 @@ export interface TreeEmits {
|
|||||||
* @param e 事件
|
* @param e 事件
|
||||||
* @param treeNode
|
* @param treeNode
|
||||||
*/
|
*/
|
||||||
(e: 'on-click', treeNode: TreeNode): void
|
(e: 'on-click', treeNode: EmitData): void
|
||||||
/**
|
/**
|
||||||
* 点击复选框时触发
|
* 点击复选框时触发
|
||||||
* @param e 事件
|
* @param e 事件
|
||||||
* @param treeNode
|
* @param treeNode
|
||||||
*/
|
*/
|
||||||
(e: 'on-check', treeNode: TreeNode): void
|
(e: 'on-check', treeNode: EmitData): void
|
||||||
/**
|
/**
|
||||||
* 操作节点的回调
|
* 操作节点的回调
|
||||||
* @param e 事件
|
* @param e 事件
|
||||||
* @param treeNode
|
* @param treeNode
|
||||||
*/
|
*/
|
||||||
(e: 'on-operate', treeNode: TreeNode): void
|
(e: 'on-operate', treeNode: EmitData): void
|
||||||
|
(e: 'update:spreadKeys', spreadKeys: (string | number)[]): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface TreeExpose {
|
export interface TreeExpose {
|
||||||
/**
|
/**
|
||||||
* 返回选中的节点数据
|
* 返回选中的节点数据
|
||||||
@ -138,3 +156,22 @@ export interface TreeExpose {
|
|||||||
|
|
||||||
// 设置节点勾选 setChecked 变为v-model控制
|
// 设置节点勾选 setChecked 变为v-model控制
|
||||||
// 实例重载 reload 变为v-model控制
|
// 实例重载 reload 变为v-model控制
|
||||||
|
|
||||||
|
export interface TreeNode extends TreeData {
|
||||||
|
parentId: string | number
|
||||||
|
children: TreeNode[]
|
||||||
|
_parentNode?: Nullable<TreeNode>
|
||||||
|
_nextSibling?: Nullable<TreeNode>
|
||||||
|
_expand?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** hook type **/
|
||||||
|
export type UseTreeData = (
|
||||||
|
props: TreeProps,
|
||||||
|
emit: TreeEmits
|
||||||
|
) => {
|
||||||
|
innerTreeData: Ref<UnwrapRef<TreeData[]>>
|
||||||
|
spreadKeys: WritableComputedRef<(string | number)[]>
|
||||||
|
treeWrapperClass: ComputedRef<Recordable>
|
||||||
|
updateInnerTreeData: (treeData: TreeData[], node: TreeData) => void
|
||||||
|
}
|
85
src/module/tree/treeHelper.ts
Normal file
85
src/module/tree/treeHelper.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { TreeData, TreeNode } from '/@src/module/tree/tree.type'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加父级parentId
|
||||||
|
* @param data
|
||||||
|
* @param parentId
|
||||||
|
*/
|
||||||
|
export const generatorTreeData = (data: TreeData[] | TreeNode[], parentId: TreeNode['parentId'] = ''): TreeNode[] => {
|
||||||
|
const innerTreeData: TreeNode[] = []
|
||||||
|
const len = data.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const item = data[i]
|
||||||
|
const inner = {
|
||||||
|
...item,
|
||||||
|
parentId: parentId,
|
||||||
|
spread: item.spread || false
|
||||||
|
}
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
inner.children = generatorTreeData(item.children, item.id)
|
||||||
|
}
|
||||||
|
innerTreeData.push(inner as TreeNode)
|
||||||
|
}
|
||||||
|
return innerTreeData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前节点的下一个节点nextSibling,没有为null
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const setNextSiblings = (data: TreeNode[]): void => {
|
||||||
|
const len = data.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
data[i]._nextSibling = (i + 1) < len ? data[i+1] : null
|
||||||
|
if (data[i].children && data[i].children.length > 0) {
|
||||||
|
setNextSiblings(data[i].children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前节点的父节点,没有为null
|
||||||
|
* @param data
|
||||||
|
* @param parentNode
|
||||||
|
*/
|
||||||
|
export const setParentNode = (data: TreeNode[], parentNode?: TreeNode): void => {
|
||||||
|
const len = data.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
data[i]._parentNode = parentNode ? parentNode : null
|
||||||
|
if (data[i].children && data[i].children.length > 0) {
|
||||||
|
setParentNode(data[i].children, data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化内部tree结构
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const initialTreeData = (data: TreeData[]): TreeNode[] => {
|
||||||
|
const innerTree = generatorTreeData(data)
|
||||||
|
setNextSiblings(innerTree)
|
||||||
|
setParentNode(innerTree)
|
||||||
|
return innerTree
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树展开的节点
|
||||||
|
* v-model:spreadKeys 或者
|
||||||
|
* data.item.spreadKeys
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const getTreeSpreadKeys = (data: TreeData[]): (string | number)[] => {
|
||||||
|
let keys: (string | number) [] = []
|
||||||
|
const len = data.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const id: number | string = data[i].id
|
||||||
|
if (data[i].spread) {
|
||||||
|
keys.push(id)
|
||||||
|
}
|
||||||
|
if (data[i].children && data[i].children.length > 0) {
|
||||||
|
keys = [...keys, ...getTreeSpreadKeys(data[i].children)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
60
src/module/tree/useTreeData.ts
Normal file
60
src/module/tree/useTreeData.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { TreeData, UseTreeData } from '/@src/module/tree/tree.type'
|
||||||
|
import { computed, ref, unref, watch } from 'vue'
|
||||||
|
import { getTreeSpreadKeys, initialTreeData } from '/@src/module/tree/treeHelper'
|
||||||
|
import { Recordable } from '/@src/module/type'
|
||||||
|
|
||||||
|
export const useTreeData: UseTreeData = (props, emit) => {
|
||||||
|
const spreadKeys = computed({
|
||||||
|
get: () => {
|
||||||
|
if (!props.data) return []
|
||||||
|
if (props.spreadKeys && props.spreadKeys.length > 0) {
|
||||||
|
return props.spreadKeys as (string | number)[]
|
||||||
|
}
|
||||||
|
return getTreeSpreadKeys(props.data)
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
emit('update:spreadKeys', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染的data
|
||||||
|
*/
|
||||||
|
const innerTreeData = ref<TreeData[]>([])
|
||||||
|
watch(() => props.data, (treeData) => {
|
||||||
|
if (treeData) {
|
||||||
|
innerTreeData.value = initialTreeData(treeData)
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
function updateInnerTreeData(treeData: TreeData[], node: TreeData): void {
|
||||||
|
for(let i = 0; i <treeData.length; i++) {
|
||||||
|
if (treeData[i].id === node.id) {
|
||||||
|
treeData[i].spread = !treeData[i].spread
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (treeData[i].children && treeData[i].children.length > 0) {
|
||||||
|
updateInnerTreeData(treeData[i].children, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tree wrapper class
|
||||||
|
*/
|
||||||
|
const treeWrapperClass = computed((): Recordable => {
|
||||||
|
const { showCheckbox, showLine } = unref(props)
|
||||||
|
return {
|
||||||
|
'layui-tree': true,
|
||||||
|
'layui-form': showCheckbox,
|
||||||
|
'layui-tree-line': showLine,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
spreadKeys,
|
||||||
|
innerTreeData,
|
||||||
|
updateInnerTreeData,
|
||||||
|
treeWrapperClass
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import type { App, DefineComponent } from 'vue'
|
import type { App, DefineComponent, Ref } from 'vue'
|
||||||
export type StringObject = Record<string, unknown>
|
export type StringObject = Record<string, unknown>
|
||||||
|
|
||||||
export type UnknownObject = Record<string | number, unknown>
|
export type UnknownObject = Record<string | number, unknown>
|
||||||
@ -14,4 +14,10 @@ export interface InstallOptions extends StringObject {
|
|||||||
pagination?: null
|
pagination?: null
|
||||||
/** Menu Attributes */
|
/** Menu Attributes */
|
||||||
menu?: null
|
menu?: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Nullable<T> = T | null
|
||||||
|
|
||||||
|
export type MaybeRef<T> = Ref<T> | T
|
||||||
|
|
||||||
|
export type Recordable = Record<string, any>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user