From abcf257be4554aa66708f4103059a25d287712a7 Mon Sep 17 00:00:00 2001
From: WumaCoder <1829913225@qq.com>
Date: Sat, 16 Jan 2021 00:01:39 +0800
Subject: [PATCH] refactor(tree): Realize tree structured data display and
dynamic update
---
.../element3/packages/tree/NodeContent.vue | 28 -
packages/element3/packages/tree/Tree.vue | 372 -------------
packages/element3/packages/tree/TreeNode.vue | 142 -----
.../tree/__tests__/entity/tree-node.spec.js | 238 ---------
.../tree/__tests__/entity/tree.spec.js | 69 ---
.../packages/tree/__tests__/libs/util.spec.js | 203 -------
.../packages/tree/__tests__/tree.spec.js | 209 --------
.../element3/packages/tree/entity/Action.js | 25 -
.../element3/packages/tree/entity/Tree.js | 203 -------
.../element3/packages/tree/entity/TreeNode.js | 499 ------------------
packages/element3/packages/tree/libs/util.js | 119 -----
.../tree => src/components/Tree}/index.js | 4 +-
.../element3/src/components/Tree/src/Tree.vue | 91 ++++
.../src/components/Tree/src/TreeNode.vue | 63 +++
.../components/Tree/src/TreeNodeContent.vue | 28 +
.../components/Tree/src/entity/TreeNode.ts | 187 +++++++
.../element3/src/components/Tree/src/props.ts | 26 +
.../element3/src/components/Tree/src/types.ts | 38 ++
.../src/components/Tree/src/utils/effect.ts | 40 ++
.../src/components/Tree/src/utils/useTools.ts | 14 +
.../src/components/Tree/tests/Tree.spec.ts | 128 +++++
.../components/Tree/tests/TreeNode.spec.ts | 25 +
.../Tree/tests/TreeNodeContent.spec.ts | 16 +
.../Tree/tests/entity/TreeNode.spec.ts | 88 +++
.../src/components/Tree/tests/props.spec.ts | 3 +
.../Tree/tests/utils/effect.spec.ts | 32 ++
.../Tree/tests/utils/useTools.spec.ts | 27 +
packages/element3/src/index.js | 2 +-
packages/website/src/i18n/component.json | 58 +-
packages/website/src/play/index.vue | 62 ++-
packages/website/src/route/nav.config.json | 2 +-
31 files changed, 887 insertions(+), 2154 deletions(-)
delete mode 100644 packages/element3/packages/tree/NodeContent.vue
delete mode 100644 packages/element3/packages/tree/Tree.vue
delete mode 100644 packages/element3/packages/tree/TreeNode.vue
delete mode 100644 packages/element3/packages/tree/__tests__/entity/tree-node.spec.js
delete mode 100644 packages/element3/packages/tree/__tests__/entity/tree.spec.js
delete mode 100644 packages/element3/packages/tree/__tests__/libs/util.spec.js
delete mode 100644 packages/element3/packages/tree/__tests__/tree.spec.js
delete mode 100644 packages/element3/packages/tree/entity/Action.js
delete mode 100644 packages/element3/packages/tree/entity/Tree.js
delete mode 100644 packages/element3/packages/tree/entity/TreeNode.js
delete mode 100644 packages/element3/packages/tree/libs/util.js
rename packages/element3/{packages/tree => src/components/Tree}/index.js (65%)
create mode 100644 packages/element3/src/components/Tree/src/Tree.vue
create mode 100644 packages/element3/src/components/Tree/src/TreeNode.vue
create mode 100644 packages/element3/src/components/Tree/src/TreeNodeContent.vue
create mode 100644 packages/element3/src/components/Tree/src/entity/TreeNode.ts
create mode 100644 packages/element3/src/components/Tree/src/props.ts
create mode 100644 packages/element3/src/components/Tree/src/types.ts
create mode 100644 packages/element3/src/components/Tree/src/utils/effect.ts
create mode 100644 packages/element3/src/components/Tree/src/utils/useTools.ts
create mode 100644 packages/element3/src/components/Tree/tests/Tree.spec.ts
create mode 100644 packages/element3/src/components/Tree/tests/TreeNode.spec.ts
create mode 100644 packages/element3/src/components/Tree/tests/TreeNodeContent.spec.ts
create mode 100644 packages/element3/src/components/Tree/tests/entity/TreeNode.spec.ts
create mode 100644 packages/element3/src/components/Tree/tests/props.spec.ts
create mode 100644 packages/element3/src/components/Tree/tests/utils/effect.spec.ts
create mode 100644 packages/element3/src/components/Tree/tests/utils/useTools.spec.ts
diff --git a/packages/element3/packages/tree/NodeContent.vue b/packages/element3/packages/tree/NodeContent.vue
deleted file mode 100644
index b71824ca0..000000000
--- a/packages/element3/packages/tree/NodeContent.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-
diff --git a/packages/element3/packages/tree/Tree.vue b/packages/element3/packages/tree/Tree.vue
deleted file mode 100644
index 1b2d7cb17..000000000
--- a/packages/element3/packages/tree/Tree.vue
+++ /dev/null
@@ -1,372 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {{ emptyText }}
-
-
-
-
-
-
diff --git a/packages/element3/packages/tree/TreeNode.vue b/packages/element3/packages/tree/TreeNode.vue
deleted file mode 100644
index 801bcbffa..000000000
--- a/packages/element3/packages/tree/TreeNode.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/element3/packages/tree/__tests__/entity/tree-node.spec.js b/packages/element3/packages/tree/__tests__/entity/tree-node.spec.js
deleted file mode 100644
index 0a3ac5473..000000000
--- a/packages/element3/packages/tree/__tests__/entity/tree-node.spec.js
+++ /dev/null
@@ -1,238 +0,0 @@
-import { TreeNode } from '../../entity/TreeNode'
-
-describe('TreeNode.js', () => {
- it('test init options', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(
- 112,
- 'root-1-2',
- [
- new TreeNode(1121, 'root-1-2-1'),
- new TreeNode(1122, 'root-1-2-2'),
- new TreeNode(1123, 'root-1-2-3')
- ],
- { isChecked: true, isExpanded: true }
- )
- ]),
- new TreeNode(12, 'root-2', [], { isExpanded: true, isChecked: true })
- ])
-
- expect(treeNode.findOne(11).isExpanded).toBeTruthy()
- expect(treeNode.findOne(11).isChecked).toBeTruthy()
- expect(treeNode.findOne(11).isIndeterminate).toBeTruthy()
-
- expect(treeNode.findOne(1122).isChecked).toBeTruthy()
- expect(treeNode.findOne(1122).isIndeterminate).toBeFalsy()
-
- expect(treeNode.isExpanded).toBeTruthy()
- expect(treeNode.isChecked).toBeTruthy()
- expect(treeNode.isIndeterminate).toBeFalsy()
- })
-
- it('test find node', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node12 = treeNode.findOne(12)
-
- expect(node12.label).toBe('root-2')
-
- const nodes = treeNode.findMany('root-1')
-
- expect(nodes).toHaveLength(3)
- })
-
- it('append a node', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- treeNode.appendChild(new TreeNode(13, 'root-3'))
- expect(treeNode.findOne(13)).toBeTruthy()
- })
-
- it('insert a node', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- treeNode.insertChild(1, new TreeNode(10, 'root-0'))
- expect(treeNode.findOne(10)).toBeTruthy()
- })
-
- it('remove a node', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const oldLen = treeNode.childNodes.length
- treeNode.removeChild(1)
- expect(treeNode.childNodes).toHaveLength(oldLen - 1)
- })
-
- it('get root', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node112 = treeNode.findOne(112)
-
- expect(node112.root).toBe(treeNode)
- })
-
- it('set checked', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node111 = treeNode.findOne(111)
- node111.setChecked(true)
- expect(node111.isChecked).toBeTruthy()
- expect(node111.parent.isIndeterminate).toBeTruthy()
- expect(node111.root.isIndeterminate).toBeTruthy()
-
- const node112 = treeNode.findOne(112)
- node112.setChecked(true)
- expect(node112.isChecked).toBeTruthy()
- expect(node112.parent.isChecked).toBeTruthy()
- expect(node112.parent.isIndeterminate).toBeFalsy()
- expect(node112.root.isIndeterminate).toBeTruthy()
- })
-
- it('test expand', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node111 = treeNode.findOne(111)
- node111.expand()
- expect(node111.isExpanded).toBeTruthy()
- expect(node111.root.isExpanded).toBeTruthy()
- })
-
- it('separation a node', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node11 = treeNode.findOne(11)
- node11.separation()
-
- expect(node11.parent).toBe(null)
- expect(treeNode.findOne(11)).toBe(null)
- expect(treeNode.childNodes).toHaveLength(1)
- expect(node11.childNodes).toHaveLength(2)
- })
-
- it('move a node', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node11 = treeNode.findOne(11)
-
- expect(node11.move(treeNode.findOne(12), 'bottom')).toBeTruthy()
- expect(node11.move(treeNode.findOne(12), 'inner')).toBeTruthy()
- expect(treeNode.childNodes).toHaveLength(1)
- expect(treeNode.findOne(12).childNodes).toHaveLength(1)
- })
-
- it('async load data', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2', [], {
- isAsync: true,
- asyncLoadFn: (node, resolve) => {
- expect(node.asyncState).toBe('loading')
- setTimeout(() => {
- expect(node === treeNode.findOne(12)).toBeTruthy()
- resolve([
- new TreeNode(121, '异步加载的节点'),
- new TreeNode(122, '异步加载的节点')
- ])
- expect(node.asyncState).toBe('loaded')
- expect(node.childNodes).toHaveLength(2)
- }, 1)
- }
- })
- ])
-
- const node12 = treeNode.findOne(12)
- expect(node12.isLeaf).toBeFalsy()
- expect(node12.asyncState).toBe('notload')
- node12.loadAsync()
- })
-
- it('collapse', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
-
- const node11 = treeNode.findOne(11)
- const node12 = treeNode.findOne(12)
- node11.collapse()
- expect(node11.isExpanded).toBeTruthy()
- expect(node12.isExpanded).toBeFalsy()
- node12.collapse()
- expect(node11.isExpanded).toBeFalsy()
- expect(node12.isExpanded).toBeTruthy()
- })
-
- it('filter', () => {
- const treeNode = new TreeNode(1, 'root', [
- new TreeNode(11, 'root-1', [
- new TreeNode(111, 'root-1-1'),
- new TreeNode(112, 'root-1-2')
- ]),
- new TreeNode(12, 'root-2')
- ])
- const node111 = treeNode.findOne(111)
- const node112 = treeNode.findOne(112)
- const node12 = treeNode.findOne(12)
-
- treeNode.filter((node) => {
- return node === node111
- })
-
- expect(node111.isVisable).toBeTruthy()
- expect(node112.isVisable).toBeFalsy()
- expect(node12.isVisable).toBeFalsy()
- })
-})
diff --git a/packages/element3/packages/tree/__tests__/entity/tree.spec.js b/packages/element3/packages/tree/__tests__/entity/tree.spec.js
deleted file mode 100644
index e6fee551e..000000000
--- a/packages/element3/packages/tree/__tests__/entity/tree.spec.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Tree } from '../../entity/Tree'
-
-const data = [
- {
- id: 1,
- label: '一级 1',
- children: [
- {
- id: 4,
- label: '二级 1-1',
- children: [
- {
- id: 9,
- label: '三级 1-1-1'
- },
- {
- id: 10,
- label: '三级 1-1-2'
- }
- ]
- }
- ]
- },
- {
- id: 2,
- label: '一级 2',
- children: [
- {
- id: 5,
- label: '二级 2-1'
- },
- {
- id: 6,
- label: '二级 2-2'
- }
- ]
- },
- {
- id: 3,
- label: '一级 3',
- children: [
- {
- id: 7,
- label: '二级 3-1'
- },
- {
- id: 8,
- label: '二级 3-2'
- }
- ]
- }
-]
-
-describe('Tree.js', () => {
- it('instantiate Tree', () => {
- const tree = new Tree(data /*, default */)
-
- expect(tree.root.childNodes).toHaveLength(data.length)
- expect(tree.root.childNodes[0].label).toBe(data[0].label)
- })
-
- it('update', () => {
- const tree = new Tree(data)
- data[0].label = '更新后'
- expect(tree.root.childNodes[0].label).toBe('一级 1') // update before
- tree.update()
- expect(tree.root.childNodes[0].label).toBe('更新后') // update after
- })
-})
diff --git a/packages/element3/packages/tree/__tests__/libs/util.spec.js b/packages/element3/packages/tree/__tests__/libs/util.spec.js
deleted file mode 100644
index 08474cbe4..000000000
--- a/packages/element3/packages/tree/__tests__/libs/util.spec.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import {
- nodeMap,
- transitionObjectKey,
- methodInterceptor,
- extractMethods
-} from '../../libs/util'
-
-describe('utils.js', () => {
- it('nodeMap', () => {
- const a = [
- {
- id: 1,
- label: '一级 1',
- children: [
- {
- id: 4,
- label: '二级 1-1',
- children: [
- {
- id: 9,
- label: '三级 1-1-1'
- },
- {
- id: 10,
- label: '三级 1-1-2'
- }
- ]
- }
- ]
- },
- {
- id: 2,
- label: '一级 2',
- children: [
- {
- id: 5,
- label: '二级 2-1'
- },
- {
- id: 6,
- label: '二级 2-2'
- }
- ]
- },
- {
- id: 3,
- label: '一级 3',
- children: [
- {
- id: 7,
- label: '二级 3-1'
- },
- {
- id: 8,
- label: '二级 3-2'
- }
- ]
- }
- ]
-
- const b = nodeMap(
- a[0],
- (node) => {
- return node
- },
- { childKey: 'children', mapChildKey: 'children' }
- )
-
- expect(b === a[0]).toBeFalsy()
- })
-
- it('transitionObjectKey', () => {
- const obj = {
- id: 1,
- label: '一级 1'
- }
-
- transitionObjectKey(obj, { key: 'id', text: 'label' })
-
- expect(obj).toStrictEqual({
- key: 1,
- text: '一级 1'
- })
- })
-
- it('deep clone a and rename property', () => {
- const a = [
- {
- id: 1,
- label: '一级 1',
- children: [
- {
- id: 4,
- label: '二级 1-1',
- children: [
- {
- id: 9,
- label: '三级 1-1-1'
- },
- {
- id: 10,
- label: '三级 1-1-2'
- }
- ]
- }
- ]
- },
- {
- id: 2,
- label: '一级 2',
- children: [
- {
- id: 5,
- label: '二级 2-1'
- },
- {
- id: 6,
- label: '二级 2-2'
- }
- ]
- },
- {
- id: 3,
- label: '一级 3',
- children: [
- {
- id: 7,
- label: '二级 3-1'
- },
- {
- id: 8,
- label: '二级 3-2'
- }
- ]
- }
- ]
-
- const b = a.map((item) => {
- return nodeMap(
- item,
- (node) => {
- transitionObjectKey(node, { key: 'id', text: 'label' })
- return node
- },
- { childKey: 'children', mapChildKey: 'childNodes' }
- )
- })
-
- expect(b[0].key).toBe(a[0].id)
- expect(b[1].key).toBe(a[1].id)
- expect(b[1].childNodes[0].key).toBe(a[1].children[0].id)
- })
-
- it('methodInterceptor', () => {
- class O {
- constructor(a) {
- this.a = a
- }
-
- sum(b) {
- return this.a + b
- }
- }
-
- const o = new O(15)
-
- methodInterceptor(
- O,
- 'sum',
- function (b) {
- expect(this.a).toBe(15)
- expect(b).toBe(10)
- return [b]
- },
- function (ret, [b]) {
- expect(b).toBe(10)
- expect(ret).toBe(25)
- return 'change'
- }
- )
-
- const num = o.sum(10)
- expect(num).toBe('change')
- })
-
- it('extractMethods', () => {
- class O {
- constructor(a) {
- this.a = a
- }
-
- sum(b) {
- return this.a + b
- }
- }
-
- const o = new O(15)
-
- const { sum } = extractMethods(o, ['sum'])
-
- expect(sum(10)).toBe(25)
- })
-})
diff --git a/packages/element3/packages/tree/__tests__/tree.spec.js b/packages/element3/packages/tree/__tests__/tree.spec.js
deleted file mode 100644
index 1939b1007..000000000
--- a/packages/element3/packages/tree/__tests__/tree.spec.js
+++ /dev/null
@@ -1,209 +0,0 @@
-import { mount } from '@vue/test-utils'
-import { nextTick, ref } from 'vue'
-import Tree from '../Tree.vue'
-
-function createData() {
- const data = [
- {
- id: 1,
- label: '一级 1',
- children: [
- {
- id: 4,
- label: '二级 1-1',
- children: [
- {
- id: 9,
- label: '三级 1-1-1'
- },
- {
- id: 10,
- label: '三级 1-1-2'
- }
- ]
- }
- ]
- },
- {
- id: 2,
- label: '一级 2',
- children: [
- {
- id: 5,
- label: '二级 2-1'
- },
- {
- id: 6,
- label: '二级 2-2'
- }
- ]
- },
- {
- id: 3,
- label: '一级 3',
- children: [
- {
- id: 7,
- label: '二级 3-1'
- },
- {
- id: 8,
- label: '二级 3-2',
- isAsync: true
- }
- ]
- }
- ]
- return data
-}
-
-describe('Tree.vue', () => {
- describe('props', () => {
- it('data', async () => {
- const tree = mount(Tree, {
- props: {
- data: createData(),
- defaultNodeKey: {
- childNodes: 'children'
- },
- defaultExpandAll: true
- }
- })
-
- const node10 = tree.find('#TreeNode10')
- const node6 = tree.find('#TreeNode6')
-
- expect(node10.exists()).toBeTruthy()
- expect(node6.exists()).toBeTruthy()
- })
-
- it('async', async () => {
- let id = 1
- const asyncLoadFn = (node, resolve) => {
- if (node.level < 3) {
- resolve([
- {
- id: ++id,
- label: 'T' + id
- },
- {
- id: ++id,
- label: 'T' + id
- },
- {
- id: ++id,
- label: 'T' + id
- }
- ])
- }
- }
- const tree = mount(Tree, {
- props: {
- data: [
- {
- id: 1,
- label: 'T1'
- }
- ],
- defaultExpandAll: true,
- async: true,
- asyncLoadFn: asyncLoadFn
- }
- })
- const root = tree.vm.root
- root.childNodes[0].expand()
- expect(root.childNodes[0].childNodes).toHaveLength(3)
- })
-
- it('async and checked', async () => {
- let id = 1
- const asyncLoadFn = (node, resolve) => {
- if (node.level < 3) {
- resolve([
- {
- id: ++id,
- label: 'T' + id
- },
- {
- id: ++id,
- label: 'T' + id
- },
- {
- id: ++id,
- label: 'T' + id
- }
- ])
- }
- }
- const tree = mount(Tree, {
- props: {
- data: [
- {
- id: 1,
- label: 'T1'
- }
- ],
- defaultExpandAll: true,
- async: true,
- showCheckbox: true,
- asyncLoadFn: asyncLoadFn
- }
- })
- const root = tree.vm.root
- const t1 = root.childNodes[0]
- t1.expand()
- t1.setChecked(true)
- expect(t1.checkedNodes.length).toBe(3)
- t1.childNodes[0].setChecked(false)
- expect(t1.checkedNodes.length).toBe(2)
- expect(t1.isChecked && !t1.isIndeterminate).toBe(false) // when isIndeterminate=false and isChecked=true, t1 checkbox is checked state
- })
- })
-
- describe('events', () => {
- it('node-click', () => {
- const tree = mount(Tree, {
- props: {
- data: createData(),
- defaultNodeKey: {
- childNodes: 'children'
- },
- defaultExpandAll: true,
- 'onNode-click'(node) {
- expect(node.id).toBe(6)
- }
- }
- })
-
- const node6 = tree.find('#TreeNode6')
-
- expect(node6.exists()).toBeTruthy()
-
- node6.trigger('click')
- })
- })
-
- describe('v-model', () => {
- it('checked', async () => {
- const checked = ref([2])
-
- const tree = mount(Tree, {
- props: {
- data: createData(),
- defaultNodeKey: {
- childNodes: 'children'
- },
- defaultExpandAll: true,
- showCheckbox: true,
- checked: checked.value,
- 'onUpdate:checked': (e) => (checked.value = e)
- }
- })
- await nextTick()
- const node4 = tree.find('#TreeNode4 input')
- await node4.trigger('click')
- await nextTick()
- expect(checked.value).toStrictEqual(['1', '2', '4', '9', '10'])
- })
- })
-})
diff --git a/packages/element3/packages/tree/entity/Action.js b/packages/element3/packages/tree/entity/Action.js
deleted file mode 100644
index ab736f744..000000000
--- a/packages/element3/packages/tree/entity/Action.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { TreeNode } from './TreeNode'
-
-export function createAction(tree) {
- // this.root changed update this.raw
- const appendChild = function (node) {
- if (TreeNode.isType(node)) {
- tree.appendRawNode(this, node.data.raw) // 这里的this表示当前的TreeNode
- return [node]
- }
- tree.appendRawNode(this, node)
- return [tree.rawNodeToTreeNode(node)]
- }
- const removeChild = function (index) {
- tree.removeChildRawNode(this, index) // 这里的this表示当前的TreeNode
- }
- const insertChild = function (index, node) {
- if (TreeNode.isType(node)) {
- tree.insertRawNode(this, index, node.data.raw) // 这里的this表示当前的TreeNode
- return [index, node]
- }
- tree.insertRawNode(this, index, node)
- return [index, tree.rawNodeToTreeNode(node)]
- }
- return { appendChild, removeChild, insertChild }
-}
diff --git a/packages/element3/packages/tree/entity/Tree.js b/packages/element3/packages/tree/entity/Tree.js
deleted file mode 100644
index 81feb57bc..000000000
--- a/packages/element3/packages/tree/entity/Tree.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import { TreeNode } from './TreeNode'
-import { nodeMap, nodeEach, transitionObjectKey } from '../libs/util'
-import { createAction } from './Action'
-
-export class Tree {
- /**
- *
- * @param {object[]} list
- * @param {object} defaultNodeKey The incoming Node proprtry name maps to the default name
- */
- constructor(list, defaultNodeKey = {}, defaultNodeValue = {}) {
- this.isUpdateRaw = true
- this.raw = list
- this.injectAction = createAction(this) // The core method is injected with interceptor functions, the insert RowNode is automatically converted to TreeNode
- this.root = new TreeNode(
- Date.now(),
- 'root',
- [],
- defaultNodeValue,
- this.injectAction
- )
- this.defaultNodeKey = Object.assign(
- {
- id: 'id',
- label: 'label',
- childNodes: 'childNodes',
- isDisabled: 'isDisabled',
- isAsync: 'isAsync',
- isChecked: 'isChecked',
- isVisable: 'isVisable',
- isExpanded: 'isExpanded'
- },
- defaultNodeKey
- )
- this.defaultNodeValue = Object.assign({}, defaultNodeValue)
- // this.checked = []
- // this.expanded = []
- this.initRoot()
- }
-
- get isEmpty() {
- for (let i = 0; i < this.root.childNodes.length; i++) {
- const node = this.root.childNodes[i]
- if (node.isVisable) {
- return false
- }
- }
- return true
- }
-
- get checked() {
- const t = {}
- this.root.depthEach((node) => {
- node.isChecked && !node.isIndeterminate && (t[node.id] = true)
- })
- return Object.keys(t)
- }
-
- set checked(v) {
- this.setCheckedByIdList(v)
- }
-
- get expanded() {
- const t = {}
- this.root.depthEach((node) => {
- node.isExpanded && (t[node.id] = true)
- })
- return Object.keys(t)
- }
-
- set expanded(v) {
- this.setExpandedByIdList(v, true)
- }
-
- initRoot() {
- // rekeyname and create TreeNode
- this.isUpdateRaw = false
- this.root.childNodes = []
- this.root.append(...this.raw)
- this.isUpdateRaw = true
- }
-
- rawNodeToTreeNode(rawNode) {
- const { childNodes } = this.defaultNodeKey
- return nodeMap(
- rawNode,
- (_node, node) => {
- const handledNode = transitionObjectKey(_node, this.defaultNodeKey)
- // debugger
- const treeNode = TreeNode.create(
- Object.assign(
- {},
- this.defaultNodeValue,
- { data: { raw: node }, interceptHandler: this.injectAction },
- handledNode,
- { childNodes: [] }
- )
- )
- return treeNode
- },
- { childKey: childNodes, mapChildKey: 'childNodes' }
- )
- }
-
- update() {
- // rebuild tree
- // TODO: 这里可以做diff优化
- this.initRoot()
- }
-
- removeChildRawNode(target, index) {
- const { childNodes } = this.defaultNodeKey
- if (!this.isUpdateRaw) {
- return
- }
- let rawChild = target.data.raw ? target.data.raw[childNodes] : this.raw
- if (!rawChild) {
- target.data.raw[childNodes] = []
- rawChild = target.data.raw[childNodes]
- }
- rawChild.splice(index, 1)
- }
-
- appendRawNode(target, rawNode) {
- const { childNodes } = this.defaultNodeKey
- if (!this.isUpdateRaw) {
- return
- }
- let rawChild = target.data.raw ? target.data.raw[childNodes] : this.raw
- if (!rawChild) {
- target.data.raw[childNodes] = []
- rawChild = target.data.raw[childNodes]
- }
- rawChild.push(rawNode)
- }
-
- insertRawNode(target, index, rawNode) {
- const { childNodes } = this.defaultNodeKey
- if (!this.isUpdateRaw) {
- return
- }
- let rawChild = target.data.raw ? target.data.raw[childNodes] : this.raw
- if (!rawChild) {
- target.data.raw[childNodes] = []
- rawChild = target.data.raw[childNodes]
- }
- rawChild.splice(index, 0, rawNode)
- }
-
- getParentRawNode(rawNode) {
- let parentNode = null
- const { childNodes } = this.defaultNodeKey
- nodeEach(
- { [childNodes]: this.raw },
- (current, parent) => {
- if (current === rawNode || current.id === rawNode.id) {
- parentNode = parent
- return true
- }
- },
- { childKey: childNodes }
- )
- return parentNode
- }
-
- showAll() {
- this.root.setSubTreeVisable(true)
- }
-
- checkedAll() {
- this.root.setChecked(true)
- }
-
- expandAll() {
- this.root.depthEach((node) => {
- if (node.isLeaf) {
- node.expand(true)
- }
- })
- }
-
- setCheckedByIdList(idList = []) {
- console.log(idList.toString())
- this.root.depthEach((node) => {
- if (node === this.root) {
- return
- }
- if (idList.indexOf(node.id) !== -1) {
- node.isChecked = true
- } else {
- // node.isChecked = false
- }
- })
- }
-
- setExpandedByIdList(idList = [], value = true) {
- this.root.depthEach((node) => {
- if (idList.indexOf(node.id) !== -1) {
- node.expand(value)
- }
- })
- }
-}
diff --git a/packages/element3/packages/tree/entity/TreeNode.js b/packages/element3/packages/tree/entity/TreeNode.js
deleted file mode 100644
index 5d90a3be1..000000000
--- a/packages/element3/packages/tree/entity/TreeNode.js
+++ /dev/null
@@ -1,499 +0,0 @@
-/* eslint-disable @typescript-eslint/no-this-alias */
-const typeFlag = Symbol('TREE_NODE')
-
-/**
- *
- * @param {*} id
- * @param {string} label
- * @param {TreeNode[]} childNodes
- * @param {object} param3
- */
-export class TreeNode {
- constructor(
- id,
- label,
- childNodes = [],
- {
- /* 默认值 */
- parent = null,
- isAsync = false,
- isVisable = true,
- isChecked = false,
- isIndeterminate = false,
- isExpanded = false,
- isDisabled = false,
- isDraggable = false,
- isLeaf = false,
- data = {},
- asyncLoadFn = () => null
- } = {},
- {
- /* 拦截函数 */
- insertChild = null,
- appendChild = null,
- removeChild = null
- } = {}
- ) {
- this.id = id || label
- this.label = label
- this.parent = parent
- this.childNodes = childNodes
- this.isVisable = isVisable
- this.isChecked = isChecked
- this.isIndeterminate = isIndeterminate
- this.isExpanded = isExpanded
- this.isDisabled = isDisabled
- this.isDraggable = isDraggable
- this.isRendered = false
- this.data = data // Additional data carried by the node
- this.isLeaf = isLeaf
-
- this.isAsync = isAsync // Load child only at expand time
- this.asyncState = 'notload' // notload || loaded || loading
- this.asyncLoadFn = asyncLoadFn // (currentNode, resolveFn) async load child node
-
- this.interceptHandler = {
- insertChild,
- appendChild,
- removeChild
- }
-
- // this.cache = {
- // level: null
- // }
- // not use, You can cache read-only properties here
-
- this.updateChildParent()
- this.updateChildChecked()
- this.updateCheckedState()
- this.updateExpandedState()
- }
-
- get root() {
- // readonly
- let root = this
- this.upwardEach((node) => {
- root = node
- })
- return root
- }
-
- get isLeaf() {
- return this.isAsync
- ? this.asyncState === 'loaded' && this.childNodes.length === 0
- : this.childNodes.length === 0
- }
-
- set isLeaf(v) {
- if (v) this.asyncState = 'loaded'
- }
-
- get isRoot() {
- // readonly
- return this.root === this
- }
-
- get level() {
- // readonly
- if (!this.parent) return 0
- return this.parent.level + 1
- }
-
- get type() {
- // readonly
- return typeFlag
- }
-
- get index() {
- // readonly
- const parent = this.parent
- if (!parent) return -1
- return parent.findChildIndex(this)
- }
-
- get checkedNodes() {
- return this.childNodes.filter(
- (treeNode) => treeNode.isChecked && !treeNode.isIndeterminate
- )
- }
-
- loadAsync() {
- if (!this.isAsync || this.asyncState !== 'notload') {
- return
- }
- const resolveFn = (childNodes = []) => {
- // this.childNodes = []
- this.append(...childNodes)
- this.asyncState = 'loaded'
- }
- this.asyncState = 'loading'
- this.asyncLoadFn(this, resolveFn)
- }
-
- updateChildParent() {
- this.childNodes.forEach((node) => {
- node.parent = this
- })
- }
-
- updateChildChecked() {
- if (this.isChecked) {
- this.setChildChecked(true)
- }
- }
-
- updateCheckedState() {
- if (this.isLeaf) {
- return
- }
- // not leaf node exec
- const checkedNodeLen = this.getCheckedNode().length
- const childrenNodeLen = this.childNodes.length
- if (childrenNodeLen === 0) {
- return
- }
- if (checkedNodeLen === childrenNodeLen) {
- // full select
- this.setCheckedState(1)
- } else if (checkedNodeLen === 0) {
- // not full select
- this.setCheckedState(0)
- } else {
- // Half select
- this.setCheckedState(2)
- }
- }
-
- updateExpandedState() {
- const childNodes = this.childNodes
- for (let i = 0; i < childNodes.length; i++) {
- const node = childNodes[i]
- if (node.isExpanded) {
- this.isExpanded = true
- return
- }
- }
- }
-
- /**
- *
- * @param {TreeNode} node
- */
- appendChild(node) {
- if (this.interceptHandler.appendChild) {
- const [_node] = this.interceptHandler.appendChild.apply(this, arguments)
- if (typeof _node !== 'undefined') node = _node
- }
- if (!TreeNode.isType(node)) {
- return false
- }
- node.parent = this
- if (this.isChecked) {
- node.setChecked(true)
- }
- this.childNodes.push(node)
- return true
- }
-
- append(...nodes) {
- nodes.forEach((node) => {
- this.appendChild(node)
- })
- return true
- }
-
- /**
- *
- * @param {number} index
- * @param {TreeNode} node
- */
- insertChild(index, node) {
- if (this.interceptHandler.appendChild) {
- const [_index, _node] = this.interceptHandler.insertChild.apply(
- this,
- arguments
- )
- if (typeof _index !== 'undefined') index = _index
- if (typeof _node !== 'undefined') node = _node
- }
- if (!TreeNode.isType(node)) {
- return false
- }
- if (this.isChecked) {
- node.setChecked(true)
- }
- node.parent = this
- this.childNodes.splice(index, 0, node)
- return true
- }
-
- insert() {
- this.insertChild.apply(this, arguments)
- }
-
- /**
- *
- * @param {number} index
- */
- removeChild(index) {
- if (this.interceptHandler.appendChild) {
- this.interceptHandler.removeChild.apply(this, arguments)
- }
- if (index < 0 || index >= this.childNodes.length) {
- return false
- }
- this.childNodes.splice(index, 1)
- return true
- }
-
- remove() {
- if (!this.parent) {
- return false
- }
- return this.parent.removeChild(this.index)
- }
-
- /**
- *
- * @param {boolean} value
- */
- setChecked(value, strictly = false) {
- this.isIndeterminate = false
- let _value = !this.isChecked
- if (typeof value !== 'undefined') {
- _value = value
- }
-
- this.isChecked = _value
- if (!strictly) {
- this.upwardEach((node) => {
- if (!node.isDisabled) this.updateCheckedState.call(node)
- })
- this.depthEach((node) => {
- if (!node.isDisabled && node.isVisable) node.isChecked = _value
- })
- }
-
- return this.isChecked
- }
-
- setChildChecked(value) {
- this.childNodes.forEach((node) => (node.isChecked = value))
- }
-
- /**
- * Traverse upward
- * @param {Function} callback( node:TreeNode ) if returns true then stop each, else not stop
- */
- upwardEach(callback, { isSkipSelf = true } = {}) {
- let current = isSkipSelf ? this.parent : this
- while (current) {
- if (callback(current)) {
- return
- }
- current = current.parent
- }
- }
-
- /**
- * from current node start, down each
- * @param {Function} callback( node:TreeNode, parentNode:TreeNode, deep: number) if returns true then stop each, else not stop
- */
- depthEach(upToDownCallBack = () => false, downToUpCallBack = () => false) {
- const dfs = (node, deep) => {
- if (!TreeNode.isType(node)) {
- return
- }
- for (let i = 0; i < node.childNodes.length; i++) {
- const _node = node.childNodes[i]
- if (upToDownCallBack(_node, node, deep)) return
- dfs(_node, deep + 1)
- if (downToUpCallBack(_node, node, deep)) return
- }
- }
- upToDownCallBack(this, this.parent, 0)
- dfs(this, 1)
- downToUpCallBack(this, this.parent, 0)
- }
-
- getCheckedNode() {
- return this.childNodes.filter((item) => item.isChecked)
- }
-
- /**
- *
- * @param {number} state 0:not checked, 1:checked, 2:indeterminate
- */
- setCheckedState(state) {
- const eunmState = [
- { isChecked: false, isIndeterminate: false },
- { isChecked: true, isIndeterminate: false },
- { isChecked: true, isIndeterminate: true }
- ]
- if (!eunmState[state]) {
- return false
- }
-
- this.isChecked = eunmState[state].isChecked
- this.isIndeterminate = eunmState[state].isIndeterminate
- return true
- }
-
- /**
- * Look for node in the subtree
- * @param {ID|TreeNode} target
- */
- findOne(target) {
- let res = null
- this.depthEach((node) => {
- if (node.id == target || node === target) {
- res = node
- return true
- }
- })
- return res
- }
-
- /**
- * find nodes
- * @param {Function | string} target
- */
- findMany(target) {
- const res = []
- this.depthEach((node, parent, deep) => {
- if (
- (typeof target === 'function' && target(node, parent, deep)) ||
- (typeof target === 'string' && node.label.search(target) !== -1)
- ) {
- res.push(node)
- }
- })
- return res
- }
-
- findChildIndex(target) {
- for (let i = 0; i < this.childNodes.length; i++) {
- const node = this.childNodes[i]
- if (node.id == target || node === target) {
- return i
- }
- }
- return -1
- }
-
- expand(value, ...extraNodes) {
- let _value = this.isExpanded
- _value = typeof value === 'undefined' ? !_value : value
- this.isExpanded = _value
- this.upwardEach((node) => {
- node.expand(true)
- })
-
- this.loadAsync()
- this.append(...extraNodes)
- this.isRendered = true
- }
-
- filter(callback = () => true) {
- const arr = []
- this.setSubTreeVisable(false)
- this.depthEach((node, parentNode, deep) => {
- const isShow = callback(node, parentNode, deep)
- if (isShow) {
- node.setVsiable(true)
- arr.push(node)
- }
- })
- return arr
- }
-
- setSubTreeVisable(value) {
- this.depthEach((node) => {
- node.isVisable = value
- })
- }
-
- setVsiable(value) {
- this.isVisable = value
- this.upwardEach((node) => {
- node.isVisable = true
- })
- }
-
- separation() {
- const parent = this.parent
- if (!parent) return null
- parent.removeChild(this.index)
- this.parent = null
- return this
- }
-
- /**
- * is allow move to target
- * @param {TreeNode} target
- * @param {string} relative top, bottom, inner
- */
- isAllowMove(target) {
- if (target === this) {
- return false
- }
-
- if (this.findOne(target)) {
- return false
- }
-
- return true
- }
-
- /**
- * currentNode move to targetNode relative location
- * @param {TreeNode} target
- * @param {string} relative top, bottom, inner
- */
- move(target, relative) {
- if (!this.isAllowMove(target, relative)) {
- return false
- }
-
- this.separation()
-
- switch (relative) {
- case 'top': // top
- target.parent.insertChild(target.index, this)
- return true
- case 'inner': // inner
- target.expand(true, this)
- return true
- case 'bottom': // bottom
- target.parent.insertChild(target.index + 1, this)
- return true
- }
- }
-
- collapse() {
- const parent = this.parent
- if (!parent) {
- return
- }
-
- parent.childNodes.forEach((node) => {
- if (node === this) {
- node.expand()
- } else {
- node.expand(false)
- }
- })
- }
-
- static isType(node) {
- if (typeof node !== 'object') {
- return false
- }
-
- return node.type === typeFlag
- }
-
- static create({ id, label, childNodes, interceptHandler, ...otherParams }) {
- return new TreeNode(id, label, childNodes, otherParams, interceptHandler)
- }
-}
diff --git a/packages/element3/packages/tree/libs/util.js b/packages/element3/packages/tree/libs/util.js
deleted file mode 100644
index 6ea5b0a9d..000000000
--- a/packages/element3/packages/tree/libs/util.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { isArray, isObject } from '../../../src/utils/types'
-
-/**
- * Deep traversal of the object
- * @param {object} target
- * @param {function} callback(cloneRawNode, rawNode, isLeaf)
- */
-export function nodeMap(
- target,
- callback = () => null,
- { childKey = 'children', mapChildKey = 'children' } = {
- /* TreeNode */
- }
-) {
- const dfs = (node) => {
- if (isObject(node) && !isArray(node[childKey])) {
- const _cloneNode = { ...node }
- return callback(_cloneNode, node, true)
- }
- const cloneNode = { ...node }
- const newNode = callback(cloneNode, node, false)
- if (typeof newNode[childKey] !== 'undefined') delete newNode[childKey]
- newNode[mapChildKey] = []
- for (let i = 0; i < node[childKey].length; i++) {
- const _node = node[childKey][i]
- const ret = dfs(_node)
- ret.parent = newNode
- newNode[mapChildKey].push(ret)
- }
-
- return newNode
- }
-
- return dfs(target)
-}
-
-export function nodeEach(
- target,
- callback = () => false,
- { childKey = 'children', root = null } = {}
-) {
- const dfs = (node) => {
- if (!isObject(node) || isArray(node)) {
- return
- }
-
- const child = node[childKey] || []
-
- for (let i = 0; i < child.length; i++) {
- const _node = child[i]
- if (callback(_node /* Current */, node /* Parent */)) return
- dfs(_node)
- }
- }
- if (callback(target /* Current */, root /* Parent */)) return
- return dfs(target)
-}
-
-/**
- * Modify the property name of the object
- * @param {object} obj
- * @param {object} keyMap newKey mapping oldKey
- */
-export function transitionObjectKey(obj, keyMap = {}) {
- const transitionKeyList = Object.keys(keyMap)
- transitionKeyList.forEach((key) => {
- if (key !== keyMap[key]) {
- obj[key] = obj[keyMap[key]]
- delete obj[keyMap[key]]
- }
- })
- return obj
-}
-
-export function isBrotherNode(nodeA, nodeB) {
- return nodeA.parent === nodeB.parent
-}
-
-export function isGreatGrandfather(nodeA, nodeB) {
- let flag = false
- nodeB.upwardEach((node) => {
- if (node === nodeA) {
- flag = true
- return true
- }
- })
-
- return flag
-}
-
-/**
- *
- * @param {*} _class 构造函数
- * @param {*} funName 方法名
- * @param {*} beforeCall 在之前执行
- * @param {*} afterCall 在之后执行
- */
-export function methodInterceptor(
- _class,
- funName,
- beforeCall = () => null,
- afterCall = () => null
-) {
- const fun = _class.prototype[funName]
- _class.prototype[funName] = function (...args) {
- const _args = beforeCall.apply(this, args) || args
- const _return = fun.apply(this, _args)
- const ret = afterCall.apply(this, [_return, _args]) || _return
- return ret
- }
-}
-
-export const extractMethods = (obj, methods) => {
- const methodList = {}
- methods.forEach((method) => {
- methodList[method] = obj[method].bind(obj)
- })
- return methodList
-}
diff --git a/packages/element3/packages/tree/index.js b/packages/element3/src/components/Tree/index.js
similarity index 65%
rename from packages/element3/packages/tree/index.js
rename to packages/element3/src/components/Tree/index.js
index 9682eb50b..89f226706 100644
--- a/packages/element3/packages/tree/index.js
+++ b/packages/element3/src/components/Tree/index.js
@@ -1,8 +1,8 @@
-import ElTree from './Tree.vue'
+import ElTree from './src/Tree.vue'
/* istanbul ignore next */
ElTree.install = function (app) {
app.component(ElTree.name, ElTree)
}
-export default ElTree
+export { ElTree }
diff --git a/packages/element3/src/components/Tree/src/Tree.vue b/packages/element3/src/components/Tree/src/Tree.vue
new file mode 100644
index 000000000..9297f90ad
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/Tree.vue
@@ -0,0 +1,91 @@
+
+
+
+
+ {{ emptyText }}
+
+
+
+
+
diff --git a/packages/element3/src/components/Tree/src/TreeNode.vue b/packages/element3/src/components/Tree/src/TreeNode.vue
new file mode 100644
index 000000000..3d61b5081
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/TreeNode.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
diff --git a/packages/element3/src/components/Tree/src/TreeNodeContent.vue b/packages/element3/src/components/Tree/src/TreeNodeContent.vue
new file mode 100644
index 000000000..d2b0a128a
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/TreeNodeContent.vue
@@ -0,0 +1,28 @@
+
diff --git a/packages/element3/src/components/Tree/src/entity/TreeNode.ts b/packages/element3/src/components/Tree/src/entity/TreeNode.ts
new file mode 100644
index 000000000..a919e732b
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/entity/TreeNode.ts
@@ -0,0 +1,187 @@
+import {
+ DefaultPropKey,
+ TreeNodeKeys,
+ TreeNodeInstance,
+ TreeNodeProps,
+ ID
+} from '../types'
+import { watch } from '../utils/effect'
+
+export class TreeNode implements TreeNodeProps {
+ private dk: DefaultPropKey = {
+ id: 'id',
+ label: 'label',
+ children: 'children',
+ parent: 'parent',
+ isVisible: 'isVisible',
+ isDisabled: 'isDisabled',
+ isExpanded: 'isExpanded',
+ isChecked: 'isChecked'
+ }
+ private instance: TreeNodeInstance = {
+ id: 0,
+ label: '',
+ children: [],
+ parent: null,
+ isVisible: true,
+ isDisabled: false,
+ isExpanded: true,
+ isChecked: false
+ }
+
+ raw: RawNode
+
+ constructor(
+ rawNode: RawNode,
+ parent: TreeNode,
+ dk: DefaultPropKey,
+ dv: TreeNodeInstance
+ ) {
+ Object.assign(this.dk, dk)
+ Object.assign(this.instance, dv)
+
+ this.raw = rawNode
+ this.instance.parent = parent
+
+ this.effect(rawNode, dk, dv)
+ }
+
+ // readonly
+ get level(): number {
+ return (this.parent?.level ?? -1) + 1
+ }
+
+ // readonly
+ get isLeaf(): boolean {
+ return this.get('children').length === 0
+ }
+
+ get id(): ID {
+ return this.get('id')
+ }
+
+ set id(v: ID) {
+ this.set('id', v)
+ }
+
+ get label(): string {
+ return this.get('label')
+ }
+
+ set label(v: string) {
+ this.set('label', v)
+ }
+
+ // readonly
+ get parent(): TreeNode {
+ return this.instance.parent
+ }
+
+ // readonly
+ get children(): TreeNode[] {
+ return this.instance.children
+ }
+
+ get isVisible(): boolean {
+ return this.get('isVisible')
+ }
+
+ set isVisible(v: boolean) {
+ this.set('isVisible', v)
+ }
+
+ get isChecked(): boolean {
+ return this.get('isChecked')
+ }
+
+ set isChecked(v: boolean) {
+ this.set('isChecked', v)
+ }
+
+ get isExpanded(): boolean {
+ return this.get('isExpanded')
+ }
+
+ set isExpanded(v: boolean) {
+ this.set('isExpanded', v)
+ }
+
+ get isDisabled(): boolean {
+ return this.get('isDisabled')
+ }
+
+ set isDisabled(v: boolean) {
+ this.set('isDisabled', v)
+ }
+
+ get(tk: TreeNodeKeys): any {
+ if (!Reflect.has(this.dk, tk)) {
+ return this.raw[tk]
+ }
+ return this.raw[this.dk[tk as string]] ?? this.instance[tk]
+ }
+
+ set(tk: TreeNodeKeys, val: unknown): void {
+ if (!Reflect.has(this.dk, tk)) {
+ this.raw[tk] = val
+ return
+ }
+ if (Reflect.has(this.raw as any, this.dk[tk])) {
+ this.raw[this.dk[tk as string]] = val
+ return
+ }
+ this.instance[tk as string] = val
+ }
+
+ /**
+ * reactive raw to this
+ */
+ effect(
+ raw: RawNode,
+ dk: DefaultPropKey,
+ dv: TreeNodeInstance
+ ): void {
+ // when raw change
+ this.raw = watch(raw, (e) => {
+ if (e.type === 'set') {
+ if (e.key === this.dk.children && e.value instanceof Array) {
+ this.instance.children = e.value.map(
+ (node) => new TreeNode(node, this, dk, dv)
+ )
+ } else {
+ this.instance[this.findDkByRk(e.key as keyof RawNode)] = e.value
+ }
+ }
+ })
+
+ // when raw children change
+ if (raw[this.dk.children as string])
+ this.raw[this.dk.children as string] = watch(
+ raw[this.dk.children as string],
+ (e) => {
+ if (e.type === 'set') {
+ this.instance.children[e.key] =
+ typeof e.value === 'object'
+ ? new TreeNode(e.value, this, dk, dv)
+ : e.value
+ }
+ }
+ )
+ }
+
+ expand(is = !this.isExpanded): void {
+ this.isExpanded = is
+ }
+
+ findDkByRk(rk: keyof RawNode): string | null {
+ for (const key in this.dk) {
+ if (Object.prototype.hasOwnProperty.call(this.dk, key)) {
+ const v = this.dk[key]
+ if (v === rk) {
+ return key
+ }
+ }
+ }
+ return null
+ }
+}
diff --git a/packages/element3/src/components/Tree/src/props.ts b/packages/element3/src/components/Tree/src/props.ts
new file mode 100644
index 000000000..00930ca53
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/props.ts
@@ -0,0 +1,26 @@
+import { DefaultPropKey } from './types'
+
+export const treeProps = {
+ defaultNodeKey: {
+ type: Object,
+ default: (): DefaultPropKey => ({
+ id: 'id',
+ label: 'label',
+ parent: 'parent',
+ children: 'children',
+ isDisabled: 'isDisabled',
+ isVisible: 'isVisible',
+ isExpanded: 'isExpanded'
+ })
+ },
+ props: {
+ type: Object
+ },
+ data: { type: Array },
+ modelValue: { type: Array },
+ indent: { type: Number, default: 14 },
+ nodeKey: { type: String },
+ emptyText: { type: String, default: '内容为空' },
+
+ iconClass: { type: String, default: 'el-icon-caret-right' }
+}
diff --git a/packages/element3/src/components/Tree/src/types.ts b/packages/element3/src/components/Tree/src/types.ts
new file mode 100644
index 000000000..35dc9211d
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/types.ts
@@ -0,0 +1,38 @@
+import { TreeNode } from './entity/TreeNode'
+import ElTree from './Tree.vue'
+import ElTreeNode from './TreeNode.vue'
+
+export type ID = number | string
+
+export interface TreeNodeInstance {
+ id: ID
+ label: string
+
+ children: TreeNode[]
+ parent: TreeNode
+ isVisible: boolean
+ isDisabled: boolean
+ isExpanded: boolean
+ isChecked: boolean
+}
+export interface TreeNodeProps extends TreeNodeInstance {
+ readonly level: number
+ readonly children: TreeNode[]
+ readonly parent: TreeNode
+}
+
+export type TreeNodeKeys = keyof TreeNodeInstance
+
+export type RawNodeKeys = keyof RawNode
+
+export type DefaultPropKey = {
+ [treeNodeKey in TreeNodeKeys]?: keyof RawNode | TreeNodeKeys
+}
+
+export type DefaultPropValue = {
+ [treeNodeKey in TreeNodeKeys]?: TreeNodeInstance[treeNodeKey]
+}
+
+export type ElTreeCtx = typeof ElTree
+
+export type ElTreeNodeCtx = typeof ElTreeNode
diff --git a/packages/element3/src/components/Tree/src/utils/effect.ts b/packages/element3/src/components/Tree/src/utils/effect.ts
new file mode 100644
index 000000000..67c3eb821
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/utils/effect.ts
@@ -0,0 +1,40 @@
+export type WatchCbEv = {
+ type: 'get' | 'set' | 'del'
+ target: any
+ key: string | number | symbol
+ value?: any
+ receiver?: any
+}
+
+export function watch(obj: T, callback: (e: WatchCbEv) => void): T {
+ const proxy = new Proxy(obj as any, {
+ get(target, key, receiver) {
+ callback({
+ type: 'get',
+ target,
+ key,
+ receiver
+ })
+ return Reflect.get(target, key, receiver)
+ },
+ set(target, key, value, receiver) {
+ callback({
+ type: 'set',
+ target,
+ key,
+ value,
+ receiver
+ })
+ return Reflect.set(target, key, value, receiver)
+ },
+ deleteProperty(target, key) {
+ callback({
+ type: 'del',
+ target,
+ key
+ })
+ return Reflect.deleteProperty(target, key)
+ }
+ })
+ return proxy as T
+}
diff --git a/packages/element3/src/components/Tree/src/utils/useTools.ts b/packages/element3/src/components/Tree/src/utils/useTools.ts
new file mode 100644
index 000000000..733b7a3d6
--- /dev/null
+++ b/packages/element3/src/components/Tree/src/utils/useTools.ts
@@ -0,0 +1,14 @@
+import { onBeforeUpdate } from 'vue'
+
+export function useForRefs(
+ arr: any[],
+ map = (v: any) => v
+): { (component: any): void } {
+ onBeforeUpdate(() => {
+ arr.length = 0
+ })
+
+ return (component: any) => {
+ if (component) arr.push(map(component))
+ }
+}
diff --git a/packages/element3/src/components/Tree/tests/Tree.spec.ts b/packages/element3/src/components/Tree/tests/Tree.spec.ts
new file mode 100644
index 000000000..9eeebd353
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/Tree.spec.ts
@@ -0,0 +1,128 @@
+import { mount } from '@vue/test-utils'
+import { nextTick, ref } from 'vue'
+import Tree from '../src/Tree.vue'
+
+describe('Tree.vue', () => {
+ it('render tree data by `props.modelValue`.', async () => {
+ const data = [
+ {
+ key: '1',
+ text: 'node1',
+ childs: [
+ {
+ key: '3',
+ text: 'node3',
+ childs: [
+ {
+ key: '5',
+ text: 'node5'
+ }
+ ]
+ },
+ {
+ key: '4',
+ text: 'node4'
+ }
+ ]
+ },
+ {
+ key: '2',
+ text: 'node2',
+ childs: []
+ }
+ ]
+ const wrapper = mount(Tree, {
+ props: {
+ modelValue: data,
+ defaultNodeKey: {
+ id: 'key',
+ label: 'text',
+ children: 'childs'
+ }
+ }
+ })
+
+ await nextTick()
+ expect(wrapper.find('#TN1').exists()).toBeTruthy()
+ expect(wrapper.find('#TN2').exists()).toBeTruthy()
+ expect(wrapper.find('#TN3').exists()).toBeTruthy()
+ expect(wrapper.find('#TN4').exists()).toBeTruthy()
+ expect(wrapper.find('#TN5').exists()).toBeTruthy()
+ })
+
+ it('when empty tree data.', async () => {
+ const data = []
+ const wrapper = mount(Tree, {
+ props: {
+ modelValue: data,
+ defaultNodeKey: {
+ id: 'key',
+ label: 'text',
+ children: 'childs'
+ }
+ }
+ })
+
+ await nextTick()
+ expect(wrapper.text()).toEqual('内容为空')
+ })
+
+ it('vModel tree data by `props.modelValue`.', async () => {
+ const data = ref([
+ {
+ key: '1',
+ text: 'node1',
+ childs: [
+ {
+ key: '3',
+ text: 'node3',
+ childs: [
+ {
+ key: '5',
+ text: 'node5'
+ }
+ ]
+ },
+ {
+ key: '4',
+ text: 'node4'
+ }
+ ]
+ },
+ {
+ key: '2',
+ text: 'node2',
+ childs: []
+ }
+ ])
+ const wrapper = mount(Tree, {
+ props: {
+ modelValue: data,
+ 'onUpdate:modelValue': (v) => {
+ data.value = v
+ },
+ defaultNodeKey: {
+ id: 'key',
+ label: 'text',
+ children: 'childs'
+ }
+ }
+ })
+
+ data.value.splice(0, 1)
+ await nextTick()
+ expect(wrapper.find('#TN1').exists()).toBeFalsy()
+ expect(wrapper.find('#TN2').exists()).toBeTruthy()
+ expect(wrapper.find('#TN3').exists()).toBeFalsy()
+ expect(wrapper.find('#TN4').exists()).toBeFalsy()
+ expect(wrapper.find('#TN5').exists()).toBeFalsy()
+
+ data.value.push({
+ key: '6',
+ text: 'node6',
+ childs: []
+ })
+ await nextTick()
+ expect(wrapper.find('#TN6').exists()).toBeTruthy()
+ })
+})
diff --git a/packages/element3/src/components/Tree/tests/TreeNode.spec.ts b/packages/element3/src/components/Tree/tests/TreeNode.spec.ts
new file mode 100644
index 000000000..2f7492fb5
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/TreeNode.spec.ts
@@ -0,0 +1,25 @@
+import { createTestData } from './entity/TreeNode.spec'
+import TreeNode from '../src/TreeNode.vue'
+import { mount } from '@vue/test-utils'
+
+describe('TreeNode.vue', () => {
+ it('render treeNode', () => {
+ const { treeNode } = createTestData()
+ const wrapper = mount(TreeNode, {
+ props: {
+ node: treeNode
+ },
+ global: {
+ provide: {
+ tree: {
+ indent: 10
+ }
+ }
+ }
+ })
+
+ expect(wrapper.find('#TN1').exists()).toBeTruthy()
+ expect(wrapper.find('#TN11').exists()).toBeTruthy()
+ expect(wrapper.find('#TN111').exists()).toBeTruthy()
+ })
+})
diff --git a/packages/element3/src/components/Tree/tests/TreeNodeContent.spec.ts b/packages/element3/src/components/Tree/tests/TreeNodeContent.spec.ts
new file mode 100644
index 000000000..1db10bea0
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/TreeNodeContent.spec.ts
@@ -0,0 +1,16 @@
+import TreeNodeContent from '../src/TreeNodeContent.vue'
+import { mount } from '@vue/test-utils'
+import { createTestData } from './entity/TreeNode.spec'
+
+describe('TreeNodeContent.vue', () => {
+ it('show label', () => {
+ const { treeNode } = createTestData()
+ const tnc = mount(TreeNodeContent, {
+ props: {
+ node: treeNode
+ }
+ })
+
+ expect(tnc).toHaveTextContent('hello')
+ })
+})
diff --git a/packages/element3/src/components/Tree/tests/entity/TreeNode.spec.ts b/packages/element3/src/components/Tree/tests/entity/TreeNode.spec.ts
new file mode 100644
index 000000000..799db4bcd
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/entity/TreeNode.spec.ts
@@ -0,0 +1,88 @@
+import { reactive } from 'vue'
+import { TreeNode } from '../../src/entity/TreeNode'
+import { DefaultPropKey, TreeNodeInstance } from '../../src/types'
+
+export function createTestData(): any {
+ const rawNode = reactive({
+ key: 1,
+ text: 'hello',
+ isChecked: false,
+ isExpanded: false,
+ childs: [
+ {
+ key: 11,
+ text: 'child11',
+ childs: [
+ {
+ key: 111,
+ text: 'child111',
+ childs: []
+ }
+ ]
+ }
+ ]
+ })
+
+ const dk: DefaultPropKey = {
+ id: 'key',
+ label: 'text',
+ children: 'childs',
+ isChecked: 'isChecked'
+ }
+
+ const dv: TreeNodeInstance = {
+ id: 0,
+ label: '',
+ children: [],
+ parent: null,
+ isChecked: false,
+ isDisabled: false,
+ isExpanded: false,
+ isVisible: true
+ }
+
+ const treeNode = new TreeNode(rawNode, null, dk, dv)
+
+ return { rawNode, treeNode, dk, dv }
+}
+
+describe('TreeNode.ts', () => {
+ it('test reactive result', () => {
+ const { rawNode, treeNode } = createTestData()
+ treeNode.label = 'test'
+ treeNode.isChecked = true
+ treeNode.isExpanded = true
+ treeNode.raw.key = 666
+ expect(rawNode.text).toEqual('test')
+ expect(rawNode.isChecked).toEqual(true)
+ expect(treeNode.isExpanded).toEqual(true)
+ expect(rawNode.key).toEqual(666)
+
+ // add a node
+ treeNode.raw.childs.push({ key: 999, text: 'js', childs: [] })
+ expect(treeNode.children).toHaveLength(2)
+ expect(treeNode.children[1].id).toEqual(999)
+
+ // delete a node
+ treeNode.raw.childs.splice(0, 1)
+ expect(treeNode.children).toHaveLength(1)
+ expect(treeNode.children[0].id).toEqual(999)
+
+ // cover childs
+ treeNode.raw.childs = [{ key: 888, text: 'ts', childs: [] }]
+ expect(treeNode.children).toHaveLength(1)
+ expect(treeNode.children[0].id).toEqual(888)
+ })
+
+ it('find treeNodeKey by rawNodeKey', () => {
+ const { treeNode } = createTestData()
+ expect(treeNode.findDkByRk('childs')).toEqual('children')
+ })
+
+ it('expand node', () => {
+ const { rawNode, treeNode } = createTestData()
+ treeNode.expand()
+ expect(treeNode.isExpanded).toEqual(true)
+ expect(rawNode.isExpanded).toEqual(true)
+ })
+})
diff --git a/packages/element3/src/components/Tree/tests/props.spec.ts b/packages/element3/src/components/Tree/tests/props.spec.ts
new file mode 100644
index 000000000..73ed4941c
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/props.spec.ts
@@ -0,0 +1,3 @@
+describe('props.ts', () => {
+ it.todo('coding...')
+})
diff --git a/packages/element3/src/components/Tree/tests/utils/effect.spec.ts b/packages/element3/src/components/Tree/tests/utils/effect.spec.ts
new file mode 100644
index 000000000..e1f00b2dd
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/utils/effect.spec.ts
@@ -0,0 +1,32 @@
+import { watch } from '../../src/utils/effect'
+
+describe('effect.ts', () => {
+ it('watch a obj', () => {
+ const call = jest.fn()
+ const obj = {
+ id: 1,
+ label: 'hello'
+ }
+ const _obj = watch(obj, call)
+
+ _obj.id = 2
+ expect(call).toHaveBeenCalledWith({
+ type: 'set',
+ key: 'id',
+ value: 2,
+ target: obj,
+ receiver: _obj
+ })
+
+ console.log(_obj.id)
+ expect(call).toHaveBeenCalledWith({
+ type: 'get',
+ key: 'id',
+ target: obj,
+ receiver: _obj
+ })
+
+ delete _obj.id
+ expect(call).toHaveBeenCalledWith({ type: 'del', key: 'id', target: obj })
+ })
+})
diff --git a/packages/element3/src/components/Tree/tests/utils/useTools.spec.ts b/packages/element3/src/components/Tree/tests/utils/useTools.spec.ts
new file mode 100644
index 000000000..50cffce37
--- /dev/null
+++ b/packages/element3/src/components/Tree/tests/utils/useTools.spec.ts
@@ -0,0 +1,27 @@
+import { mount } from '@vue/test-utils'
+import { reactive } from 'vue'
+import { useForRefs } from '../../src/utils/useTools'
+
+describe('useTools.ts', () => {
+ it('useForRefs', () => {
+ /**
+ * Implement the Ref array in v-for
+ * https://v3.cn.vuejs.org/guide/migration/array-refs.html
+ */
+ const warpper = mount({
+ template: `
+ {{item}}
+ `,
+ setup() {
+ const children = reactive([])
+ const itemRefs = useForRefs(children)
+ return {
+ arr: [1, 2, 5, 8],
+ itemRefs,
+ children
+ }
+ }
+ })
+ expect(warpper.vm.children).toHaveLength(4)
+ })
+})
diff --git a/packages/element3/src/index.js b/packages/element3/src/index.js
index 46c25fbb3..d6676378a 100644
--- a/packages/element3/src/index.js
+++ b/packages/element3/src/index.js
@@ -42,7 +42,7 @@ import ElTable from '../packages/table'
import ElTableColumn from '../packages/table-column'
import { ElTag } from './components/Tag'
import { ElProgress } from './components/Progress'
-import ElTree from '../packages/tree'
+import { ElTree } from './components/Tree'
import ElPagination from '../packages/pagination'
import { ElBadge } from './components/Badge'
import { ElAvatar } from './components/Avatar'
diff --git a/packages/website/src/i18n/component.json b/packages/website/src/i18n/component.json
index 361c60d01..ea96214d2 100644
--- a/packages/website/src/i18n/component.json
+++ b/packages/website/src/i18n/component.json
@@ -1,30 +1,30 @@
{
- "demo-block": {
- "hide-text": "隐藏代码",
- "show-text": "显示代码",
- "button-text": "在线运行",
- "tooltip-text": "前往 codepen.io 运行此示例"
- },
- "footer": {
- "links": "链接",
- "repo": "代码仓库",
- "community": "社区",
- "changelog": "更新日志",
- "theme": "在线主题生成器",
- "faq": "常见问题",
- "gitter": "在线讨论",
- "starter": "脚手架",
- "feedback": "反馈建议",
- "contribution": "贡献指南",
- "eleme": "饿了么"
- },
- "header": {
- "guide": "指南",
- "components": "组件",
- "theme": "主题",
- "resource": "资源"
- },
- "nav": {
- "dropdown": "版本:"
- }
-}
\ No newline at end of file
+ "demo-block": {
+ "hide-text": "隐藏代码",
+ "show-text": "显示代码",
+ "button-text": "在线运行",
+ "tooltip-text": "前往 codepen.io 运行此示例"
+ },
+ "footer": {
+ "links": "链接",
+ "repo": "代码仓库",
+ "community": "社区",
+ "changelog": "更新日志",
+ "theme": "在线主题生成器",
+ "faq": "常见问题",
+ "gitter": "在线讨论",
+ "starter": "脚手架",
+ "feedback": "反馈建议",
+ "contribution": "贡献指南",
+ "eleme": "饿了么"
+ },
+ "header": {
+ "guide": "指南",
+ "components": "组件",
+ "theme": "主题",
+ "resource": "资源"
+ },
+ "nav": {
+ "dropdown": "版本:"
+ }
+}
diff --git a/packages/website/src/play/index.vue b/packages/website/src/play/index.vue
index e369cabf7..813c2d0a0 100644
--- a/packages/website/src/play/index.vue
+++ b/packages/website/src/play/index.vue
@@ -1,25 +1,59 @@
-
- top
- right
- bottom
- left
-
-
-
- 用户管理
- 配置管理
- 角色管理
- 定时任务补偿
-
+
diff --git a/packages/website/src/route/nav.config.json b/packages/website/src/route/nav.config.json
index ed6469a4e..b021ab276 100644
--- a/packages/website/src/route/nav.config.json
+++ b/packages/website/src/route/nav.config.json
@@ -263,4 +263,4 @@
}
]
}
-]
\ No newline at end of file
+]