Quasar Tree代表一个高度可配置的组件,用于显示分层数据,如树结构的目录。

安装

编辑 /quasar.conf.js:

framework: {
components: ['QTree']
}

基本用法

这是您可以编写的最简单的树:

<template>
<q-tree
:nodes="simple"
node-key="label"
/>
</template>

<script>
export default {
data: () => ({
simple: [
{
label: 'Satisfied customers',
children: [
{
label: 'Good food',
children: [
{ label: 'Quality ingredients' },
{ label: 'Good recipe' }
]
},
{
label: 'Good service (disabled node)',
disabled: true,
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
children: [
{ label: 'Happy atmosphere' },
{ label: 'Good table presentation' },
{ label: 'Pleasing decor' }
]
}
]
}
]
})
}
</script>

请注意,节点必须具有由每个键的属性定义的唯一键。在上面的例子中,标签是唯一的,所以我们使用label属性来定义这些键。但是,您可以将任何属性添加到节点(如’id’或任何您想要的),然后使用该属性(如node-key="id")。

Vue属性

Vue属性 类型 说明
nodes 数组 Tree的Vue模型
node-key 字符串 用作唯一键的节点属性。
color 字符串 连接线的颜色。
control-color 字符串 复选框的颜色。
text-color 字符串 文本的颜色。
dark 布尔 在黑暗背景下渲染时。
icon 字符串 每个节点的连接器图标。
selected 任何 使用.sync。所选节点的唯一键值。
tick-strategy 字符串 ‘leaf’、’leaf-filtered’、’strict’、’none’之一。
ticked 数组 使用.sync。打勾节点的唯一键集合。
expanded 数组 使用.sync。扩展节点的唯一键集合。
default-expand-all 布尔 第一次渲染时展开所有节点。
accordion 布尔 展开一个节点关闭它的兄弟节点。
filter 字符串 过滤节点时使用的字符串。
filter-method 函数 自定义过滤方法。
no-nodes-label 字符串 当没有节点可用时,覆盖默认的i18n消息。
no-results-label 字符串 过滤后没有节点可用时,覆盖默认的i18n消息。

节点模型结构

以下描述了QTree v-model拥有的节点属性。

节点属性 类型 说明
label 字符串 节点的标签
icon 字符串 节点的图标
img 字符串 节点的图像。使用静态文件夹。例如:’statics/mountains.png’
avatar 字符串 节点的头像。使用静态文件夹。例如:’statics/boy-avatar.png’
children 数组 子元素节点数组。
disabled 布尔 节点是否被禁用?
expandable 布尔 节点是否可扩展?
tickable 布尔 当使用打勾策略时,每个节点显示一个复选框。应该禁用节点的复选框?
noTick 布尔 在使用打勾策略时,节点是否显示复选框?
tickStrategy 字符串 仅此节点覆盖全局打勾策略。 ‘leaf’、’leaf-filtered’、’strict’、’none’之一。
lazy 布尔 子元素应该懒加载吗?在这种情况下也不要指定’children’属性。
header 字符串 节点头部范围的插槽名称,没有必需的“header”前缀。例如:’story’是指’header-story’范围的插槽。
body 字符串 节点主体范围的插槽名称,没有必需的“body-”前缀。例如:’story’是指’body-story’范围的插槽。

Selection vs Ticking, Expansion

上述所有属性都需要.sync修饰符才能正常工作。例:

<!-- 别忘了添加 ".sync" -->
<q-tree selected.sync="selection" ...

勾选策略

有三种勾选策略: ‘leaf’、’leaf-filtered’、’strict’ 外加默认值’none’——禁止勾选。

策略 说明
leaf 勾选节点只是叶子节点。 勾选节点也会影响父节点的打勾状态(父节点变成部分打勾状态或打勾状态)以及其子节点(所有可打勾的子节点都打勾)。
leaf-filtered leaf相同的概念,只是这个策略只适用于过滤的节点(过滤后保持可见的节点)。
strict 勾选节点独立于父节点或子节点状态。

您可以为QTree应用全局勾选策略,并通过在nodes模型中指定tickStrategy来本地更改某个节点的勾选策略。

自定义过滤方法

您可以通过指定filter-method属性来自定义过滤方法。 下面的方法实际上是默认的过滤策略:

<template>
<q-tree :filter-method="myFilterMethod" ...>
</template>

<script>
export default {
methods: {
myFilterMethod (node, filter) {
const filt = filter.toLowerCase()
return node.label && node.label.toLowerCase().indexOf(filt) > -1
}
}
}
</script>

例子

节点图标/头像/图像,控制扩展和着色

<template>
<div>
<q-btn
color="secondary"
@click="togglePropsGoodServiceExpand"
label="Toggle 'Good service' expansion"
/>
<q-tree
:nodes="props"
:expanded.sync="propsExpanded"
color="red"
node-key="label"
/>
</div>
</template>

