refactor(tree): 移注第一版tree的实现
This commit is contained in:
parent
b4477f049b
commit
32252dba94
26
docs/docs/zh-CN/components/colorPicker.md
Normal file
26
docs/docs/zh-CN/components/colorPicker.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
::: field 基础使用
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: demo
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<lay-color-picker></lay-color-picker>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
::: field icon-picker 属性
|
||||||
|
:::
|
||||||
|
|
||||||
|
| | | |
|
||||||
|
| ---------- | -------- | --- |
|
||||||
|
| v-model | 默认值 | -- |
|
||||||
|
| page | 开启分页 | -- |
|
||||||
|
| showSearch | 启用搜索 | -- |
|
@ -328,6 +328,12 @@ export default {
|
|||||||
subTitle: 'select',
|
subTitle: 'select',
|
||||||
path: '/zh-CN/components/select',
|
path: '/zh-CN/components/select',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 40,
|
||||||
|
title: '颜色选择器',
|
||||||
|
subTitle: 'colorPicker',
|
||||||
|
path: '/zh-CN/components/colorPicker',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const selected = ref(1)
|
const selected = ref(1)
|
||||||
|
@ -219,6 +219,11 @@ const zhCN = [
|
|||||||
component: () => import('../../docs/zh-CN/components/select.md'),
|
component: () => import('../../docs/zh-CN/components/select.md'),
|
||||||
meta: { title: '下拉选择' },
|
meta: { title: '下拉选择' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/zh-CN/components/colorPicker',
|
||||||
|
component: () => import('../../docs/zh-CN/components/colorPicker.md'),
|
||||||
|
meta: { title: '颜色选择器' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -57,6 +57,7 @@ import LayCheckboxGroup from './module/checkboxGroup/index'
|
|||||||
import LaySlider from './module/slider/index'
|
import LaySlider from './module/slider/index'
|
||||||
import LayCarousel from './module/carousel/index'
|
import LayCarousel from './module/carousel/index'
|
||||||
import LayCarouselItem from './module/carouselItem/index'
|
import LayCarouselItem from './module/carouselItem/index'
|
||||||
|
import LayColorPicker from './module/colorPicker/index'
|
||||||
|
|
||||||
const components: Record<string, IDefineComponent> = {
|
const components: Record<string, IDefineComponent> = {
|
||||||
LayRadio,
|
LayRadio,
|
||||||
@ -113,7 +114,8 @@ const components: Record<string, IDefineComponent> = {
|
|||||||
LayCheckboxGroup,
|
LayCheckboxGroup,
|
||||||
LaySlider,
|
LaySlider,
|
||||||
LayCarousel,
|
LayCarousel,
|
||||||
LayCarouselItem
|
LayCarouselItem,
|
||||||
|
LayColorPicker,
|
||||||
}
|
}
|
||||||
|
|
||||||
const install = (app: App, options?: InstallOptions): void => {
|
const install = (app: App, options?: InstallOptions): void => {
|
||||||
@ -182,6 +184,7 @@ export {
|
|||||||
LaySlider,
|
LaySlider,
|
||||||
LayCarousel,
|
LayCarousel,
|
||||||
LayCarouselItem,
|
LayCarouselItem,
|
||||||
|
LayColorPicker,
|
||||||
install,
|
install,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import type { App } from 'vue'
|
||||||
|
import Component from './index.vue'
|
||||||
|
import { DefineComponent } from 'vue'
|
||||||
|
|
||||||
|
Component.install = (app: App) => {
|
||||||
|
app.component(Component.name || 'LayColorPicker', Component)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Component as DefineComponent
|
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'LayColorPicker'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pre>
|
||||||
|
// todo =_=
|
||||||
|
</pre>
|
||||||
|
</template>
|
@ -1,182 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'LayTreeEntity',
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import LayIcon from '../icon'
|
|
||||||
import LayCheckbox from '../checkbox'
|
|
||||||
import { TreeNode } from '/@src/module/tree/tree.type'
|
|
||||||
|
|
||||||
type EventType = 'icon' | 'node'
|
|
||||||
|
|
||||||
interface TreeEntityProps {
|
|
||||||
node: TreeNode
|
|
||||||
showCheckbox?: boolean
|
|
||||||
updateCheckedByNode: (node: TreeNode) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EmitEvent {
|
|
||||||
(e: 'node-click', node: TreeNode, type: EventType): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TreeEntityProps>(), {
|
|
||||||
showCheckbox: false,
|
|
||||||
})
|
|
||||||
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))) as boolean
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 展开收起 icon样式
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
const nodeIconType = (node: TreeNode): string => {
|
|
||||||
return !node.spread ? 'layui-icon-addition' : 'layui-icon-subtraction'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点Icon点击
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
function handleIconClick(node: TreeNode) {
|
|
||||||
emit('node-click', node, 'icon')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节点点击
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
function handleNodeClick(node: TreeNode) {
|
|
||||||
emit('node-click', node, 'node')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 递归emit事件
|
|
||||||
* @param node
|
|
||||||
* @param type
|
|
||||||
*/
|
|
||||||
function innerClick(node: TreeNode, type: EventType, e?: Event) {
|
|
||||||
emit('node-click', node, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checkbox click
|
|
||||||
* @param arg
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
function handleCheckboxChange(
|
|
||||||
arg: { checked: boolean; value: string },
|
|
||||||
node: TreeNode
|
|
||||||
) {
|
|
||||||
props.updateCheckedByNode(node)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<template v-if="node.children && node.children.length > 0">
|
|
||||||
<div
|
|
||||||
class="layui-tree-set"
|
|
||||||
:class="{
|
|
||||||
'layui-tree-setLineShort': renderLineShort(node),
|
|
||||||
'layui-tree-setHide': !node.parentId,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick layui-tree-icon">
|
|
||||||
<LayIcon
|
|
||||||
:type="nodeIconType(node)"
|
|
||||||
@click.prevent.stop="handleIconClick(node, 'icon')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<LayCheckbox
|
|
||||||
v-if="showCheckbox"
|
|
||||||
v-model:checked="node._checked"
|
|
||||||
name="name"
|
|
||||||
skin="primary"
|
|
||||||
@change="
|
|
||||||
(args) => {
|
|
||||||
handleCheckboxChange(args, node)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!-- {{ node.title }} || {{node.id}}-->
|
|
||||||
</LayCheckbox>
|
|
||||||
<span
|
|
||||||
class="layui-tree-txt"
|
|
||||||
@click.prevent.stop="handleNodeClick(node, 'node')"
|
|
||||||
>
|
|
||||||
{{ node.title }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-show="node.spread"
|
|
||||||
class="layui-tree-pack layui-tree-showLine"
|
|
||||||
style="display: block"
|
|
||||||
>
|
|
||||||
<LayTreeEntity
|
|
||||||
v-for="(item, index) in node.children"
|
|
||||||
:key="index"
|
|
||||||
:node="item"
|
|
||||||
:show-checkbox="showCheckbox"
|
|
||||||
:update-checked-by-node="updateCheckedByNode"
|
|
||||||
@node-click="innerClick"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
class="layui-tree-set"
|
|
||||||
:class="{
|
|
||||||
'layui-tree-setLineShort': renderLineShort(node),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="layui-tree-entry">
|
|
||||||
<div class="layui-tree-main">
|
|
||||||
<span class="layui-tree-iconClick">
|
|
||||||
<LayIcon
|
|
||||||
type="layui-icon-file"
|
|
||||||
@click.prevent.stop="handleIconClick(node, 'icon')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<LayCheckbox
|
|
||||||
v-if="showCheckbox"
|
|
||||||
v-model:checked="node._checked"
|
|
||||||
name="name"
|
|
||||||
skin="primary"
|
|
||||||
label="1"
|
|
||||||
@change="
|
|
||||||
(args) => {
|
|
||||||
handleCheckboxChange(args, node)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
</LayCheckbox>
|
|
||||||
<span
|
|
||||||
class="layui-tree-txt"
|
|
||||||
@click.prevent.stop="handleNodeClick(node, 'node')"
|
|
||||||
>
|
|
||||||
{{ node.title }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<style scoped lang="less"></style>
|
|
@ -5,13 +5,13 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StringOrNumber } from '/@src/module/tree/new-tree/tree.type'
|
import { StringOrNumber } from '/@src/module/tree/tree.type'
|
||||||
import { Nullable } from '/@src/module/type'
|
import { Nullable } from '/@src/module/type'
|
||||||
|
|
||||||
import LayIcon from '../../icon'
|
import LayIcon from '../icon'
|
||||||
import LayCheckbox from '../../checkbox'
|
import LayCheckbox from '../checkbox'
|
||||||
import { nextTick, Ref } from 'vue'
|
import { Ref } from 'vue'
|
||||||
import { Tree } from '/@src/module/tree/new-tree/tree'
|
import { Tree } from '/@src/module/tree/tree'
|
||||||
|
|
||||||
type CustomKey = string | number
|
type CustomKey = string | number
|
||||||
type CustomString = (() => string) | string
|
type CustomString = (() => string) | string
|
||||||
@ -134,7 +134,9 @@ function handleTitleClick(node: TreeData) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition>
|
<transition
|
||||||
|
name="move"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="node.isLeaf.value"
|
v-if="node.isLeaf.value"
|
||||||
class="layui-tree-pack layui-tree-showLine"
|
class="layui-tree-pack layui-tree-showLine"
|
||||||
@ -153,4 +155,5 @@ function handleTitleClick(node: TreeData) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
</style>
|
@ -1,6 +1,5 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
// import Component from './index.vue'
|
import Component from './index.vue'
|
||||||
import Component from './new-tree/index.vue'
|
|
||||||
import type { IDefineComponent } from '../type/index'
|
import type { IDefineComponent } from '../type/index'
|
||||||
|
|
||||||
Component.install = (app: App) => {
|
Component.install = (app: App) => {
|
||||||
|
@ -1,202 +1,99 @@
|
|||||||
<script setup lang="ts">
|
<!--
|
||||||
import { VNode, VNodeChild } from 'vue'
|
* @Date: 2021-10-16 13:22:38
|
||||||
import TreeEntity from './TreeEntity.vue'
|
* @LastEditors: 落小梅
|
||||||
import { useTreeData } from '/@src/module/tree/useTreeData'
|
* @LastEditTime: 2021-10-16 13:53:14
|
||||||
import { TreeNode } from '/@src/module/tree/tree.type'
|
* @FilePath: \layui-vue\src\module\tree\new-tree\index.vue
|
||||||
import { getEmitNode } from '/@src/module/tree/treeHelper'
|
-->
|
||||||
|
|
||||||
type EditAction = 'add' | 'update' | 'del'
|
|
||||||
|
|
||||||
type EditType = boolean | EditAction[]
|
|
||||||
|
|
||||||
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 TreeProps {
|
|
||||||
/**
|
|
||||||
* 指定唯一的id
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
|
||||||
key?: string
|
|
||||||
/**
|
|
||||||
* 选中的节点
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
|
||||||
checkedKeys?: NonNullable<(string | number)[]>
|
|
||||||
/**
|
|
||||||
* 展开的节点
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
|
||||||
spreadKeys?: (string | number)[]
|
|
||||||
/**
|
|
||||||
* 数据源
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
|
||||||
data?: TreeData[]
|
|
||||||
/**
|
|
||||||
* 是否显示复选框 默认 false
|
|
||||||
*/
|
|
||||||
showCheckbox?: boolean
|
|
||||||
/**
|
|
||||||
* 是否开启节点的操作图标。默认 false。
|
|
||||||
若为 true,则默认显示“改删”图标
|
|
||||||
若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有:add、update、del,如:
|
|
||||||
edit: ['add', 'update', 'del']
|
|
||||||
*/
|
|
||||||
edit?: EditType
|
|
||||||
/**
|
|
||||||
* 是否开启手风琴模式,默认 false
|
|
||||||
*/
|
|
||||||
accordion?: boolean
|
|
||||||
/**
|
|
||||||
* 是否仅允许节点左侧图标控制展开收缩。默认 false(即点击节点本身也可控制)。若为 true,则只能通过节点左侧图标来展开收缩
|
|
||||||
*/
|
|
||||||
onlyIconControl?: boolean
|
|
||||||
/**
|
|
||||||
* 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式
|
|
||||||
* 废弃:能过事件用户自行控制
|
|
||||||
*/
|
|
||||||
isJump?: boolean
|
|
||||||
/**
|
|
||||||
* 是否开启连接线。默认 true,若设为 false,则节点左侧出现三角图标。
|
|
||||||
*/
|
|
||||||
showLine?: boolean
|
|
||||||
/**
|
|
||||||
* 自定义各类默认文本,目前支持以下设定:
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line vue/require-default-prop
|
|
||||||
text?: {
|
|
||||||
/**
|
|
||||||
* 节点默认名称
|
|
||||||
*/
|
|
||||||
defaultNodeName?: () => string | string
|
|
||||||
/**
|
|
||||||
* 数据为空时的提示文本
|
|
||||||
*/
|
|
||||||
none?: (() => string) | string | VNode | Element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EmitData {
|
|
||||||
/**
|
|
||||||
* 当前点击的节点数据
|
|
||||||
*/
|
|
||||||
data: TreeData
|
|
||||||
/**
|
|
||||||
* 节点的展开状态
|
|
||||||
* remove
|
|
||||||
*/
|
|
||||||
state?: 'open' | 'close' | 'normal'
|
|
||||||
/**
|
|
||||||
* 当前节点元素
|
|
||||||
* remove
|
|
||||||
*/
|
|
||||||
elem?: Element | VNode | VNodeChild
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TreeEmits {
|
|
||||||
/**
|
|
||||||
* 节点被点击后触发
|
|
||||||
* @param e 事件
|
|
||||||
* @param treeNode
|
|
||||||
*/
|
|
||||||
(e: 'node-click', treeNode: EmitData): void
|
|
||||||
/**
|
|
||||||
* 点击复选框时触发
|
|
||||||
* @param e 事件
|
|
||||||
* @param treeNode
|
|
||||||
*/
|
|
||||||
(e: 'node-check', treeNode: EmitData): void
|
|
||||||
// /**
|
|
||||||
// * 操作节点的回调
|
|
||||||
// * @param e 事件
|
|
||||||
// * @param treeNode
|
|
||||||
// */
|
|
||||||
// (e: 'node-operate', treeNode: EmitData): void
|
|
||||||
(e: 'update:spreadKeys', spreadKeys: (string | number)[]): void
|
|
||||||
(e: 'update:checkedKeys', checkedKeys: (string | number)[]): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TreeProps>(), {
|
|
||||||
showCheckbox: false,
|
|
||||||
accordion: false,
|
|
||||||
onlyIconControl: false,
|
|
||||||
isJump: false,
|
|
||||||
showLine: true,
|
|
||||||
edit: () => true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<TreeEmits>()
|
|
||||||
|
|
||||||
const {
|
|
||||||
innerTreeData,
|
|
||||||
updateInnerTreeData,
|
|
||||||
treeWrapperClass,
|
|
||||||
updateCheckedByNode,
|
|
||||||
} = useTreeData(props, emit)
|
|
||||||
|
|
||||||
function handleNodeClick(node: TreeNode, type: 'node' | 'icon') {
|
|
||||||
// 是否只通过icon控制展开收起
|
|
||||||
if (props.onlyIconControl) {
|
|
||||||
type === 'icon' && updateInnerTreeData(innerTreeData.value, node)
|
|
||||||
} else {
|
|
||||||
updateInnerTreeData(innerTreeData.value, node)
|
|
||||||
}
|
|
||||||
// icon 点击不emit出事件
|
|
||||||
const emitNode = getEmitNode(props.data!, node)
|
|
||||||
type !== 'icon' && emit('node-click', { data: emitNode! })
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'LayTree',
|
name: 'LayTree',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import { TreeEmits, TreeProps as Tp } from './tree.type'
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import TreeNode from './TreeNode.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useTree } from '/@src/module/tree/useTree'
|
||||||
|
import { TreeData } from '/@src/module/tree/tree'
|
||||||
|
|
||||||
|
type StringFn = () => string
|
||||||
|
type StringOrNumber = string | number
|
||||||
|
type KeysType = (number | string)[]
|
||||||
|
type EditType = boolean | ('add' | 'update' | 'delete')
|
||||||
|
|
||||||
|
interface OriginalTreeData {
|
||||||
|
title: StringFn | string
|
||||||
|
id: StringOrNumber
|
||||||
|
field: StringFn | string
|
||||||
|
children?: OriginalTreeData[]
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeProps {
|
||||||
|
checkedKeys?: KeysType
|
||||||
|
data: OriginalTreeData
|
||||||
|
showCheckbox?: boolean
|
||||||
|
edit?: EditType
|
||||||
|
accordion?: boolean
|
||||||
|
onlyIconControl?: boolean
|
||||||
|
showLine?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
replaceFields?: {
|
||||||
|
id?: string
|
||||||
|
children?: string
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeEmits {
|
||||||
|
(e: 'update:checkedKeys', keys: KeysType): void
|
||||||
|
(e: 'update:expandKeys', keys: KeysType): void
|
||||||
|
(e: 'node-click', node: OriginalTreeData): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<TreeProps>(), {
|
||||||
|
showCheckbox: false,
|
||||||
|
edit: false,
|
||||||
|
accordion: false,
|
||||||
|
onlyIconControl: false,
|
||||||
|
disabled: false,
|
||||||
|
showLine: true,
|
||||||
|
replaceFields: () => {
|
||||||
|
return {
|
||||||
|
id: 'id',
|
||||||
|
children: 'children',
|
||||||
|
title: 'title',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<TreeEmits>()
|
||||||
|
|
||||||
|
const className = computed(() => {
|
||||||
|
return {
|
||||||
|
'layui-tree': true,
|
||||||
|
'layui-form': props.showCheckbox,
|
||||||
|
'layui-tree-line': props.showLine,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { tree, nodeList } = useTree(props, emit)
|
||||||
|
|
||||||
|
function handleClick(node: TreeData) {
|
||||||
|
const originNode = tree.getOriginData(node.id)
|
||||||
|
emit('node-click', originNode)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div :class="treeWrapperClass">
|
<div :class="className">
|
||||||
<TreeEntity
|
<tree-node
|
||||||
v-for="(node, index) in innerTreeData"
|
:tree="tree"
|
||||||
:key="node.id || index"
|
:node-list="nodeList"
|
||||||
:node="node"
|
|
||||||
:show-checkbox="showCheckbox"
|
:show-checkbox="showCheckbox"
|
||||||
:update-checked-by-node="updateCheckedByNode"
|
:show-line="showLine"
|
||||||
@node-click="handleNodeClick"
|
:only-icon-control="onlyIconControl"
|
||||||
/>
|
@node-click="handleClick"
|
||||||
|
></tree-node>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
<!--
|
|
||||||
* @Date: 2021-10-16 13:22:38
|
|
||||||
* @LastEditors: 落小梅
|
|
||||||
* @LastEditTime: 2021-10-16 13:53:14
|
|
||||||
* @FilePath: \layui-vue\src\module\tree\new-tree\index.vue
|
|
||||||
-->
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'LayTree',
|
|
||||||
}
|
|
||||||
|
|
||||||
// import { TreeEmits, TreeProps as Tp } from './tree.type'
|
|
||||||
</script>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import TreeNode from './TreeNode.vue'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useTree } from '/@src/module/tree/new-tree/useTree'
|
|
||||||
import { TreeData } from '/@src/module/tree/new-tree/tree'
|
|
||||||
|
|
||||||
type StringFn = () => string
|
|
||||||
type StringOrNumber = string | number
|
|
||||||
type KeysType = (number | string)[]
|
|
||||||
type EditType = boolean | ('add' | 'update' | 'delete')
|
|
||||||
|
|
||||||
interface OriginalTreeData {
|
|
||||||
title: StringFn | string
|
|
||||||
id: StringOrNumber
|
|
||||||
field: StringFn | string
|
|
||||||
children?: OriginalTreeData[]
|
|
||||||
disabled?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TreeProps {
|
|
||||||
checkedKeys?: KeysType
|
|
||||||
data: OriginalTreeData
|
|
||||||
showCheckbox?: boolean
|
|
||||||
edit?: EditType
|
|
||||||
accordion?: boolean
|
|
||||||
onlyIconControl?: boolean
|
|
||||||
showLine?: boolean
|
|
||||||
disabled?: boolean
|
|
||||||
replaceFields?: {
|
|
||||||
id?: string
|
|
||||||
children?: string
|
|
||||||
title?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TreeEmits {
|
|
||||||
(e: 'update:checkedKeys', keys: KeysType): void
|
|
||||||
(e: 'update:expandKeys', keys: KeysType): void
|
|
||||||
(e: 'node-click', node: OriginalTreeData): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TreeProps>(), {
|
|
||||||
showCheckbox: false,
|
|
||||||
edit: false,
|
|
||||||
accordion: false,
|
|
||||||
onlyIconControl: false,
|
|
||||||
disabled: false,
|
|
||||||
showLine: true,
|
|
||||||
replaceFields: () => {
|
|
||||||
return {
|
|
||||||
id: 'id',
|
|
||||||
children: 'children',
|
|
||||||
title: 'title',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<TreeEmits>()
|
|
||||||
|
|
||||||
const className = computed(() => {
|
|
||||||
return {
|
|
||||||
'layui-tree': true,
|
|
||||||
'layui-form': props.showCheckbox,
|
|
||||||
'layui-tree-line': props.showLine,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { tree, nodeList } = useTree(props, emit)
|
|
||||||
|
|
||||||
function handleClick(node: TreeData) {
|
|
||||||
const originNode = tree.getOriginData(node.id)
|
|
||||||
emit('node-click', originNode)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div :class="className">
|
|
||||||
<tree-node
|
|
||||||
:tree="tree"
|
|
||||||
:node-list="nodeList"
|
|
||||||
:show-checkbox="showCheckbox"
|
|
||||||
:show-line="showLine"
|
|
||||||
:only-icon-control="onlyIconControl"
|
|
||||||
@node-click="handleClick"
|
|
||||||
></tree-node>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,34 +0,0 @@
|
|||||||
export type StringFn = () => string
|
|
||||||
export type StringOrNumber = string | number
|
|
||||||
export type KeysType = (number | string)[]
|
|
||||||
export type EditType = boolean | ('add' | 'update' | 'delete')
|
|
||||||
|
|
||||||
export interface OriginalTreeData {
|
|
||||||
title: StringFn | string
|
|
||||||
id: StringOrNumber
|
|
||||||
field: StringFn | string
|
|
||||||
children?: OriginalTreeData[]
|
|
||||||
disabled?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TreeProps {
|
|
||||||
checkedKeys?: KeysType
|
|
||||||
expandKeys?: KeysType
|
|
||||||
data: OriginalTreeData
|
|
||||||
showCheckbox?: boolean
|
|
||||||
edit?: EditType
|
|
||||||
accordion?: boolean
|
|
||||||
onlyIconControl?: boolean
|
|
||||||
showLine?: boolean
|
|
||||||
replaceFields?: {
|
|
||||||
id?: string
|
|
||||||
children?: string
|
|
||||||
title?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TreeEmits {
|
|
||||||
(e: 'update:checkedKeys', keys: KeysType): void
|
|
||||||
(e: 'update:expandKeys', keys: KeysType): void
|
|
||||||
(e: 'node-click', node: OriginalTreeData, event: Event): void
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
import {
|
|
||||||
OriginalTreeData,
|
|
||||||
StringFn,
|
|
||||||
StringOrNumber,
|
|
||||||
} from '/@src/module/tree/new-tree/tree.type'
|
|
||||||
import { computed, ref, Ref } from 'vue'
|
|
||||||
import { Nullable } from '/@src/module/type'
|
|
||||||
|
|
||||||
export interface TreeConfig {
|
|
||||||
disabled: boolean
|
|
||||||
showLine: boolean
|
|
||||||
showCheckbox: boolean
|
|
||||||
spread: boolean
|
|
||||||
checkedKeys: StringOrNumber[]
|
|
||||||
spreadKeys: StringOrNumber[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useTreeData = (
|
|
||||||
treeConfig: TreeConfig,
|
|
||||||
origin: OriginalTreeData | OriginalTreeData[]
|
|
||||||
) => {
|
|
||||||
const tree = computed(() => {
|
|
||||||
const initTree = initialTree(origin)
|
|
||||||
const treeNodes = getTreeNodes(initTree, treeConfig, '')
|
|
||||||
return treeNodes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTreeNodes(
|
|
||||||
origin: OriginalTreeData[],
|
|
||||||
config: TreeConfig,
|
|
||||||
parentId = ''
|
|
||||||
) {
|
|
||||||
const len = origin.length
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const node = createTreeNode(config, origin[i], i, len, parentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTreeNode(
|
|
||||||
config: TreeConfig,
|
|
||||||
current: OriginalTreeData,
|
|
||||||
i: number,
|
|
||||||
len: number,
|
|
||||||
parentId: string
|
|
||||||
) {
|
|
||||||
const treeNode = new TreeNode(current)
|
|
||||||
const { disabled, spread } = config
|
|
||||||
treeNode.isDisabled.value = disabled
|
|
||||||
treeNode.isLeaf.value = spread
|
|
||||||
if (i < len - 1) {
|
|
||||||
treeNode.hasNextSibling = true
|
|
||||||
}
|
|
||||||
treeNode.parentId = parentId
|
|
||||||
}
|
|
||||||
|
|
||||||
class TreeNode {
|
|
||||||
id: StringOrNumber
|
|
||||||
parentId: StringOrNumber
|
|
||||||
title: StringFn | string
|
|
||||||
children: Nullable<TreeNode[]>
|
|
||||||
parentNode: Nullable<TreeNode>
|
|
||||||
isDisabled: Ref<boolean>
|
|
||||||
isLeaf: Ref<boolean>
|
|
||||||
isChecked: Ref<boolean>
|
|
||||||
hasNextSibling: boolean
|
|
||||||
constructor(options: OriginalTreeData) {
|
|
||||||
this.id = options.id
|
|
||||||
this.title = options.title
|
|
||||||
this.children = []
|
|
||||||
this.parentId = ''
|
|
||||||
this.parentNode = null
|
|
||||||
this.isDisabled = ref(false)
|
|
||||||
this.isLeaf = ref(false)
|
|
||||||
this.isChecked = ref(false)
|
|
||||||
this.hasNextSibling = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init tree.
|
|
||||||
* if origin source is object then return Array.of(originSource)
|
|
||||||
* if array, return
|
|
||||||
* @param originData
|
|
||||||
*/
|
|
||||||
function initialTree(originData: OriginalTreeData | OriginalTreeData[]): any {
|
|
||||||
let treeNodes = []
|
|
||||||
if (!Array.isArray(originData)) {
|
|
||||||
treeNodes.push(originData)
|
|
||||||
} else {
|
|
||||||
treeNodes = Array.of(originData)
|
|
||||||
}
|
|
||||||
return treeNodes
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
OriginalTreeData,
|
OriginalTreeData,
|
||||||
StringOrNumber,
|
StringOrNumber,
|
||||||
} from '/@src/module/tree/new-tree/tree.type'
|
} from '/@src/module/tree/tree.type'
|
||||||
import { Nullable } from '/@src/module/type'
|
import { Nullable } from '/@src/module/type'
|
||||||
import { Ref, ref } from 'vue'
|
import { Ref, ref } from 'vue'
|
||||||
|
|
@ -1,193 +1,34 @@
|
|||||||
/**
|
export type StringFn = () => string
|
||||||
* defineProps 暂不支持外部导入的复杂类型,这里备用后续迁移。
|
export type StringOrNumber = string | number
|
||||||
* @see https://github.com/vuejs/vue-next/issues/4294
|
export type KeysType = (number | string)[]
|
||||||
* 暂时 在index.vue内部单独实现一份
|
export type EditType = boolean | ('add' | 'update' | 'delete')
|
||||||
*/
|
|
||||||
import {
|
|
||||||
ComputedRef,
|
|
||||||
Ref,
|
|
||||||
UnwrapRef,
|
|
||||||
VNode,
|
|
||||||
VNodeChild,
|
|
||||||
WritableComputedRef,
|
|
||||||
} from 'vue'
|
|
||||||
import { Nullable, Recordable } from '/@src/module/type'
|
|
||||||
|
|
||||||
type EditAction = 'add' | 'update' | 'del'
|
export interface OriginalTreeData {
|
||||||
|
title: StringFn | string
|
||||||
type EditType = boolean | EditAction[]
|
id: StringOrNumber
|
||||||
|
field: StringFn | string
|
||||||
export declare interface TreeData {
|
children?: OriginalTreeData[]
|
||||||
/**
|
|
||||||
* 节点唯一索引值,用于对指定节点进行各类操作
|
|
||||||
*/
|
|
||||||
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
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface TreeProps {
|
export interface TreeProps {
|
||||||
/**
|
checkedKeys?: KeysType
|
||||||
* 指定唯一的id
|
expandKeys?: KeysType
|
||||||
*/
|
data: OriginalTreeData
|
||||||
key?: string
|
|
||||||
/**
|
|
||||||
* 选中的节点
|
|
||||||
*/
|
|
||||||
checkedKeys?: NonNullable<(string | number)[]>
|
|
||||||
/**
|
|
||||||
* 展开的节点
|
|
||||||
*/
|
|
||||||
spreadKeys?: (string | number)[]
|
|
||||||
/**
|
|
||||||
* 数据源
|
|
||||||
*/
|
|
||||||
data?: TreeData[]
|
|
||||||
/**
|
|
||||||
* 是否显示复选框 默认 false
|
|
||||||
*/
|
|
||||||
showCheckbox?: boolean
|
showCheckbox?: boolean
|
||||||
/**
|
|
||||||
* 是否开启节点的操作图标。默认 false。
|
|
||||||
若为 true,则默认显示“改删”图标
|
|
||||||
若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有:add、update、del,如:
|
|
||||||
edit: ['add', 'update', 'del']
|
|
||||||
*/
|
|
||||||
edit?: EditType
|
edit?: EditType
|
||||||
/**
|
|
||||||
* 是否开启手风琴模式,默认 false
|
|
||||||
*/
|
|
||||||
accordion?: boolean
|
accordion?: boolean
|
||||||
/**
|
|
||||||
* 是否仅允许节点左侧图标控制展开收缩。默认 false(即点击节点本身也可控制)。若为 true,则只能通过节点左侧图标来展开收缩
|
|
||||||
*/
|
|
||||||
onlyIconControl?: boolean
|
onlyIconControl?: boolean
|
||||||
/**
|
|
||||||
* 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式
|
|
||||||
* 废弃:能过事件用户自行控制
|
|
||||||
*/
|
|
||||||
isJump?: boolean
|
|
||||||
/**
|
|
||||||
* 是否开启连接线。默认 true,若设为 false,则节点左侧出现三角图标。
|
|
||||||
*/
|
|
||||||
showLine?: boolean
|
showLine?: boolean
|
||||||
/**
|
replaceFields?: {
|
||||||
* 自定义各类默认文本,目前支持以下设定:
|
id?: string
|
||||||
*/
|
children?: string
|
||||||
text?: {
|
title?: string
|
||||||
/**
|
|
||||||
* 节点默认名称
|
|
||||||
*/
|
|
||||||
defaultNodeName?: () => string | string
|
|
||||||
/**
|
|
||||||
* 数据为空时的提示文本
|
|
||||||
*/
|
|
||||||
none?: (() => string) | string | VNode | Element
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmitData {
|
export interface TreeEmits {
|
||||||
/**
|
(e: 'update:checkedKeys', keys: KeysType): void
|
||||||
* 当前点击的节点数据
|
(e: 'update:expandKeys', keys: KeysType): void
|
||||||
*/
|
(e: 'node-click', node: OriginalTreeData, event: Event): void
|
||||||
data: TreeData
|
|
||||||
/**
|
|
||||||
* 节点的展开状态
|
|
||||||
*/
|
|
||||||
state?: 'open' | 'close' | 'normal'
|
|
||||||
/**
|
|
||||||
* 当前节点元素
|
|
||||||
*/
|
|
||||||
elem?: Element | VNode | VNodeChild
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface TreeEmits {
|
|
||||||
/**
|
|
||||||
* 节点被点击后触发
|
|
||||||
* @param e 事件
|
|
||||||
* @param treeNode
|
|
||||||
*/
|
|
||||||
(e: 'node-click', treeNode: EmitData): void
|
|
||||||
/**
|
|
||||||
* 点击复选框时触发
|
|
||||||
* @param e 事件
|
|
||||||
* @param treeNode
|
|
||||||
*/
|
|
||||||
(e: 'node-check', treeNode: EmitData): void
|
|
||||||
/**
|
|
||||||
* 操作节点的回调
|
|
||||||
* @param e 事件
|
|
||||||
* @param treeNode
|
|
||||||
*/
|
|
||||||
// (e: 'on-operate', treeNode: EmitData): void
|
|
||||||
/**
|
|
||||||
* update:spreadKeys
|
|
||||||
* @param e
|
|
||||||
* @param spreadKeys
|
|
||||||
*/
|
|
||||||
(e: 'update:spreadKeys', spreadKeys: (string | number)[]): void
|
|
||||||
(e: 'update:checkedKeys', checkedKeys: (string | number)[]): void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TreeExpose {
|
|
||||||
/**
|
|
||||||
* 返回选中的节点数据
|
|
||||||
* 一般来说,v-model就够使了,但不排除需要直接获取其值的时候,此时通过该方法获取
|
|
||||||
* eg: treeRef.getChecked()
|
|
||||||
*/
|
|
||||||
getChecked: () => TreeData
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置节点勾选 setChecked 变为v-model控制
|
|
||||||
// 实例重载 reload 变为v-model控制
|
|
||||||
|
|
||||||
export interface TreeNode extends TreeData {
|
|
||||||
parentId: string | number
|
|
||||||
children: TreeNode[]
|
|
||||||
_parentNode?: Nullable<TreeNode>
|
|
||||||
_nextSibling?: Nullable<TreeNode>
|
|
||||||
_expand?: boolean
|
|
||||||
_checked?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/** hook type **/
|
|
||||||
export type UseTreeData = (
|
|
||||||
props: TreeProps,
|
|
||||||
emit: TreeEmits
|
|
||||||
) => {
|
|
||||||
innerTreeData: Ref<UnwrapRef<TreeNode[]>>
|
|
||||||
checkedKeys: WritableComputedRef<(string | number)[]>
|
|
||||||
spreadKeys: WritableComputedRef<(string | number)[]>
|
|
||||||
treeWrapperClass: ComputedRef<Recordable>
|
|
||||||
updateInnerTreeData: (treeData: TreeData[], node: TreeData) => void
|
|
||||||
updateCheckedByNode: (treeNode: TreeNode) => void
|
|
||||||
}
|
}
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
import { TreeData, TreeNode } from '/@src/module/tree/tree.type'
|
|
||||||
import { Nullable } from '/@src/module/type'
|
|
||||||
import { WritableComputedRef } from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加父级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
|
|
||||||
* @param checkedKeys
|
|
||||||
*/
|
|
||||||
export const initialTreeData = (
|
|
||||||
data: TreeData[],
|
|
||||||
checkedKeys: WritableComputedRef<(string | number)[]>
|
|
||||||
): TreeNode[] => {
|
|
||||||
const innerTree = generatorTreeData(data, '')
|
|
||||||
setNextSiblings(innerTree)
|
|
||||||
setParentNode(innerTree)
|
|
||||||
patchCheckedKeys(innerTree, checkedKeys)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取点击的原数据的节点
|
|
||||||
* @param data
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
export const getEmitNode = (
|
|
||||||
data: TreeData[],
|
|
||||||
node: TreeNode
|
|
||||||
): Nullable<TreeData> => {
|
|
||||||
let item: Nullable<TreeData> = null
|
|
||||||
const len = data.length
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
if (data[i].id === node.id) {
|
|
||||||
item = data[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (data[i].children && data[i].children.length > 0) {
|
|
||||||
item = getEmitNode(data[i].children, node)
|
|
||||||
if (item) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checkedKes分发到node
|
|
||||||
* @param tree
|
|
||||||
* @param checkedKeys
|
|
||||||
* @param checked
|
|
||||||
*/
|
|
||||||
export const patchCheckedKeys = (
|
|
||||||
tree: TreeNode[],
|
|
||||||
checkedKeys: WritableComputedRef<(string | number)[]>,
|
|
||||||
checked = false
|
|
||||||
): void => {
|
|
||||||
if (!checkedKeys.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const len = tree.length
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
tree[i]._checked = checked
|
|
||||||
const node = tree[i]
|
|
||||||
// 该节点是checked
|
|
||||||
if (checkedKeys.value.indexOf(node.id) > -1) {
|
|
||||||
node._checked = true
|
|
||||||
if (node.children && node.children.length > 0) {
|
|
||||||
patchCheckedKeys(node.children, checkedKeys, true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (node.children && node.children.length > 0) {
|
|
||||||
patchCheckedKeys(node.children, checkedKeys, false)
|
|
||||||
// 判断children是否为都选中,如果是都选中的情况下,父组件也得是选中
|
|
||||||
const allChildrenChecked = node.children.every((it) => it._checked)
|
|
||||||
if (allChildrenChecked) {
|
|
||||||
node._checked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateChildren(data: TreeNode[], flag: boolean) {
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
data[i]._checked = flag
|
|
||||||
if (data[i].children && data[i].children.length > 0) {
|
|
||||||
updateChildren(data[i].children, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新树
|
|
||||||
* @param data
|
|
||||||
* @param clickNode
|
|
||||||
* @param parentNode
|
|
||||||
*/
|
|
||||||
export function updateInnerTreeDataChecked(
|
|
||||||
data: TreeNode[],
|
|
||||||
clickNode: TreeNode,
|
|
||||||
parentNode?: TreeNode
|
|
||||||
) {
|
|
||||||
const len = data.length
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const currentNode = data[i]
|
|
||||||
// 找到当前更新的节点
|
|
||||||
if (currentNode.id === clickNode.id) {
|
|
||||||
// 如果当前节点有子节点,更新子节点
|
|
||||||
if (currentNode.children && currentNode.children.length > 0) {
|
|
||||||
updateChildren(data[i].children, currentNode._checked!)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (currentNode.children && currentNode.children.length > 0) {
|
|
||||||
updateInnerTreeDataChecked(currentNode.children, clickNode, currentNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 当前节点有选中,父节点一定选中
|
|
||||||
if (currentNode.children && currentNode.children.length > 0) {
|
|
||||||
currentNode._checked = currentNode.children.some((it) => it._checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCheckedKeys(tree: TreeNode[]): (string | number)[] {
|
|
||||||
let keys: (string | number)[] = []
|
|
||||||
const len = tree.length
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const current = tree[i]
|
|
||||||
if (current._checked) {
|
|
||||||
keys.push(current.id)
|
|
||||||
if (current.children && current.children.length > 0) {
|
|
||||||
keys = [...keys, ...getCheckedKeys(current.children)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import { TreeEmits, TreeProps } from '/@src/module/tree/new-tree/tree.type'
|
import { TreeEmits, TreeProps } from '/@src/module/tree/tree.type'
|
||||||
import { computed, ComputedRef, watch } from 'vue'
|
import { computed, ComputedRef, watch } from 'vue'
|
||||||
import { Tree, TreeData } from '/@src/module/tree/new-tree/tree'
|
import { Tree, TreeData } from '/@src/module/tree/tree'
|
||||||
|
|
||||||
export declare type UseTree = (
|
export declare type UseTree = (
|
||||||
props: TreeProps,
|
props: TreeProps,
|
@ -1,102 +0,0 @@
|
|||||||
import { TreeData, TreeNode, UseTreeData } from '/@src/module/tree/tree.type'
|
|
||||||
import { computed, ref, unref, watch } from 'vue'
|
|
||||||
import {
|
|
||||||
getCheckedKeys,
|
|
||||||
getTreeSpreadKeys,
|
|
||||||
initialTreeData,
|
|
||||||
updateInnerTreeDataChecked,
|
|
||||||
} 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: (string | number)[]) => {
|
|
||||||
emit('update:spreadKeys', value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const checkedKeys = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.checkedKeys!
|
|
||||||
},
|
|
||||||
set: (value: (string | number)[]) => {
|
|
||||||
emit('update:checkedKeys', value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染的data
|
|
||||||
*/
|
|
||||||
const innerTreeData = ref<TreeNode[]>([])
|
|
||||||
watch(
|
|
||||||
() => props.data,
|
|
||||||
(treeData) => {
|
|
||||||
if (treeData) {
|
|
||||||
innerTreeData.value = initialTreeData(treeData, checkedKeys)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* watch innerTreeDate to emit checkedKeys
|
|
||||||
* @param treeData
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
watch(
|
|
||||||
innerTreeData,
|
|
||||||
(tree) => {
|
|
||||||
const emitCheckedKeys = getCheckedKeys(tree)
|
|
||||||
checkedKeys.value = emitCheckedKeys
|
|
||||||
},
|
|
||||||
{ 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,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新checked状态到node中
|
|
||||||
* @param node
|
|
||||||
*/
|
|
||||||
function updateCheckedByNode(node: TreeNode) {
|
|
||||||
updateInnerTreeDataChecked(innerTreeData.value, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
spreadKeys,
|
|
||||||
checkedKeys,
|
|
||||||
innerTreeData,
|
|
||||||
updateInnerTreeData,
|
|
||||||
treeWrapperClass,
|
|
||||||
updateCheckedByNode,
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user