Tree: support scoped slot (#9686)

This commit is contained in:
杨奕 2018-02-06 16:45:27 +08:00 committed by GitHub
parent 3bc67fa575
commit 248b1bf8de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 463 additions and 191 deletions

View File

@ -17,6 +17,35 @@
.filter-tree {
margin-top: 20px;
}
.custom-tree-container {
display: flex;
margin: -24px;
}
.block {
flex: 1;
padding: 8px 24px 24px;
&:first-child {
border-right: solid 1px #eff2f6;
}
> p {
text-align: center;
margin: 0;
line-height: 4;
}
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
}
</style>
@ -218,11 +247,11 @@
this.$refs.tree.setCheckedNodes([
{
id: 5,
label: '二级 2-1'
label: 'Level two 2-1'
},
{
id: 9,
label: '三级 1-1-1'
label: 'Level three 1-1-1'
}
]);
},
@ -249,13 +278,11 @@
renderContent(h, { node, data, store }) {
return (
<span style="flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px;">
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<span>{node.label}</span>
</span>
<span>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
},
@ -272,6 +299,7 @@
data2,
data3,
data4: JSON.parse(JSON.stringify(data2)),
data5: JSON.parse(JSON.stringify(data2)),
regions,
defaultProps,
props,
@ -685,63 +713,92 @@ Tree nodes can be initially expanded or checked
### Custom node content
The content of tree nodes can be customized, so you can add icons or buttons as you will
:::demo Use `render-content` to assign a render function that returns the content of tree nodes. See Vue's documentation for a detailed introduction of render functions. Note that this demo can't run in jsfiddle because it doesn't support JSX syntax. In a real project, `render-content` will work if relevant dependencies are correctly configured.
:::demo There are two ways to customize template for tree nodes: `render-content` and scoped slot. Use `render-content` to assign a render function that returns the content of tree nodes. See Vue's documentation for a detailed introduction of render functions. If you prefer scoped slot, you'll have access to `node` and `data` in the scope, standing for the TreeNode object and node data of the current node respectively. Note that the `render-content` demo can't run in jsfiddle because it doesn't support JSX syntax. In a real project, `render-content` will work if relevant dependencies are correctly configured.
```html
<el-tree
:data="data4"
:props="defaultProps"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
<div class="custom-tree-container">
<div class="block">
<p>Using render-content</p>
<el-tree
:data="data4"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
</div>
<div class="block">
<p>Using scoped slot</p>
<el-tree
:data="data5"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
size="mini"
@click="() => append(data)">
Append
</el-button>
<el-button
type="text"
size="mini"
@click="() => remove(node, data)">
Delete
</el-button>
</span>
</span>
</el-tree>
</div>
</div>
<script>
let id = 1000;
export default {
data() {
const data = [{
id: 1,
label: 'Level one 1',
children: [{
id: 4,
label: 'Level two 1-1',
children: [{
id: 9,
label: 'Level three 1-1-1'
}, {
id: 10,
label: 'Level three 1-1-2'
}]
}]
}, {
id: 2,
label: 'Level one 2',
children: [{
id: 5,
label: 'Level two 2-1'
}, {
id: 6,
label: 'Level two 2-2'
}]
}, {
id: 3,
label: 'Level one 3',
children: [{
id: 7,
label: 'Level two 3-1'
}, {
id: 8,
label: 'Level two 3-2'
}]
}];
return {
data4: [{
id: 1,
label: 'Level one 1',
children: [{
id: 4,
label: 'Level two 1-1',
children: [{
id: 9,
label: 'Level three 1-1-1'
}, {
id: 10,
label: 'Level three 1-1-2'
}]
}]
}, {
id: 2,
label: 'Level one 2',
children: [{
id: 5,
label: 'Level two 2-1'
}, {
id: 6,
label: 'Level two 2-2'
}]
}, {
id: 3,
label: 'Level one 3',
children: [{
id: 7,
label: 'Level two 3-1'
}, {
id: 8,
label: 'Level two 3-2'
}]
}],
defaultProps: {
children: 'children',
label: 'label'
}
data4: JSON.parse(JSON.stringify(data)),
data5: JSON.parse(JSON.stringify(data))
}
},
@ -763,19 +820,28 @@ The content of tree nodes can be customized, so you can add icons or buttons as
renderContent(h, { node, data, store }) {
return (
<span style="flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px;">
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<span>{node.label}</span>
</span>
<span>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
}
}
};
</script>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
```
:::
@ -986,3 +1052,8 @@ Only one node among the same level can be expanded at one time.
| current-change | triggers when current node changes | two parameters: node object corresponding to the current node, `node` property of TreeNode |
| node-expand | triggers when current node open | three parameters: node object corresponding to the node opened, `node` property of TreeNode, TreeNode itself |
| node-collapse | triggers when current node close | three parameters: node object corresponding to the node closed, `node` property of TreeNode, TreeNode itself |
### Scoped slot
| name | Description |
|------|--------|
| — | Custom content for tree nodes. The scope parameter is { node, data } |

View File

@ -17,6 +17,35 @@
.filter-tree {
margin-top: 20px;
}
.custom-tree-container {
display: flex;
margin: -24px;
}
.block {
flex: 1;
padding: 8px 24px 24px;
&:first-child {
border-right: solid 1px #eff2f6;
}
> p {
text-align: center;
margin: 0;
line-height: 4;
}
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
}
</style>
@ -218,11 +247,11 @@
this.$refs.tree.setCheckedNodes([
{
id: 5,
label: '二级 2-1'
label: 'Level two 2-1'
},
{
id: 9,
label: '三级 1-1-1'
label: 'Level three 1-1-1'
}
]);
},
@ -249,13 +278,11 @@
renderContent(h, { node, data, store }) {
return (
<span style="flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px;">
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<span>{node.label}</span>
</span>
<span>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
},
@ -272,6 +299,7 @@
data2,
data3,
data4: JSON.parse(JSON.stringify(data2)),
data5: JSON.parse(JSON.stringify(data2)),
regions,
defaultProps,
props,
@ -685,63 +713,92 @@ Los nodos pueden estar desplegados o seleccionados por defecto.
### Contenido personalizado en los nodos
El contenido de los nodos puede ser personalizado, así que puede añadir iconos y botones a su gusto.
:::demo Utilice `render-content` para asignar una función de renderizado que devuelve el contenido del árbol de nodos. Mire la documentación de node para una introducción detallada a las funciondes de renderizado. Ten en cuenta que este ejemplo no puede ejecutarse en jsfiddle ya que no soporta la sintaxis JSX. En un proyecto real `render-content` funcionará si las dependencias relevantes están configuradas correctamente.
:::demo There are two ways to customize template for tree nodes: `render-content` and scoped slot. Utilice `render-content` para asignar una función de renderizado que devuelve el contenido del árbol de nodos. Mire la documentación de node para una introducción detallada a las funciondes de renderizado. If you prefer scoped slot, you'll have access to `node` and `data` in the scope, standing for the TreeNode object and node data of the current node respectively. Ten en cuenta que este ejemplo no puede ejecutarse en jsfiddle ya que no soporta la sintaxis JSX. En un proyecto real `render-content` funcionará si las dependencias relevantes están configuradas correctamente.
```html
<el-tree
:data="data4"
:props="defaultProps"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
<div class="custom-tree-container">
<div class="block">
<p>Using render-content</p>
<el-tree
:data="data4"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
</div>
<div class="block">
<p>Using scoped slot</p>
<el-tree
:data="data5"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
size="mini"
@click="() => append(data)">
Append
</el-button>
<el-button
type="text"
size="mini"
@click="() => remove(node, data)">
Delete
</el-button>
</span>
</span>
</el-tree>
</div>
</div>
<script>
let id = 1000;
export default {
data() {
const data = [{
id: 1,
label: 'Level one 1',
children: [{
id: 4,
label: 'Level two 1-1',
children: [{
id: 9,
label: 'Level three 1-1-1'
}, {
id: 10,
label: 'Level three 1-1-2'
}]
}]
}, {
id: 2,
label: 'Level one 2',
children: [{
id: 5,
label: 'Level two 2-1'
}, {
id: 6,
label: 'Level two 2-2'
}]
}, {
id: 3,
label: 'Level one 3',
children: [{
id: 7,
label: 'Level two 3-1'
}, {
id: 8,
label: 'Level two 3-2'
}]
}];
return {
data4: [{
id: 1,
label: 'Level one 1',
children: [{
id: 4,
label: 'Level two 1-1',
children: [{
id: 9,
label: 'Level three 1-1-1'
}, {
id: 10,
label: 'Level three 1-1-2'
}]
}]
}, {
id: 2,
label: 'Level one 2',
children: [{
id: 5,
label: 'Level two 2-1'
}, {
id: 6,
label: 'Level two 2-2'
}]
}, {
id: 3,
label: 'Level one 3',
children: [{
id: 7,
label: 'Level two 3-1'
}, {
id: 8,
label: 'Level two 3-2'
}]
}],
defaultProps: {
children: 'children',
label: 'label'
}
data4: JSON.parse(JSON.stringify(data)),
data5: JSON.parse(JSON.stringify(data))
}
},
@ -763,19 +820,28 @@ El contenido de los nodos puede ser personalizado, así que puede añadir iconos
renderContent(h, { node, data, store }) {
return (
<span style="flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px;">
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<span>{node.label}</span>
</span>
<span>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
}
}
};
</script>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
```
:::
@ -984,3 +1050,8 @@ Solo puede ser expandido un nodo del mismo nivel a la vez.
| current-change | cambia cuando el nodo actual cambia | dos parámetros: objeto nodo que se corresponde al nodo actual y propiedad `node` del TreeNode |
| node-expand | se lanza cuando el nodo actual se abre | tres parámetros: el objeto del nodo abierto, propiedad `node` de TreeNode y el TreeNode en si |
| node-collapse | se lanza cuando el nodo actual se cierra | tres parámetros: el objeto del nodo cerrado, propiedad `node` de TreeNode y el TreeNode en si |
### Scoped slot
| name | Description |
|------|--------|
| — | Custom content for tree nodes. The scope parameter is { node, data } |

View File

@ -17,6 +17,35 @@
.filter-tree {
margin-top: 20px;
}
.custom-tree-container {
display: flex;
margin: -24px;
}
.block {
flex: 1;
padding: 8px 24px 24px;
&:first-child {
border-right: solid 1px #eff2f6;
}
> p {
text-align: center;
margin: 0;
line-height: 4;
}
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
}
</style>
@ -249,13 +278,11 @@
renderContent(h, { node, data }) {
return (
<span style="flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px;">
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<span>{node.label}</span>
</span>
<span>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
},
@ -272,6 +299,7 @@
data2,
data3,
data4: JSON.parse(JSON.stringify(data2)),
data5: JSON.parse(JSON.stringify(data2)),
regions,
defaultProps,
props,
@ -684,63 +712,92 @@
### 自定义节点内容
节点的内容支持自定义,可以在节点区添加按钮或图标等内容
:::demo 使用`render-content`指定渲染函数,该函数返回需要的节点区内容即可。渲染函数的用法请参考 Vue 文档。注意:由于 jsfiddle 不支持 JSX 语法,所以例在 jsfiddle 中无法运行。但是在实际的项目中,只要正确地配置了相关依赖,就可以正常运行。
:::demo 可以通过两种方法进行树节点内容的自定义:`render-content`和 scoped slot。使用`render-content`指定渲染函数,该函数返回需要的节点区内容即可。渲染函数的用法请参考 Vue 文档。使用 scoped slot 会传入两个参数`node`和`data`,分别表示当前节点的 Node 对象和当前节点的数据。注意:由于 jsfiddle 不支持 JSX 语法,所以`render-content`示例在 jsfiddle 中无法运行。但是在实际的项目中,只要正确地配置了相关依赖,就可以正常运行。
```html
<el-tree
:data="data4"
:props="defaultProps"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
<div class="custom-tree-container">
<div class="block">
<p>使用 render-content</p>
<el-tree
:data="data4"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
:render-content="renderContent">
</el-tree>
</div>
<div class="block">
<p>使用 scoped slot</p>
<el-tree
:data="data5"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
size="mini"
@click="() => append(data)">
Append
</el-button>
<el-button
type="text"
size="mini"
@click="() => remove(node, data)">
Delete
</el-button>
</span>
</span>
</el-tree>
</div>
</div>
<script>
let id = 1000;
export default {
data() {
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'
}]
}];
return {
data4: [{
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'
}]
}],
defaultProps: {
children: 'children',
label: 'label'
}
data4: JSON.parse(JSON.stringify(data)),
data5: JSON.parse(JSON.stringify(data))
}
},
@ -762,19 +819,28 @@
renderContent(h, { node, data, store }) {
return (
<span style="flex: 1; display: flex; align-items: center; justify-content: space-between; font-size: 14px; padding-right: 8px;">
<span class="custom-tree-node">
<span>{node.label}</span>
<span>
<span>{node.label}</span>
</span>
<span>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button style="font-size: 12px;" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
<el-button size="mini" type="text" on-click={ () => this.append(data) }>Append</el-button>
<el-button size="mini" type="text" on-click={ () => this.remove(node, data) }>Delete</el-button>
</span>
</span>);
}
}
};
</script>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
```
:::
@ -985,3 +1051,8 @@
| current-change | 当前选中节点变化时触发的事件 | 共两个参数,依次为:当前节点的数据,当前节点的 Node 对象 |
| node-expand | 节点被展开时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
| node-collapse | 节点被关闭时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 |
### Scoped slot
| name | 说明 |
|------|--------|
| — | 自定义树节点的内容,参数为 { node, data } |

View File

@ -97,13 +97,15 @@
},
render(h) {
const parent = this.$parent;
const tree = parent.tree;
const node = this.node;
const data = node.data;
const store = node.store;
const { data, store } = node;
return (
parent.renderContent
? parent.renderContent.call(parent._renderProxy, h, { _self: parent.tree.$vnode.context, node, data, store })
: <span class="el-tree-node__label">{ this.node.label }</span>
? parent.renderContent.call(parent._renderProxy, h, { _self: tree.$vnode.context, node, data, store })
: tree.$scopedSlots.default
? tree.$scopedSlots.default({ node, data })
: <span class="el-tree-node__label">{ this.node.label }</span>
);
}
}

