From a25430ed48e644abd744a3496460411b8eb0287b Mon Sep 17 00:00:00 2001 From: gxuud Date: Mon, 18 Oct 2021 03:20:47 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=8F=AF=E5=8B=BE=E9=80=89?= =?UTF-8?q?=E6=A0=91=E3=80=81=E6=8E=A7=E5=88=B6=E7=88=B6=E5=AD=90check?= =?UTF-8?q?=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/tree/src/config.ts | 9 ++ devui/tree/src/tree-types.ts | 19 +++ devui/tree/src/tree.tsx | 136 +++++++++++++++++++- docs/components/tree/index.md | 233 ++++++++++++++++++++++++++++++++++ 4 files changed, 392 insertions(+), 5 deletions(-) create mode 100644 devui/tree/src/config.ts diff --git a/devui/tree/src/config.ts b/devui/tree/src/config.ts new file mode 100644 index 00000000..dbd834d3 --- /dev/null +++ b/devui/tree/src/config.ts @@ -0,0 +1,9 @@ +export const CHECK_CONFIG = { + none: {}, + half: { + halfchecked: true + }, + select: { + checked: true, + }, +} diff --git a/devui/tree/src/tree-types.ts b/devui/tree/src/tree-types.ts index cec8a0d9..ba522219 100644 --- a/devui/tree/src/tree-types.ts +++ b/devui/tree/src/tree-types.ts @@ -9,14 +9,33 @@ export interface TreeItem { children?: TreeData [key: string]: any } +export interface SelectType { + [key: string]: 'none' | 'half' | 'select' +} + +export interface ReverseTree { + id?: string + children?: string[] + parent?: ReverseTree +} export type TreeData = Array +export type CheckableRelationType = 'upward' | 'upward' | 'both' | 'none' + export const treeProps = { data: { type: Array as PropType, required: true, default: () => [], + }, + checkable: { + type: Boolean, + default: false + }, + checkableRelation: { + type: String as () => CheckableRelationType, + default: 'none', } } as const diff --git a/devui/tree/src/tree.tsx b/devui/tree/src/tree.tsx index 0ea203d1..8ea449cf 100644 --- a/devui/tree/src/tree.tsx +++ b/devui/tree/src/tree.tsx @@ -1,7 +1,9 @@ -import { defineComponent, toRefs, provide } from 'vue' -import { treeProps, TreeProps, TreeItem, TreeRootType } from './tree-types' +import { defineComponent, toRefs, provide, ref, unref } from 'vue' +import { treeProps, TreeProps, TreeItem, TreeRootType, SelectType, ReverseTree } from './tree-types' +import { CHECK_CONFIG } from './config'; import { flatten, precheckTree } from './util' import Loading from '../../loading/src/service' +import Checkbox from '../../checkbox/src/checkbox' import useToggle from './composables/use-toggle' import useMergeNode from './composables/use-merge-node' import useHighlightNode from './composables/use-highlight' @@ -14,10 +16,11 @@ import './tree.scss' export default defineComponent({ name: 'DTree', props: treeProps, - emits: [], + emits: ['nodeSelected'], setup(props: TreeProps, ctx) { - const { data } = toRefs({ ...props, data: precheckTree(props.data) }) + const { data, checkable, checkableRelation: cbr } = toRefs({ ...props, data: precheckTree(props.data) }) const flatData = flatten(data.value) + const selected = ref({}) const { mergeData } = useMergeNode(data.value) const { openedData, toggle } = useToggle(mergeData.value) @@ -26,7 +29,128 @@ export default defineComponent({ provide('treeRoot', { ctx, props }); const Indent = () => { - return + return + } + + const findPedigreeById = (id: string) => { + const treeData = unref(data) + let parentLevel: ReverseTree = {} + let childLevel: string[] = [] + let target = undefined + const ergodic = (curr: any[], parentNode: ReverseTree) => { + curr.every(({ children, id: itemId }) => { + if (target) { + return false + } + if (itemId === id) { + parentLevel = parentNode + childLevel = Array.isArray(children) + ? flatten(children).map(({ id: key }) => key) + : []; + target = itemId + return false + } + if (Array.isArray(children)) { + ergodic(children, { + id: itemId, + children: children.map(({ id: key }) => key), + parent: parentNode, + }); + } + return true + }) + } + ergodic(treeData, {}) + return { + parentLevel, + childLevel, + } + } + + const generateParentObject = (parentLevel: ReverseTree, isSelected: boolean, checkId: string): SelectType => { + const state: SelectType = {} + const currentSelected = unref(selected) + const ergodic = (currObj: ReverseTree, isOverflow = false) => { + const { id, children, parent } = currObj; + if (!parent) { + return + } + if (isSelected) { + const optional = children.filter( + (item) => !currentSelected[item] || currentSelected[item] === 'none' + ); + if (optional.length <= 1) { + if (optional[0] === checkId) { + state[id] = 'select' + } else if (isOverflow) { + state[id] = 'half' + } + } else { + state[id] = 'half' + } + ergodic(parent, state[id] === 'select'); + } else { + const optional = children.filter( + (item) => currentSelected[item] && currentSelected[item] !== 'none' + ); + if (optional.length <= 1) { + if (optional[0] === checkId || isOverflow) { + state[id] = 'none' + } + } else { + state[id] = 'half' + } + ergodic(parent, state[id] === 'none') + } + } + ergodic(parentLevel) + return state + } + + const onNodeClick = (item: TreeItem) => { + const { id } = item + let currentSelected = Object.assign({}, unref(selected)) + const isSelected = currentSelected[id] === 'none' || !currentSelected[id]; + if (cbr.value === 'none') { + currentSelected = Object.assign(currentSelected, { [id]: isSelected ? 'select' : 'none' }) + } else if (cbr.value === 'both') { + const { parentLevel, childLevel } = findPedigreeById(id); + currentSelected = Object.assign( + currentSelected, + Object.fromEntries( + childLevel.map((key) => [ + key, + isSelected ? 'select' : 'none', + ]) + ), + generateParentObject(parentLevel, isSelected, id), + { [id]: isSelected ? 'select' : 'none' } + ) + } else if (cbr.value === 'upward') { + const { parentLevel } = findPedigreeById(id); + currentSelected = Object.assign( + currentSelected, + generateParentObject(parentLevel, isSelected, id), + { [id]: isSelected ? 'select' : 'none' } + ) + } else if (cbr.value === 'downward') { + const { childLevel } = findPedigreeById(id); + currentSelected = Object.assign( + currentSelected, + Object.fromEntries( + childLevel.map((key) => [ + key, + isSelected ? 'select' : 'none', + ]) + ), + { [id]: isSelected ? 'select' : 'none' } + ) + } + selected.value = currentSelected + const currentSelectedItem = flatData.filter( + ({ id }) => currentSelected[id] && currentSelected[id] !== 'none' + ) + ctx.emit('nodeSelected', currentSelectedItem) } const renderNode = (item: TreeItem) => { const { id = '', label, disabled, open, isParent, level, children } = item @@ -74,6 +198,7 @@ export default defineComponent({ : ) } + const checkState = CHECK_CONFIG[selected.value[id] ?? 'none'] return (
{renderNodeWithIcon(item)} + {checkable.value && onNodeClick(item)} disabled={disabled} {...checkState} />} {item.isParent &&
}
diff --git a/docs/components/tree/index.md b/docs/components/tree/index.md index 3599119a..3251f147 100644 --- a/docs/components/tree/index.md +++ b/docs/components/tree/index.md @@ -304,6 +304,7 @@ export default defineComponent({