refactor(tree): 移注第一版tree的实现

This commit is contained in:
落小梅 2021-10-21 14:45:21 +08:00
parent b4477f049b
commit 32252dba94
18 changed files with 189 additions and 1114 deletions

View 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 | 启用搜索 | -- |

View File

@ -328,6 +328,12 @@ export default {
subTitle: 'select',
path: '/zh-CN/components/select',
},
{
id: 40,
title: '颜色选择器',
subTitle: 'colorPicker',
path: '/zh-CN/components/colorPicker',
},
]
const selected = ref(1)

View File

@ -219,6 +219,11 @@ const zhCN = [
component: () => import('../../docs/zh-CN/components/select.md'),
meta: { title: '下拉选择' },
},
{
path: '/zh-CN/components/colorPicker',
component: () => import('../../docs/zh-CN/components/colorPicker.md'),
meta: { title: '颜色选择器' },
},
],
},
]

View File

@ -57,6 +57,7 @@ import LayCheckboxGroup from './module/checkboxGroup/index'
import LaySlider from './module/slider/index'
import LayCarousel from './module/carousel/index'
import LayCarouselItem from './module/carouselItem/index'
import LayColorPicker from './module/colorPicker/index'
const components: Record<string, IDefineComponent> = {
LayRadio,
@ -113,7 +114,8 @@ const components: Record<string, IDefineComponent> = {
LayCheckboxGroup,
LaySlider,
LayCarousel,
LayCarouselItem
LayCarouselItem,
LayColorPicker,
}
const install = (app: App, options?: InstallOptions): void => {
@ -182,6 +184,7 @@ export {
LaySlider,
LayCarousel,
LayCarouselItem,
LayColorPicker,
install,
}

View File

@ -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

View File

@ -0,0 +1,14 @@
<script lang="ts">
export default {
name: 'LayColorPicker'
}
</script>
<script lang="ts" setup>
</script>
<template>
<pre>
// todo =_=
</pre>
</template>

View File

@ -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>

View File

@ -5,13 +5,13 @@ export default {
</script>
<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 LayIcon from '../../icon'
import LayCheckbox from '../../checkbox'
import { nextTick, Ref } from 'vue'
import { Tree } from '/@src/module/tree/new-tree/tree'
import LayIcon from '../icon'
import LayCheckbox from '../checkbox'
import { Ref } from 'vue'
import { Tree } from '/@src/module/tree/tree'
type CustomKey = string | number
type CustomString = (() => string) | string
@ -134,7 +134,9 @@ function handleTitleClick(node: TreeData) {
</span>
</div>
</div>
<transition>
<transition
name="move"
>
<div
v-if="node.isLeaf.value"
class="layui-tree-pack layui-tree-showLine"
@ -153,4 +155,5 @@ function handleTitleClick(node: TreeData) {
</div>
</template>
<style scoped></style>
<style scoped>
</style>

View File

@ -1,6 +1,5 @@
import type { App } from 'vue'
// import Component from './index.vue'
import Component from './new-tree/index.vue'
import Component from './index.vue'
import type { IDefineComponent } from '../type/index'
Component.install = (app: App) => {

View File

@ -1,202 +1,99 @@
<script setup lang="ts">
import { VNode, VNodeChild } from 'vue'
import TreeEntity from './TreeEntity.vue'
import { useTreeData } from '/@src/module/tree/useTreeData'
import { TreeNode } from '/@src/module/tree/tree.type'
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则默认显示改删图标
若为 数组则可自由配置操作图标的显示状态和顺序目前支持的操作图标有addupdatedel
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>
<!--
* @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/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>
<template>
<div :class="treeWrapperClass">
<TreeEntity
v-for="(node, index) in innerTreeData"
:key="node.id || index"
:node="node"
<div :class="className">
<tree-node
:tree="tree"
:node-list="nodeList"
:show-checkbox="showCheckbox"
:update-checked-by-node="updateCheckedByNode"
@node-click="handleNodeClick"
/>
:show-line="showLine"
:only-icon-control="onlyIconControl"
@node-click="handleClick"
></tree-node>
</div>
</template>
<style scoped></style>

View File

@ -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>

View File

@ -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
}

View File

@ -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
}

View File

@ -1,7 +1,7 @@
import {
OriginalTreeData,
StringOrNumber,
} from '/@src/module/tree/new-tree/tree.type'
} from '/@src/module/tree/tree.type'
import { Nullable } from '/@src/module/type'
import { Ref, ref } from 'vue'

View File

@ -1,193 +1,34 @@
/**
* defineProps
* @see https://github.com/vuejs/vue-next/issues/4294
* index.vue内部单独实现一份
*/
import {
ComputedRef,
Ref,
UnwrapRef,
VNode,
VNodeChild,
WritableComputedRef,
} from 'vue'
import { Nullable, Recordable } from '/@src/module/type'
export type StringFn = () => string
export type StringOrNumber = string | number
export type KeysType = (number | string)[]
export type EditType = boolean | ('add' | 'update' | 'delete')
type EditAction = 'add' | 'update' | 'del'
type EditType = boolean | EditAction[]
export declare 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
*/
export interface OriginalTreeData {
title: StringFn | string
id: StringOrNumber
field: StringFn | string
children?: OriginalTreeData[]
disabled?: boolean
}
export declare interface TreeProps {
/**
* id
*/
key?: string
/**
*
*/
checkedKeys?: NonNullable<(string | number)[]>
/**
*
*/
spreadKeys?: (string | number)[]
/**
*
*/
data?: TreeData[]
/**
* false
*/
export interface TreeProps {
checkedKeys?: KeysType
expandKeys?: KeysType
data: OriginalTreeData
showCheckbox?: boolean
/**
* false
true
addupdatedel
edit: ['add', 'update', 'del']
*/
edit?: EditType
/**
* false
*/
accordion?: boolean
/**
* false true
*/
onlyIconControl?: boolean
/**
* false link url
*
*/
isJump?: boolean
/**
* 线 true false
*/
showLine?: boolean
/**
*
*/
text?: {
/**
*
*/
defaultNodeName?: () => string | string
/**
*
*/
none?: (() => string) | string | VNode | Element
replaceFields?: {
id?: string
children?: string
title?: string
}
}
export interface EmitData {
/**
*
*/
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
export interface TreeEmits {
(e: 'update:checkedKeys', keys: KeysType): void
(e: 'update:expandKeys', keys: KeysType): void
(e: 'node-click', node: OriginalTreeData, event: Event): void
}

View File

@ -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
}
/**
* nextSiblingnull
* @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
}

View File

@ -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 { Tree, TreeData } from '/@src/module/tree/new-tree/tree'
import { Tree, TreeData } from '/@src/module/tree/tree'
export declare type UseTree = (
props: TreeProps,

View File

@ -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,
}
}