From 2cd46a4890703d302d768e9b117683b30af5bcf5 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Mon, 30 Aug 2021 01:51:32 +0800
Subject: [PATCH] feat(tree, tree-select): add label-field prop
---
CHANGELOG.en-US.md | 3 +-
CHANGELOG.zh-CN.md | 3 +-
.../demos/enUS/custom-field.demo.md | 142 ++++++++++++++++++
.../demos/enUS/index.demo-entry.md | 16 +-
.../demos/zhCN/custom-field.demo.md | 142 ++++++++++++++++++
.../demos/zhCN/index.demo-entry.md | 16 +-
src/tree-select/src/TreeSelect.tsx | 39 +++--
src/tree-select/src/utils.ts | 22 +--
src/tree/demos/enUS/custom-field.demo.md | 46 ++++++
src/tree/demos/enUS/index.demo-entry.md | 8 +-
src/tree/demos/zhCN/custom-field.demo.md | 46 ++++++
src/tree/demos/zhCN/index.demo-entry.md | 8 +-
src/tree/src/Tree.tsx | 42 ++++--
src/tree/src/TreeNodeContent.tsx | 11 +-
src/tree/src/interface.ts | 5 +-
src/tree/src/utils.ts | 12 +-
16 files changed, 494 insertions(+), 67 deletions(-)
create mode 100644 src/tree-select/demos/enUS/custom-field.demo.md
create mode 100644 src/tree-select/demos/zhCN/custom-field.demo.md
create mode 100644 src/tree/demos/enUS/custom-field.demo.md
create mode 100644 src/tree/demos/zhCN/custom-field.demo.md
diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md
index 6832e9b1b..c910c76a1 100644
--- a/CHANGELOG.en-US.md
+++ b/CHANGELOG.en-US.md
@@ -7,7 +7,8 @@
- `n-cascader` add `onUpdateValue` prop.
- `n-auto-complete` add `onUpdateValue` prop.
- `n-data-table`'s column's `renderFilterMenu` add `hide` params.
-- `n-tree` & `n-tree-select` add `node-key` prop.
+- `n-tree` & `n-tree-select` add `key-field` prop.
+- `n-tree` & `n-tree-select` add `label-field` prop.
### Fixes
diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md
index aa5991f7a..63d42c41c 100644
--- a/CHANGELOG.zh-CN.md
+++ b/CHANGELOG.zh-CN.md
@@ -7,7 +7,8 @@
- `n-cascader` 新增 `onUpdateValue` 方法
- `n-auto-complete` 新增 `onUpdateValue` 方法
- `n-data-table` 的列的 `renderFilterMenu` 新增 hide 参数
-- `n-tree` 和 `n-tree-select` 新增 `node-key` 属性
+- `n-tree` 和 `n-tree-select` 新增 `key-field` 属性
+- `n-tree` 和 `n-tree-select` 新增 `label-field` 属性
### Fixes
diff --git a/src/tree-select/demos/enUS/custom-field.demo.md b/src/tree-select/demos/enUS/custom-field.demo.md
new file mode 100644
index 000000000..2d6b9a6c8
--- /dev/null
+++ b/src/tree-select/demos/enUS/custom-field.demo.md
@@ -0,0 +1,142 @@
+# Customize Key and Label Field
+
+Various data would come from backend.
+
+```html
+
+```
+
+```js
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+ setup () {
+ return {
+ options: [
+ {
+ whateverLabel: 'Rubber Soul',
+ whateverKey: 'Rubber Soul',
+ children: [
+ {
+ whateverLabel:
+ "Everybody's Got Something to Hide Except Me and My Monkey",
+ whateverKey:
+ "Everybody's Got Something to Hide Except Me and My Monkey"
+ },
+ {
+ whateverLabel: 'Drive My Car',
+ whateverKey: 'Drive My Car',
+ disabled: true
+ },
+ {
+ whateverLabel: 'Norwegian Wood',
+ whateverKey: 'Norwegian Wood'
+ },
+ {
+ whateverLabel: "You Won't See",
+ whateverKey: "You Won't See",
+ disabled: true
+ },
+ {
+ whateverLabel: 'Nowhere Man',
+ whateverKey: 'Nowhere Man'
+ },
+ {
+ whateverLabel: 'Think For Yourself',
+ whateverKey: 'Think For Yourself'
+ },
+ {
+ whateverLabel: 'The Word',
+ whateverKey: 'The Word'
+ },
+ {
+ whateverLabel: 'Michelle',
+ whateverKey: 'Michelle',
+ disabled: true
+ },
+ {
+ whateverLabel: 'What goes on',
+ whateverKey: 'What goes on'
+ },
+ {
+ whateverLabel: 'Girl',
+ whateverKey: 'Girl'
+ },
+ {
+ whateverLabel: "I'm looking through you",
+ whateverKey: "I'm looking through you"
+ },
+ {
+ whateverLabel: 'In My Life',
+ whateverKey: 'In My Life'
+ },
+ {
+ whateverLabel: 'Wait',
+ whateverKey: 'Wait'
+ }
+ ]
+ },
+ {
+ whateverLabel: 'Let It Be',
+ whateverKey: 'Let It Be Album',
+ children: [
+ {
+ whateverLabel: 'Two Of Us',
+ whateverKey: 'Two Of Us'
+ },
+ {
+ whateverLabel: 'Dig A Pony',
+ whateverKey: 'Dig A Pony'
+ },
+ {
+ whateverLabel: 'Across The Universe',
+ whateverKey: 'Across The Universe'
+ },
+ {
+ whateverLabel: 'I Me Mine',
+ whateverKey: 'I Me Mine'
+ },
+ {
+ whateverLabel: 'Dig It',
+ whateverKey: 'Dig It'
+ },
+ {
+ whateverLabel: 'Let It Be',
+ whateverKey: 'Let It Be'
+ },
+ {
+ whateverLabel: 'Maggie Mae',
+ whateverKey: 'Maggie Mae'
+ },
+ {
+ whateverLabel: "I've Got A Feeling",
+ whateverKey: "I've Got A Feeling"
+ },
+ {
+ whateverLabel: 'One After 909',
+ whateverKey: 'One After 909'
+ },
+ {
+ whateverLabel: 'The Long And Winding Road',
+ whateverKey: 'The Long And Winding Road'
+ },
+ {
+ whateverLabel: 'For You Blue',
+ whateverKey: 'For You Blue'
+ },
+ {
+ whateverLabel: 'Get Back',
+ whateverKey: 'Get Back'
+ }
+ ]
+ }
+ ]
+ }
+ }
+})
+```
diff --git a/src/tree-select/demos/enUS/index.demo-entry.md b/src/tree-select/demos/enUS/index.demo-entry.md
index f5750a391..8a40dc5dd 100644
--- a/src/tree-select/demos/enUS/index.demo-entry.md
+++ b/src/tree-select/demos/enUS/index.demo-entry.md
@@ -6,6 +6,7 @@ It's said that 99% of the people can't distinguish it from cascader.
```demo
basic
+custom-field
multiple
checkbox
filterable
@@ -29,9 +30,10 @@ debug
| expanded-keys | `Array` | `undefined` | Expanded keys. |
| filterable | `boolean` | `false` | Whether the tree select is disabled. |
| filter | `(pattern: string, option: TreeSelectOption) => boolean` | - | Filter function. |
+| key-field | `string` | `'key'` | The key field in `TreeOption`. |
+| label-field | `string` | `'label'` | The the label field in `TreeOption`. |
| max-tag-count | `number \| 'responsive'` | `undefined` | Max tag count to be shown in multiple mode. Set to `'responsive'` will keep all tags in the same row. |
| multiple | `boolean` | `false` | Whether to support multiple select. |
-| node-key | `string` | `undefined` | Replace the key in `TreeSelectOption`. |
| options | `TreeSelectOption[]` | `[]` | Options. |
| placeholder | `string` | `'Please Select'` | Placeholder. |
| separator | `string` | `' / '` | Option value separator. |
@@ -46,9 +48,9 @@ debug
### TreeSelectOption Properties
-| Name | Type | Description |
-| --------- | -------------------- | ------------------------------------ |
-| key | `string \| number` | Key of the option. Should be unique. |
-| label | `string` | Displayed content of the option. |
-| children? | `TreeSelectOption[]` | Child options of the option. |
-| disabled? | `boolean` | Whether to disabled the option. |
+| Name | Type | Description |
+| --- | --- | --- |
+| key | `string \| number` | Key of the option. Should be unique. You can use `key-field` to customize the field name. |
+| label | `string` | Displayed content of the option. You can use `label-field` to customize the field name. |
+| children? | `TreeSelectOption[]` | Child options of the option. |
+| disabled? | `boolean` | Whether to disabled the option. |
diff --git a/src/tree-select/demos/zhCN/custom-field.demo.md b/src/tree-select/demos/zhCN/custom-field.demo.md
new file mode 100644
index 000000000..d6ebeb380
--- /dev/null
+++ b/src/tree-select/demos/zhCN/custom-field.demo.md
@@ -0,0 +1,142 @@
+# 自定义 key 和 label 的字段
+
+后端会传来各种各样的数据。
+
+```html
+
+```
+
+```js
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+ setup () {
+ return {
+ options: [
+ {
+ whateverLabel: 'Rubber Soul',
+ whateverKey: 'Rubber Soul',
+ children: [
+ {
+ whateverLabel:
+ "Everybody's Got Something to Hide Except Me and My Monkey",
+ whateverKey:
+ "Everybody's Got Something to Hide Except Me and My Monkey"
+ },
+ {
+ whateverLabel: 'Drive My Car',
+ whateverKey: 'Drive My Car',
+ disabled: true
+ },
+ {
+ whateverLabel: 'Norwegian Wood',
+ whateverKey: 'Norwegian Wood'
+ },
+ {
+ whateverLabel: "You Won't See",
+ whateverKey: "You Won't See",
+ disabled: true
+ },
+ {
+ whateverLabel: 'Nowhere Man',
+ whateverKey: 'Nowhere Man'
+ },
+ {
+ whateverLabel: 'Think For Yourself',
+ whateverKey: 'Think For Yourself'
+ },
+ {
+ whateverLabel: 'The Word',
+ whateverKey: 'The Word'
+ },
+ {
+ whateverLabel: 'Michelle',
+ whateverKey: 'Michelle',
+ disabled: true
+ },
+ {
+ whateverLabel: 'What goes on',
+ whateverKey: 'What goes on'
+ },
+ {
+ whateverLabel: 'Girl',
+ whateverKey: 'Girl'
+ },
+ {
+ whateverLabel: "I'm looking through you",
+ whateverKey: "I'm looking through you"
+ },
+ {
+ whateverLabel: 'In My Life',
+ whateverKey: 'In My Life'
+ },
+ {
+ whateverLabel: 'Wait',
+ whateverKey: 'Wait'
+ }
+ ]
+ },
+ {
+ whateverLabel: 'Let It Be',
+ whateverKey: 'Let It Be Album',
+ children: [
+ {
+ whateverLabel: 'Two Of Us',
+ whateverKey: 'Two Of Us'
+ },
+ {
+ whateverLabel: 'Dig A Pony',
+ whateverKey: 'Dig A Pony'
+ },
+ {
+ whateverLabel: 'Across The Universe',
+ whateverKey: 'Across The Universe'
+ },
+ {
+ whateverLabel: 'I Me Mine',
+ whateverKey: 'I Me Mine'
+ },
+ {
+ whateverLabel: 'Dig It',
+ whateverKey: 'Dig It'
+ },
+ {
+ whateverLabel: 'Let It Be',
+ whateverKey: 'Let It Be'
+ },
+ {
+ whateverLabel: 'Maggie Mae',
+ whateverKey: 'Maggie Mae'
+ },
+ {
+ whateverLabel: "I've Got A Feeling",
+ whateverKey: "I've Got A Feeling"
+ },
+ {
+ whateverLabel: 'One After 909',
+ whateverKey: 'One After 909'
+ },
+ {
+ whateverLabel: 'The Long And Winding Road',
+ whateverKey: 'The Long And Winding Road'
+ },
+ {
+ whateverLabel: 'For You Blue',
+ whateverKey: 'For You Blue'
+ },
+ {
+ whateverLabel: 'Get Back',
+ whateverKey: 'Get Back'
+ }
+ ]
+ }
+ ]
+ }
+ }
+})
+```
diff --git a/src/tree-select/demos/zhCN/index.demo-entry.md b/src/tree-select/demos/zhCN/index.demo-entry.md
index 6451608f5..c38b0f2ea 100644
--- a/src/tree-select/demos/zhCN/index.demo-entry.md
+++ b/src/tree-select/demos/zhCN/index.demo-entry.md
@@ -6,6 +6,7 @@
```demo
basic
+custom-field
multiple
checkbox
filterable
@@ -29,10 +30,11 @@ debug
| expanded-keys | `Array` | `undefined` | 展开节点的 key |
| filterable | `boolean` | `false` | 是否可过滤 |
| filter | `(pattern: string, option: TreeSelectOption) => boolean` | - | 过滤器函数 |
+| key-field | `string` | `'key'` | `TreeOption` 中的 key 字段名 |
+| label-field | `string` | `'label'` | 替代 `TreeOption` 中的 label 字段名 |
| leaf-only | `boolean` | `false` | 是否开启仅末层树节点可选 |
| max-tag-count | `number \| 'responsive'` | `undefined` | 多选时最多直接显示多少选项,设为 `'responsive'` 会保证最多一行 |
| multiple | `boolean` | `false` | 是否支持多选 |
-| node-key | `string` | `undefined` | 替代 `TreeSelectOption` 中的 key |
| options | `TreeSelectOption[]` | `[]` | 选项 |
| placeholder | `string` | `'请选择'` | 占位信息 |
| separator | `string` | `' / '` | 数据分隔符 |
@@ -47,9 +49,9 @@ debug
### TreeSelectOption Properties
-| 名称 | 类型 | 说明 |
-| --------- | -------------------- | -------------------- |
-| key | `string \| number` | 选项的 key,需要唯一 |
-| label | `string` | 选项的显示内容 |
-| children? | `TreeSelectOption[]` | 节点的子选项 |
-| disabled? | `boolean` | 是否禁用选项 |
+| 名称 | 类型 | 说明 |
+| --- | --- | --- |
+| key | `string \| number` | 选项的 key,需要唯一,可使用 `key-field` 修改字段名 |
+| label | `string` | 选项的显示内容,可使用 `label-field` 修改字段名 |
+| children? | `TreeSelectOption[]` | 节点的子选项 |
+| disabled? | `boolean` | 是否禁用选项 |
diff --git a/src/tree-select/src/TreeSelect.tsx b/src/tree-select/src/TreeSelect.tsx
index c58c391cd..8822802f7 100644
--- a/src/tree-select/src/TreeSelect.tsx
+++ b/src/tree-select/src/TreeSelect.tsx
@@ -61,7 +61,6 @@ const props = {
type: Boolean,
default: true
},
- nodeKey: String,
cascade: Boolean,
checkable: Boolean,
clearable: Boolean,
@@ -154,6 +153,17 @@ export default defineComponent({
const controlledShowRef = toRef(props, 'show')
const mergedShowRef = useMergedState(controlledShowRef, uncontrolledShowRef)
const patternRef = ref('')
+ const mergedFilterRef = computed(() => {
+ const { filter } = props
+ if (filter) return filter
+ const { labelField } = props
+ return (pattern: string, node: TreeSelectOption): boolean => {
+ if (!pattern.length) return true
+ return ((node as any)[labelField] as string)
+ .toLowerCase()
+ .includes(pattern.toLowerCase())
+ }
+ })
const filteredTreeInfoRef = computed<{
filteredTree: TreeSelectOption[]
highlightKeySet: Set | undefined
@@ -167,26 +177,31 @@ export default defineComponent({
}
}
const { value: pattern } = patternRef
- if (!pattern.length || !props.filter) {
+ if (!pattern.length || !mergedFilterRef.value) {
return {
filteredTree: props.options,
highlightKeySet: undefined,
expandedKeys: undefined
}
}
- return filterTree(props.options, props.filter, pattern)
+ return filterTree(
+ props.options,
+ mergedFilterRef.value,
+ pattern,
+ props.keyField
+ )
})
// used to resolve selected options
const dataTreeMateRef = computed(() =>
createTreeMate(
props.options,
- createTreeMateOptions(props.nodeKey)
+ createTreeMateOptions(props.keyField)
)
)
const displayTreeMateRef = computed(() =>
createTreeMate(
filteredTreeInfoRef.value.filteredTree,
- createTreeMateOptions(props.nodeKey)
+ createTreeMateOptions(props.keyField)
)
)
const { value: initMergedValue } = mergedValueRef
@@ -244,7 +259,7 @@ export default defineComponent({
}
})
const selectedOptionRef = computed(() => {
- const { multiple, showPath, separator } = props
+ const { multiple, showPath, separator, labelField } = props
if (multiple) return null
const { value: mergedValue } = mergedValueRef
if (!Array.isArray(mergedValue) && mergedValue !== null) {
@@ -255,9 +270,10 @@ export default defineComponent({
? treeOption2SelectOptionWithPath(
tmNode,
treeMate.getPath(mergedValue).treeNodePath,
- separator
+ separator,
+ labelField
)
- : treeOption2SelectOption(tmNode)
+ : treeOption2SelectOption(tmNode, labelField)
}
}
return null
@@ -269,6 +285,7 @@ export default defineComponent({
if (Array.isArray(mergedValue)) {
const res: SelectBaseOption[] = []
const { value: treeMate } = dataTreeMateRef
+ const { keyField, labelField } = props
mergedValue.forEach((value) => {
const tmNode = treeMate.getNode(value)
if (tmNode !== null) {
@@ -277,9 +294,10 @@ export default defineComponent({
? treeOption2SelectOptionWithPath(
tmNode,
treeMate.getPath(value).treeNodePath,
- separator
+ separator,
+ keyField
)
- : treeOption2SelectOption(tmNode)
+ : treeOption2SelectOption(tmNode, labelField)
)
}
})
@@ -694,6 +712,7 @@ export default defineComponent({
animated={false}
data={filteredTreeInfo.filteredTree}
cancelable={multiple}
+ labelField={this.labelField}
theme={mergedTheme.peers.Tree}
themeOverrides={
mergedTheme.peerOverrides.Tree
diff --git a/src/tree-select/src/utils.ts b/src/tree-select/src/utils.ts
index 6a83c0fb5..dfbb694c1 100644
--- a/src/tree-select/src/utils.ts
+++ b/src/tree-select/src/utils.ts
@@ -3,11 +3,13 @@ import { Key } from '../../tree/src/interface'
import { TreeSelectTmNode, TreeSelectOption } from './interface'
export function treeOption2SelectOption (
- tmNode: TreeSelectTmNode
+ tmNode: TreeSelectTmNode,
+ labelField: string
): SelectBaseOption {
const { rawNode } = tmNode
return {
...rawNode,
+ label: (rawNode as any)[labelField] as string,
value: tmNode.key
}
}
@@ -15,20 +17,22 @@ export function treeOption2SelectOption (
export function treeOption2SelectOptionWithPath (
tmNode: TreeSelectTmNode,
path: TreeSelectTmNode[],
- separator: string
+ separator: string,
+ labelField: string
): SelectBaseOption {
const { rawNode } = tmNode
return {
...rawNode,
value: tmNode.key,
- label: path.map((v) => v.rawNode.label).join(separator)
+ label: path.map((v) => v.rawNode[labelField]).join(separator)
}
}
export function filterTree (
tree: TreeSelectOption[],
filter: (pattern: string, v: TreeSelectOption) => boolean,
- pattern: string
+ pattern: string,
+ keyField: string
): {
filteredTree: TreeSelectOption[]
expandedKeys: Key[]
@@ -44,10 +48,10 @@ export function filterTree (
t.forEach((n) => {
path.push(n)
if (filter(pattern, n)) {
- visitedTailKeys.add(n.key)
- highlightKeySet.add(n.key)
+ visitedTailKeys.add((n as any)[keyField] as Key)
+ highlightKeySet.add((n as any)[keyField] as Key)
for (let i = path.length - 2; i >= 0; --i) {
- const { key } = path[i]
+ const key: Key = (path[i] as any)[keyField]
if (!visitedNonTailKeys.has(key)) {
visitedNonTailKeys.add(key)
if (visitedTailKeys.has(key)) {
@@ -67,7 +71,7 @@ export function filterTree (
visit(tree)
function build (t: TreeSelectOption[], sibs: TreeSelectOption[]): void {
t.forEach((n) => {
- const { key } = n
+ const key = (n as any)[keyField] as Key
const isVisitedTail = visitedTailKeys.has(key)
const isVisitedNonTail = visitedNonTailKeys.has(key)
if (!isVisitedTail && !isVisitedNonTail) return
@@ -78,7 +82,7 @@ export function filterTree (
sibs.push(n)
} else {
// It it is not visited path tail, use cloned node
- expandedKeys.push(n.key)
+ expandedKeys.push(key)
const clonedNode = { ...n, children: [] }
sibs.push(clonedNode)
build(children, clonedNode.children)
diff --git a/src/tree/demos/enUS/custom-field.demo.md b/src/tree/demos/enUS/custom-field.demo.md
new file mode 100644
index 000000000..5d4e7ba43
--- /dev/null
+++ b/src/tree/demos/enUS/custom-field.demo.md
@@ -0,0 +1,46 @@
+# Customize Key and Label Field
+
+Various data would come from backend.
+
+```html
+
+```
+
+```js
+import { defineComponent, ref } from 'vue'
+
+function createData (level = 4, baseKey = '') {
+ if (!level) return undefined
+ return Array.apply(null, { length: 6 - level }).map((_, index) => {
+ const key = '' + baseKey + level + index
+ return {
+ whateverLabel: createLabel(level),
+ whateverKey: key,
+ children: createData(level - 1, key)
+ }
+ })
+}
+
+function createLabel (level) {
+ if (level === 4) return 'Out of Tao, One is born'
+ if (level === 3) return 'Out of One, Two'
+ if (level === 2) return 'Out of Two, Three'
+ if (level === 1) return 'Out of Three, the created universe'
+}
+
+export default defineComponent({
+ setup () {
+ return {
+ data: createData(),
+ defaultExpandedKeys: ref(['40', '41'])
+ }
+ }
+})
+```
diff --git a/src/tree/demos/enUS/index.demo-entry.md b/src/tree/demos/enUS/index.demo-entry.md
index ccbc3f946..1429c96a5 100644
--- a/src/tree/demos/enUS/index.demo-entry.md
+++ b/src/tree/demos/enUS/index.demo-entry.md
@@ -8,6 +8,7 @@ What's more, not only biology, I forget balanced tree everytime after I revise i
```demo
basic
+custom-field
cascade
multiple
filter
@@ -41,9 +42,10 @@ batch-render
| expand-on-dragenter | `boolean` | `true` | Whether to expand nodes after dragenter. |
| expanded-keys | `Array` | `undefined` | If set, expanded status will work in controlled manner. |
| filter | `(node: TreeOption) => boolean` | `undefined` | A simple string based filter. |
+| key-field | `string` | `'key'` | The key field in `TreeOption`. |
+| label-field | `string` | `'label'` | The the label field in `TreeOption`. |
| leaf-only | `boolean` | `false` | Whether to open or not, only the bottom tree node is optional. |
| multiple | `boolean` | `false` | Whether to allow multiple selection of nodes. |
-| node-key | `string` | `undefined` | Replace the key in `TreeOption`. |
| on-load | `(node: TreeOption) => Promise` | `undefined` | Callback function for asynchronously loading data. |
| pattern | `string` | `''` | What to search by default. |
| remote | `boolean` | `false` | Whether to load nodes async. It should work with `on-load`. |
@@ -66,8 +68,8 @@ batch-render
| Name | Type | Description |
| --- | --- | --- |
-| key | `string \| number` | Key of the node, should be unique. |
-| label | `string` | Label of the node. |
+| key | `string \| number` | Key of the node, should be unique. You can use `key-field` to customize the field name. |
+| label | `string` | Label of the node. You can use `label-field` to customize the field name. |
| checkboxDisabled? | `boolean` | Whether the checkbox is disabled. |
| children? | `TreeOption[]` | Child nodes of the node. |
| disabled? | `boolean` | Whether the node is disabled. |
diff --git a/src/tree/demos/zhCN/custom-field.demo.md b/src/tree/demos/zhCN/custom-field.demo.md
new file mode 100644
index 000000000..ecb7b1435
--- /dev/null
+++ b/src/tree/demos/zhCN/custom-field.demo.md
@@ -0,0 +1,46 @@
+# 自定义 key 和 label 的字段
+
+后端会传来各种各样的数据。
+
+```html
+
+```
+
+```js
+import { defineComponent, ref } from 'vue'
+
+function createData (level = 4, baseKey = '') {
+ if (!level) return undefined
+ return Array.apply(null, { length: 6 - level }).map((_, index) => {
+ const key = '' + baseKey + level + index
+ return {
+ whateverLabel: createLabel(level),
+ whateverKey: key,
+ children: createData(level - 1, key)
+ }
+ })
+}
+
+function createLabel (level) {
+ if (level === 4) return '道生一'
+ if (level === 3) return '一生二'
+ if (level === 2) return '二生三'
+ if (level === 1) return '三生万物'
+}
+
+export default defineComponent({
+ setup () {
+ return {
+ data: createData(),
+ defaultExpandedKeys: ref(['40', '41'])
+ }
+ }
+})
+```
diff --git a/src/tree/demos/zhCN/index.demo-entry.md b/src/tree/demos/zhCN/index.demo-entry.md
index c948e94d3..7daf34974 100644
--- a/src/tree/demos/zhCN/index.demo-entry.md
+++ b/src/tree/demos/zhCN/index.demo-entry.md
@@ -8,6 +8,7 @@
```demo
basic
+custom-field
cascade
multiple
filter
@@ -41,9 +42,10 @@ batch-render
| expand-on-dragenter | `boolean` | `true` | 是否在拖入后展开节点 |
| expanded-keys | `Array` | `undefined` | 如果设定则展开受控 |
| filter | `(node: TreeOption) => boolean` | `undefined` | 一个简单的字符串过滤算法 |
+| key-field | `string` | `'key'` | `TreeOption` 中的 key 字段名 |
+| label-field | `string` | `'label'` | 替代 `TreeOption` 中的 label 字段名 |
| leaf-only | `boolean` | `false` | 是否开启仅末层树节点可选 |
| multiple | `boolean` | `false` | 是否允许节点多选 |
-| node-key | `string` | `undefined` | 替代 `TreeOption` 中的 key |
| on-load | `(node: TreeOption) => Promise` | `undefined` | 异步加载数据的回调函数 |
| pattern | `string` | `''` | 默认搜索的内容 |
| remote | `boolean` | `false` | 是否异步获取选项,和 `onLoad` 配合 |
@@ -66,8 +68,8 @@ batch-render
| 名称 | 类型 | 说明 |
| --- | --- | --- |
-| key | `string \| number` | 节点的 `key`,需要唯一 |
-| label | `string` | 节点的内容 |
+| key | `string \| number` | 节点的 `key`,需要唯一,可使用 `key-field` 修改字段名 |
+| label | `string` | 节点的内容,可使用 `label-field` 修改字段名 |
| checkboxDisabled? | `boolean` | 是否禁用节点的 `checkbox` |
| children? | `TreeOption[]` | 节点的子节点 |
| disabled? | `boolean` | 是否禁用节点 |
diff --git a/src/tree/src/Tree.tsx b/src/tree/src/Tree.tsx
index fd3c6fde1..f39be81e1 100644
--- a/src/tree/src/Tree.tsx
+++ b/src/tree/src/Tree.tsx
@@ -31,7 +31,7 @@ import type { ScrollbarInst } from '../../scrollbar'
import { treeLight } from '../styles'
import type { TreeTheme } from '../styles'
import NTreeNode from './TreeNode'
-import { keysWithFilter, emptyImage, defaultFilter } from './utils'
+import { keysWithFilter, emptyImage } from './utils'
import { useKeyboard } from './keyboard'
import {
TreeDragInfo,
@@ -62,11 +62,11 @@ import style from './styles/index.cssr'
const ITEM_SIZE = 30 // 24 + 3 + 3
export function createTreeMateOptions (
- nodeKey: string | undefined
+ keyField: string
): TreeMateOptions {
return {
getKey (node: T) {
- return nodeKey ? (node as any)[nodeKey] : (node as any).key
+ return (node as any)[keyField]
},
getDisabled (node: T) {
return !!((node as any).disabled || (node as any).checkboxDisabled)
@@ -75,12 +75,17 @@ export function createTreeMateOptions (
}
export const treeSharedProps = {
- filter: {
- type: Function as PropType<(pattern: string, node: TreeOption) => boolean>,
- default: defaultFilter
- },
+ filter: Function as PropType<(pattern: string, node: TreeOption) => boolean>,
defaultExpandAll: Boolean,
expandedKeys: Array as PropType,
+ keyField: {
+ type: String,
+ default: 'key'
+ },
+ labelField: {
+ type: String,
+ default: 'label'
+ },
defaultExpandedKeys: {
type: Array as PropType,
default: () => []
@@ -107,7 +112,6 @@ const treeProps = {
type: Boolean,
default: true
},
- nodeKey: String,
checkable: Boolean,
draggable: Boolean,
blockNode: Boolean,
@@ -232,7 +236,7 @@ export default defineComponent({
: computed(() =>
createTreeMate(
props.data,
- createTreeMateOptions(props.nodeKey)
+ createTreeMateOptions(props.keyField)
)
)
const dataTreeMateRef = props.internalDataTreeMate
@@ -314,6 +318,18 @@ export default defineComponent({
return droppingNode.parent
})
+ const mergedFilterRef = computed(() => {
+ const { filter } = props
+ if (filter) return filter
+ const { labelField } = props
+ return (pattern: string, node: TreeOption): boolean => {
+ if (!pattern.length) return true
+ return ((node as any)[labelField] as string)
+ .toLowerCase()
+ .includes(pattern.toLowerCase())
+ }
+ })
+
// shallow watch data
watch(
toRef(props, 'data'),
@@ -329,7 +345,12 @@ export default defineComponent({
watch(toRef(props, 'pattern'), (value) => {
if (value) {
const { expandedKeys: expandedKeysAfterChange, highlightKeySet } =
- keysWithFilter(props.data, props.pattern, props.filter)
+ keysWithFilter(
+ props.data,
+ props.pattern,
+ props.keyField,
+ mergedFilterRef.value
+ )
uncontrolledHighlightKeySetRef.value = highlightKeySet
doUpdateExpandedKeys(expandedKeysAfterChange)
} else {
@@ -1013,6 +1034,7 @@ export default defineComponent({
renderLabelRef: toRef(props, 'renderLabel'),
renderPrefixRef: toRef(props, 'renderPrefix'),
renderSuffixRef: toRef(props, 'renderSuffix'),
+ labelFieldRef: toRef(props, 'labelField'),
handleSwitcherClick,
handleDragEnd,
handleDragEnter,
diff --git a/src/tree/src/TreeNodeContent.tsx b/src/tree/src/TreeNodeContent.tsx
index 043a61edf..022cd9688 100644
--- a/src/tree/src/TreeNodeContent.tsx
+++ b/src/tree/src/TreeNodeContent.tsx
@@ -9,10 +9,7 @@ export default defineComponent({
type: String,
required: true
},
- disabled: {
- type: Boolean,
- default: false
- },
+ disabled: Boolean,
checked: Boolean,
selected: Boolean,
onClick: Function as PropType<(e: MouseEvent) => void>,
@@ -23,7 +20,7 @@ export default defineComponent({
}
},
setup (props) {
- const { renderLabelRef, renderPrefixRef, renderSuffixRef } =
+ const { renderLabelRef, renderPrefixRef, renderSuffixRef, labelFieldRef } =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
inject(treeInjectionKey)!
const selfRef = ref(null)
@@ -39,12 +36,14 @@ export default defineComponent({
renderLabel: renderLabelRef,
renderPrefix: renderPrefixRef,
renderSuffix: renderSuffixRef,
+ labelField: labelFieldRef,
handleClick
}
},
render () {
const {
clsPrefix,
+ labelField,
checked = false,
selected = false,
renderLabel,
@@ -54,7 +53,7 @@ export default defineComponent({
onDragstart,
tmNode: {
rawNode,
- rawNode: { prefix, label, suffix }
+ rawNode: { prefix, suffix, [labelField]: label }
}
} = this
return (
diff --git a/src/tree/src/interface.ts b/src/tree/src/interface.ts
index 4e27303f6..8bf545376 100644
--- a/src/tree/src/interface.ts
+++ b/src/tree/src/interface.ts
@@ -6,8 +6,8 @@ import type { TreeTheme } from '../styles'
export type Key = string | number
export interface TreeOptionBase {
- key: Key
- label: string
+ key?: Key
+ label?: string
checkboxDisabled?: boolean
disabled?: boolean
isLeaf?: boolean
@@ -99,6 +99,7 @@ export interface TreeInjection {
renderLabelRef: Ref
renderPrefixRef: Ref
renderSuffixRef: Ref
+ labelFieldRef: Ref
handleSwitcherClick: (node: TreeNode) => void
handleSelect: (node: TreeNode) => void
handleCheck: (node: TreeNode, checked: boolean) => void
diff --git a/src/tree/src/utils.ts b/src/tree/src/utils.ts
index 90c78d365..be9c3a883 100644
--- a/src/tree/src/utils.ts
+++ b/src/tree/src/utils.ts
@@ -15,6 +15,7 @@ function traverse (
export function keysWithFilter (
nodes: TreeOption[],
pattern: string,
+ keyField: string,
filter: (pattern: string, node: TreeOption) => boolean
): {
expandedKeys: Key[]
@@ -28,10 +29,10 @@ export function keysWithFilter (
(node) => {
path.push(node)
if (filter(pattern, node)) {
- highlightKeySet.add(node.key)
+ highlightKeySet.add((node as any)[keyField])
for (let i = path.length - 2; i >= 0; --i) {
- if (!keys.has(path[i].key)) {
- keys.add(path[i].key)
+ if (!keys.has((path[i] as any)[keyField])) {
+ keys.add((path[i] as any)[keyField])
} else {
return
}
@@ -55,9 +56,4 @@ if (typeof window !== 'undefined') {
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
}
-export const defaultFilter = (pattern: string, node: TreeOption): boolean => {
- if (!pattern.length) return true
- return node.label.toLowerCase().includes(pattern.toLowerCase())
-}
-
export { emptyImage }