mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-24 12:45:18 +08:00
feat(tree, tree-select): add label-field prop
This commit is contained in:
parent
5f26ecf0e1
commit
2cd46a4890
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
142
src/tree-select/demos/enUS/custom-field.demo.md
Normal file
142
src/tree-select/demos/enUS/custom-field.demo.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Customize Key and Label Field
|
||||
|
||||
Various data would come from backend.
|
||||
|
||||
```html
|
||||
<n-tree-select
|
||||
:options="options"
|
||||
default-value="Drive My Car"
|
||||
label-field="whateverLabel"
|
||||
key-field="whateverKey"
|
||||
/>
|
||||
```
|
||||
|
||||
```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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
@ -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<string \| number>` | `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. |
|
||||
|
142
src/tree-select/demos/zhCN/custom-field.demo.md
Normal file
142
src/tree-select/demos/zhCN/custom-field.demo.md
Normal file
@ -0,0 +1,142 @@
|
||||
# 自定义 key 和 label 的字段
|
||||
|
||||
后端会传来各种各样的数据。
|
||||
|
||||
```html
|
||||
<n-tree-select
|
||||
:options="options"
|
||||
default-value="Drive My Car"
|
||||
label-field="whateverLabel"
|
||||
key-field="whateverKey"
|
||||
/>
|
||||
```
|
||||
|
||||
```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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
@ -6,6 +6,7 @@
|
||||
|
||||
```demo
|
||||
basic
|
||||
custom-field
|
||||
multiple
|
||||
checkbox
|
||||
filterable
|
||||
@ -29,10 +30,11 @@ debug
|
||||
| expanded-keys | `Array<string \| number>` | `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` | 是否禁用选项 |
|
||||
|
@ -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<Key> | 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<TreeSelectOption>(
|
||||
props.options,
|
||||
createTreeMateOptions(props.nodeKey)
|
||||
createTreeMateOptions(props.keyField)
|
||||
)
|
||||
)
|
||||
const displayTreeMateRef = computed(() =>
|
||||
createTreeMate<TreeSelectOption>(
|
||||
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
|
||||
|
@ -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)
|
||||
|
46
src/tree/demos/enUS/custom-field.demo.md
Normal file
46
src/tree/demos/enUS/custom-field.demo.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Customize Key and Label Field
|
||||
|
||||
Various data would come from backend.
|
||||
|
||||
```html
|
||||
<n-tree
|
||||
block-line
|
||||
:data="data"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
key-field="whateverKey"
|
||||
label-field="whateverLabel"
|
||||
selectable
|
||||
/>
|
||||
```
|
||||
|
||||
```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'])
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
@ -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<string \| number>` | `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<void>` | `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. |
|
||||
|
46
src/tree/demos/zhCN/custom-field.demo.md
Normal file
46
src/tree/demos/zhCN/custom-field.demo.md
Normal file
@ -0,0 +1,46 @@
|
||||
# 自定义 key 和 label 的字段
|
||||
|
||||
后端会传来各种各样的数据。
|
||||
|
||||
```html
|
||||
<n-tree
|
||||
block-line
|
||||
:data="data"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
key-field="whateverKey"
|
||||
label-field="whateverLabel"
|
||||
selectable
|
||||
/>
|
||||
```
|
||||
|
||||
```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'])
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
@ -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<string \| number>` | `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<void>` | `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` | 是否禁用节点 |
|
||||
|
@ -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<T> (
|
||||
nodeKey: string | undefined
|
||||
keyField: string
|
||||
): TreeMateOptions<T, T, T> {
|
||||
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<T> (
|
||||
}
|
||||
|
||||
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<Key[]>,
|
||||
keyField: {
|
||||
type: String,
|
||||
default: 'key'
|
||||
},
|
||||
labelField: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
defaultExpandedKeys: {
|
||||
type: Array as PropType<Key[]>,
|
||||
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<TreeOption>(
|
||||
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,
|
||||
|
@ -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<HTMLElement | null>(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 (
|
||||
|
@ -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<RenderLabel | undefined>
|
||||
renderPrefixRef: Ref<RenderPrefix | undefined>
|
||||
renderSuffixRef: Ref<RenderSuffix | undefined>
|
||||
labelFieldRef: Ref<string>
|
||||
handleSwitcherClick: (node: TreeNode<TreeOption>) => void
|
||||
handleSelect: (node: TreeNode<TreeOption>) => void
|
||||
handleCheck: (node: TreeNode<TreeOption>, checked: boolean) => void
|
||||
|
@ -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 }
|
||||
|
Loading…
Reference in New Issue
Block a user