View File

@ -501,6 +501,63 @@ describe('Tree', () => {
expect(button.textContent).to.equal('一级 1');
});
it('scoped slot', () => {
vm = createVue({
template: `
<el-tree ref="tree" :data="data">
<div slot-scope="scope" class="custom-tree-template">
<span>{{ scope.node.label }}</span>
<el-button></el-button>
</div>
</el-tree>
`,
data() {
return {
data: [{
id: 1,
label: '一级 1',
children: [{
id: 11,
label: '二级 1-1',
children: [{
id: 111,
label: '三级 1-1'
}]
}]
}, {
id: 2,
label: '一级 2',
children: [{
id: 21,
label: '二级 2-1'
}, {
id: 22,
label: '二级 2-2'
}]
}, {
id: 3,
label: '一级 3',
children: [{
id: 31,
label: '二级 3-1'
}, {
id: 32,
label: '二级 3-2'
}]
}]
};
}
}, true);
const firstNode = document.querySelector('.custom-tree-template');
expect(firstNode).to.exist;
const span = firstNode.querySelector('span');
const button = firstNode.querySelector('.el-button');
expect(span).to.exist;
expect(span.innerText).to.equal('一级 1');
expect(button).to.exist;
});
it('load node', done => {
vm = getTreeVm(':props="defaultProps" lazy :load="loadNode" show-checkbox', {
methods: {