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 }