<script>
export default {
data: () => ({
propsExpanded: ['Satisfied customers', 'Pleasant surroundings'],
props: [
{
label: 'Satisfied customers',
avatar: 'statics/boy-avatar.png',
children: [
{
label: 'Good food',
icon: 'restaurant_menu',
children: [
{ label: 'Quality ingredients' },
{ label: 'Good recipe' }
]
},
{
label: 'Good service',
icon: 'room_service',
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
icon: 'photo',
children: [
{
label: 'Happy atmosphere',
img: 'statics/parallax1.jpg'
},
{
label: 'Good table presentation',
img: 'statics/parallax2.jpg'
},
{
label: 'Pleasing decor',
img: 'statics/mountains.jpg'
}
]
}
]
}
]
}),
methods: {
togglePropsGoodServiceExpand () {
const index = this.propsExpanded.indexOf('Good service')
if (index > -1) {
this.propsExpanded.splice(index, 1)
}
else {
this.propsExpanded.push('Good service')
}
}
}
}
</script>

定制节点(头部和主体插槽)

<template>
<q-tree
:nodes="customize"
node-key="label"
default-expand-all
>
<div slot="header-root" slot-scope="prop" class="row items-center">
<img src="~assets/quasar-logo.svg" class="avatar q-mr-sm">
<div>
{{ prop.node.label }} <q-chip color="orange" small>New!</q-chip>
</div>
</div>

<div slot="header-generic" slot-scope="prop" class="row items-center">
<q-icon :name="prop.node.icon || 'star'" color="orange" size="28px" class="q-mr-sm" />
<div class="text-weight-bold text-primary">{{ prop.node.label }}</div>
</div>

<div slot="body-story" slot-scope="prop">
<span class="text-weight-thin">The story is:</span> {{ prop.node.story }}
</div>

<div slot="body-toggle" slot-scope="prop">
<p class="caption">{{ prop.node.caption }}</p>
<q-toggle v-model="prop.node.enabled" label="I agree to the terms and conditions" />
</div>
</q-tree>
</template>

<script>
export default {
data: () => ({
customize: [
{
label: 'Satisfied customers',
header: 'root',
children: [
{
label: 'Good food',
icon: 'restaurant_menu',
header: 'generic',
children: [
{
label: 'Quality ingredients',
header: 'generic',
body: 'story',
story: 'Lorem ipsum dolor sit amet.'
},
{
label: 'Good recipe',
body: 'story',
story: 'A Congressman works with his equally conniving wife to exact revenge on the people who betrayed him.'
}
]
},
{
label: 'Good service',
header: 'generic',
body: 'toggle',
caption: 'Why are we as consumers so captivated by stories of great customer service? Perhaps it is because...',
enabled: false,
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
children: [
{ label: 'Happy atmosphere' },
{ label: 'Good table presentation', header: 'generic' },
{ label: 'Pleasing decor' }
]
}
]
}
]
})
}
</script>

应用默认的头部和主体插槽

<template>
<q-tree
:nodes="customize"
node-key="label"
default-expand-all
>
<div slot="default-header" slot-scope="prop" class="row items-center">
<q-icon :name="prop.node.icon || 'share'" color="orange" size="28px" class="q-mr-sm" />
<div class="text-weight-bold text-primary">{{ prop.node.label }}</div>
</div>

<div slot="default-body" slot-scope="prop">
<div v-if="prop.node.story">
<span class="text-weight-thin">This node has a story</span>: {{ prop.node.story }}
</div>
<span v-else class="text-weight-thin">This is some default content.</span>
</div>
</q-tree>
</template>

<script>
export default {
data: () => ({
customize: [
{
label: 'Satisfied customers',
header: 'root',
children: [
{
label: 'Good food',
icon: 'restaurant_menu',
header: 'generic',
children: [
{
label: 'Quality ingredients',
header: 'generic',
body: 'story',
story: 'Lorem ipsum dolor sit amet.'
},
{
label: 'Good recipe',
body: 'story',
story: 'A Congressman works with his equally conniving wife to exact revenge on the people who betrayed him.'
}
]
},
{
label: 'Good service',
header: 'generic',
body: 'toggle',
caption: 'Why are we as consumers so captivated by stories of great customer service? Perhaps it is because...',
enabled: false,
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
children: [
{ label: 'Happy atmosphere' },
{ label: 'Good table presentation', header: 'generic' },
{ label: 'Pleasing decor' }
]
}
]
}
]
})
}
</script>

过滤节点

<template>
<div>
<q-input
v-model="filter"
stack-label="Filter"
clearable
class="q-mb-sm"
/>
<q-tree
:nodes="simple"
:filter="filter"
default-expand-all
node-key="label"
/>
</div>
</template>

<script>
export default {
data: () => ({
filter: '',
simple: [
{
label: 'Satisfied customers',
children: [
{
label: 'Good food',
children: [
{ label: 'Quality ingredients' },
{ label: 'Good recipe' }
]
},
{
label: 'Good service (disabled node)',
disabled: true,
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
children: [
{ label: 'Happy atmosphere' },
{ label: 'Good table presentation' },
{ label: 'Pleasing decor' }
]
}
]
}
]
})
}
</script>

