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', | ||||
|         path: '/zh-CN/components/select', | ||||
|       }, | ||||
|       { | ||||
|         id: 40, | ||||
|         title: '颜色选择器', | ||||
|         subTitle: 'colorPicker', | ||||
|         path: '/zh-CN/components/colorPicker', | ||||
|       }, | ||||
|     ] | ||||
| 
 | ||||
|     const selected = ref(1) | ||||
|  | ||||
| @ -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: '颜色选择器' }, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| @ -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, | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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 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> | ||||
| @ -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) => { | ||||
|  | ||||
| @ -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,则默认显示“改删”图标 | ||||
|    若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有: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> | ||||
| <!-- | ||||
|  * @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> | ||||
|  | ||||
| @ -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 { | ||||
|   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' | ||||
| 
 | ||||
| @ -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,则默认显示“改删”图标 | ||||
|    若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有: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 | ||||
|   /** | ||||
|    * 自定义各类默认文本,目前支持以下设定: | ||||
|    */ | ||||
|   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 | ||||
| } | ||||
|  | ||||
| @ -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 { Tree, TreeData } from '/@src/module/tree/new-tree/tree' | ||||
| import { Tree, TreeData } from '/@src/module/tree/tree' | ||||
| 
 | ||||
| export declare type UseTree = ( | ||||
|   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