手风琴模式(兄弟节点在展开时收缩)

<q-tree
:nodes="simple"
accordion
node-key="label"
/>

可选节点

<template>
<div>
<div class="q-mb-sm">
<q-btn size="sm" color="primary" @click="selectGoodService" label="Select 'Good service'" />
<q-btn v-if="selected" size="sm" color="red" @click="unselectNode" label="Unselect node" />
</div>
<q-tree
:nodes="props"
default-expand-all
:selected.sync="selected"
node-key="label"
/>
</div>
</template>

<script>
export default {
data: () => ({
selected: null,
props: [
{
label: 'Satisfied customers',
avatar: 'statics/boy-avatar.png',
children: [
{
label: 'Good food',
icon: 'restaurant_menu',
children: [
{ label: 'Quality ingredients' },
{ label: 'Good recipe' }
]
},
{
label: 'Good service',
icon: 'room_service',
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
icon: 'photo',
children: [
{
label: 'Happy atmosphere',
img: 'statics/parallax1.jpg'
},
{
label: 'Good table presentation',
img: 'statics/parallax2.jpg'
},
{
label: 'Pleasing decor',
img: 'statics/mountains.jpg'
}
]
}
]
}
]
}),
methods: {
selectGoodService () {
if (this.selected !== 'Good service') {
this.selected = 'Good service'
}
},
unselectNode () {
this.selected = null
},
}
}
</script>

可选节点和策略

<template>
<div>
<div class="q-mb-sm row no-wrap items-center">
<q-select
v-model="tickStrategy"
color="secondary"
stack-label="Tick Strategy"
:options="[
{label: 'None', value: 'none'},
{label: 'Leaf', value: 'leaf'},
{label: 'Leaf Filtered', value: 'leaf-filtered'},
{label: 'Strict', value: 'strict'}
]"
class="q-ma-none q-mr-sm"
style="width: 150px"
/>
<q-input
v-if="tickStrategy === 'leaf-filtered'"
color="secondary"
stack-label="Filter"
v-model="tickFilter"
class="q-ma-none"
clearable
/>
</div>
<q-tree
:nodes="tickable"
color="secondary"
default-expand-all
:ticked.sync="ticked"
:tick-strategy="tickStrategy"
:filter="tickFilter"
node-key="label"
/>
</div>
</template>

<script>
export default {
data: () => ({
ticked: [],
tickStrategy: 'leaf',
tickFilter: null,
tickable: [
{
label: 'Satisfied customers',
avatar: 'statics/boy-avatar.png',
children: [
{
label: 'Good food',
icon: 'restaurant_menu',
children: [
{ label: 'Quality ingredients' },
{ label: 'Good recipe' }
]
},
{
label: 'Good service',
icon: 'room_service',
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings',
icon: 'photo',
children: [
{
label: 'Happy atmosphere (not tickable)',
tickable: false,
img: 'statics/parallax1.jpg'
},
{
label: 'Good table presentation (disabled node)',
disabled: true,
img: 'statics/parallax2.jpg'
},
{
label: 'Pleasing decor',
img: 'statics/mountains.jpg'
}
]
},
{
label: 'Extra information (has no tick)',
noTick: true,
icon: 'photo'
},
{
label: 'Forced tick strategy (to "strict" in this case)',
tickStrategy: 'strict',
icon: 'school',
children: [
{
label: 'Happy atmosphere',
img: 'statics/parallax1.jpg'
},
{
label: 'Good table presentation',
img: 'statics/parallax2.jpg'
},
{
label: 'Very pleasing decor',
img: 'statics/mountains.jpg'
}
]
}
]
}
]
})
}
</script>

延迟加载节点

<template>
<q-tree
:nodes="lazy"
default-expand-all
node-key="label"
@lazy-load="onLazyLoad"
/>
</template>

<script>
export default {
data: () => ({
lazy: [
{
label: 'Node 1',
children: [
{ label: 'Node 1.1', lazy: true },
{ label: 'Node 1.2', lazy: true }
]
},
{
label: 'Node 2',
lazy: true
},
{
label: 'Lazy load empty',
lazy: true
},
{
label: 'Node is not expandable',
expandable: false,
children: [
{ label: 'Some node' }
]
}
]
}),
methods: {
onLazyLoad ({ node, key, done, fail }) {
// 如果发生任何错误,调用fail()

setTimeout(() => {
// 模拟加载并设置一个空节点
if (key.indexOf('Lazy load empty') > -1) {
done([])
return
}

const label = node.label
done([
{ label: `${label}.1` },
{ label: `${label}.2`, lazy: true },
{
label: `${label}.3`,
children: [
{ label: `${label}.3.1`, lazy: true },
{ label: `${label}.3.2`, lazy: true }
]
}
])
}, 1000)
}
}
}
</script>