Vue 树形选择器( Vue tree select )组件在搭建 Vue 的 app 中特别常用,Vue tree select 除了简单的树形结构外,还有非常多样的功能来配合不同场景的使用。比如搜索过滤,前端添加删除树枝,前端编辑修改子树名,拖拽排序,对用户操作事件记录等。本文记录了我自己使用多年最好用的 2 款 Vue tree select 组件,每一款都经过我实际测试,推荐给大家。
一、Liquor Tree
酒树 (Liquor Tree)
Liquor Tree 是一款轻量级树形选择器,对移动端友好,可拖放,支持键盘快捷键,每个操作动作都有事件记录,与 Vue 高度整合。Liquor Tree 代码简洁,扩展性强,可根据你的应用场景按需定制。
A Vue tree component that allows you to present hierarchically organized data in a nice and logical manner. Vue 树组件,可让您以美观和逻辑的方式呈现层次结构的数据。 supports mobile, and has a variety of response events. Flexible configuration options, and support for keyboard navigation. 支持移动,并具有各种响应事件。 灵活的配置选项,并支持键盘导航。 View demo 查看演示 Documentation
github 地址 :
GitHub - amsik/liquor-tree: Tree component based on Vue.js
Vue 官方地址 :
Liquor Tree
产品特点 :
- 拖放 移动友好
- 每个动作的事件
- 灵活的配置
- 每页任意数量的实例
- 多选
- 键盘导航
- 过滤
- 分类
- 与 Vuex 集成
入门 :
安装 :
Npm:
$ **npm install liquor-tree**
Yarn:
$ yarn add liquor-tree
要在自己的计算机上运行该演示,请执行以下操作:
克隆此存储库
- npm install ( npm安装 )
- npm run build ( npm运行构建 )
- npm run storybook
- 访问 http://localhost:9001/
这里有很多例子供您参考。 所有来源都位于 liquor-tree/docs/storybook/stories
它必须安装到 VueJS 实例中。请查看官方文档,了解如何使用 VueJS 组件 components
(当然,如果需要的话)。
您不需要关心样式,它们会自动附加到文档中。
当与模块系统一起使用时,有三种方法可以注册组件(可能更多…我不知道)。
好了,下面这是我们的方式:import Vue from 'vue' import LiquorTree from 'liquor-tree' // global registration Vue.use(LiquorTree) // 第一种 // or Vue.component(LiquorTree.name, LiquorTree) // 第二种 // or import LiquorTree from 'liquor-tree' // local registration export default { name: 'your-awesome-component', components: { [LiquorTree.name]: LiquorTree // 第三种 }, ... }
要注册库,您可以在我上面提到的 3 种方法之间进行选择。
当直接在浏览器中使用时,您可以通过CND包含 **
liquor-tree
**(这是库的最新版本):
<script src="https://cdn.jsdelivr.net/npm/liquor-tree/dist/liquor-tree.umd.js"></script>
Usage 用法
<!-- Vue Component -->
<template>
<tree
:data="items"
:options="options"
ref="tree"
/>
</template>
<script>
import Vue from 'vue'
import LiquorTree from 'liquor-tree'
Vue.use(LiquorTree)
export default {
...
data() {
return {
items: [
{text: 'Item 1'},
{text: 'Item 2'},
{text: 'Item 3', children: [
{text: 'Item 3.1'},
{text: 'Item 3.2'}
]},
{
text: '园区其他机构',
children: [
{
text: '园区其他机构1',
children: [
{ text: '园区其他机构1.1.1' },
{ text: '园区其他机构1.1.2' },
{ text: '园区其他机构1.1.3' },
],
},
{
text: '园区其他机构2',
children: [
{ text: '园区其他机构2.1.1' },
{ text: '园区其他机构2.1.2' },
{ text: '园区其他机构2.1.3' },
],
},
],
},
],
options: {
checkbox: true
}
}
}
...
}
</script>
Development
Check out the
package.json
s script section. There are 2 scripts:
npm run dev
- it will open browser and you can play with codenpm run build
- it will craete a module file inproduction
mode
Component Options 组件选项
Name 名称Type 类型DefaultDescription 描述multipleBooleantrue
Allows to select more than one node.
允许选择多个节点
checkboxBooleanfalse
checkbox
mode. It shows checkboxes for every node
复选框模式。它显示每个节点的复选框
checkOnSelectBooleanfalse
For
checkbox
mode only. Node will have
checked
state when user clicks either text or checkbox
仅用于复选框模式。当用户单击文本或复选框时,节点将处于选中状态
autoCheckChildrenBooleantrue
For
checkbox
mode only. Children will have the same
checked
state as their parent.
仅用于复选框模式。子级将具有与其父级相同的选中状态。
parentSelectBooleanfalse
By clicking node which has children it expands node. i.e we have two ways to expand/collapse node: by clicking on arrow and on text
通过单击具有子节点的节点,可以展开节点。即,我们有两种方法来展开/折叠节点:单击箭头和文本
keyboardNavigationBooleantrue
Allows user to navigate tree using keyboard
允许用户使用键盘浏览树
propertyNamesObject-
This options allows the default tree’s structure to be redefined. See example
此选项允许重新定义默认树的结构。见示例
deletionBoolean | Functionfalse
If keyboardNavigation is false this property is ignored. This property defines deletion behaviour. See example
如果 keyboardNavigation 为 false ,则忽略此属性。此属性定义删除行为。见示例
fetchDataObject-See guidedndObject-See guide 请参阅指南****editingObject-See guide
Structure 结构
The component has only two props: data and options. 该组件只有两个支柱:数据和选项。
- property options - This property defines tree behavior. 属性选项 - 此属性定义树行为
- property data - Array-like object that defines tree nodes. 属性数据 - 定义树节点的类似数组的对象
关于 Liquor Tree 树形组件的介绍就到此为止了,因为此组件未满足业务需求:搜索过滤功能。
再加上官方文档全都是英文的,所以就懒得再继续研究下去了。
因此另辟蹊径,找到了个人认为更加友好、更加优秀的一款 Tree 树形组件。
期间查询到一款应该还不错 :LyTree 树形组件 ( 貌似无搜索过滤 )
链接 🔗 : tree树形组件 - DCloud 插件市场
二、vue-treeselect
@riophae/vue-treeselect
**GitHub 地址 🔗 : **GitHub - riophae/vue-treeselect: A multi-select component with nested options support for Vue.jsA multi-select component with nested options support for Vue.js - GitHub - riophae/vue-treeselect: A multi-select component with nested options support for Vue.jshttps://github.com/riophae/vue-treeselect
Vue-TreeSelect 官网 🔗 :Vue-TreeselectA multi-select component with nested options support for Vue.jshttps://vue-treeselect.js.org/
A multi-select component with nested options support for Vue.js
一个支持 Vue.js 的嵌套选项的多选组件
Introduction 介绍
vue-treeselect is a multi-select component with nested options support for Vue.js.
treeselecte 是一个具有嵌套选项的多选择组件,支持 Vue.js。
- Single & multiple select with nested options support ( 支持嵌套选项的单个和多个选项 )
- Fuzzy matching ( 模糊匹配 )
- Async searching ( 异步搜索 )
- Delayed loading ( load data of deep level options only when needed )
- ( 支持嵌套选项的单个和多个选择 )
- Keyboard support ( navigate using Arrow Up & Arrow Down keys, select option using Enter key, etc. ) ( 支持嵌套选项的单个和多个选择,使用回车键等 )
- Rich options & highly customizable ( 丰富的选项和高度可定制的 )
- Supports a wide range of browsers ( 支持多种浏览器 )
Requires Vue 2.2+ ( 必须 Vue 2.2+ )
Getting Started 入门
It's recommended to install vue-treeselect via npm, and build your app using a bundler like webpack.
建议通过 npm 安装 vue-treeselect ,并使用类似 bundler 的 webpack 构建您的应用程序。
npm install --save @riophae/vue-treeselect
This example shows how to integrate vue-treeselect with your Vue SFCs.
这个示例展示了如何将 Vue 树选择与 Vue SFC 集成。
<!-- Vue SFC --> <template> <div id="app"> <treeselect v-model="value" :multiple="true" :options="options" /> </div> </template> <script> // import the component import Treeselect from '@riophae/vue-treeselect' // import the styles import '@riophae/vue-treeselect/dist/vue-treeselect.css' export default { // register the component components: { Treeselect }, data() { return { // define the default value value: null, // define options options: [ { id: 'a', label: 'a', children: [ { id: 'aa', label: 'aa', }, { id: 'ab', label: 'ab', } ], }, { id: 'b', label: 'b', }, { id: 'c', label: 'c', } ], } }, } </script>
If you just don't want to use webpack or any other bundlers, you can simply include the standalone UMD build in your page. In this way, make sure Vue as a dependency is included before vue-treeselect.
如果你只是不想使用 webpack 或任何其他捆绑器,你可以简单地在你的页面中包括独立的UMD 构建。通过这种方式,确保在 Vue 树选择之前包含 Vue 作为依赖项。
<html> <head> <!-- include Vue 2.x --> <script src="https://cdn.jsdelivr.net/npm/vue@^2"></script> <!-- include vue-treeselect & its styles. you can change the version tag to better suit your needs. --> <script src="https://cdn.jsdelivr.net/npm/@riophae/vue-treeselect@^0.4.0/dist/vue-treeselect.umd.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@riophae/vue-treeselect@^0.4.0/dist/vue-treeselect.min.css"> </head> <body> <div id="app"> <treeselect v-model="value" :multiple="true" :options="options" /> </div> </body> <script> // register the component Vue.component('treeselect', VueTreeselect.Treeselect) new Vue({ el: '#app', data: { // define the default value value: null, // define options options: [ { id: 'a', label: 'a', children: [ { id: 'aa', label: 'aa', }, { id: 'ab', label: 'ab', } ], }, { id: 'b', label: 'b', }, { id: 'c', label: 'c', } ], }, }) </script> </html>
个人实际项目所用 :
通过Treeselect组件的配置属性中添加
"alwaysOpen": true
来让搜索栏默认展开下拉。
API - Props
props: {
/**
* 即使有禁用的选定节点,是否允许重置值
*/
allowClearingDisabled: {
type: Boolean,
default: false,
},
/**
* 选择/取消选择祖先节点时,是否应选择/取消选中其禁用的后代
* 和 allowClearingDisabled 一起使用
*/
allowSelectingDisabledDescendants: {
type: Boolean,
default: false,
},
/**
* 菜单是否应始终打开
*/
alwaysOpen: {
type: Boolean,
default: false,
},
/**
* 是否将菜单加到body上
*/
appendToBody: {
type: Boolean,
default: false,
},
/**
* 是否启用异步搜索模式
*/
async: {
type: Boolean,
default: false,
},
/**
* 是否自动将组件集中在装载上?
*/
autoFocus: {
type: Boolean,
default: false,
},
/**
* 装载时自动加载根选项。当设置为“false”时,打开菜单时将加载根选项。
*/
autoLoadRootOptions: {
type: Boolean,
default: true,
},
/**
* 当用户取消选择一个节点时,会自动取消选择其祖先。仅适用于平面模式。
*/
autoDeselectAncestors: {
type: Boolean,
default: false,
},
/**
* 当用户取消选择节点时,会自动取消选择其子节点。仅适用于平面模式。
*/
autoDeselectDescendants: {
type: Boolean,
default: false,
},
/**
* 当用户选择一个节点时,会自动选择其祖先。仅适用于平面模式。
*/
autoSelectAncestors: {
type: Boolean,
default: false,
},
/**
* 当用户选择一个节点时,会自动选择其子节点。仅适用于平面模式。
*/
autoSelectDescendants: {
type: Boolean,
default: false,
},
/**
* 如果没有文本输入,按退格键是否删除最后一项。
*/
backspaceRemoves: {
type: Boolean,
default: true,
},
/**
* 在清除所有输入字段之前进行处理的函数。
* 返回“false”以防止清除值
* @type {function(): (boolean|Promise<boolean>)}
*/
beforeClearAll: {
type: Function,
default: constant(true),
},
/**
* 在叶节点之前显示分支节点?
*/
branchNodesFirst: {
type: Boolean,
default: false,
},
/**
* 是否应该缓存每个搜索请求的结果?
*/
cacheOptions: {
type: Boolean,
default: true,
},
/**
* 是否显示重置值的“×”按钮?
*/
clearable: {
type: Boolean,
default: true,
},
/**
* 清楚文本,multiple为true时
*/
clearAllText: {
type: String,
default: 'Clear all',
},
/**
* 选择后是否清除搜索输入。
* 仅当“multiple”为“true”时使用。
* 对于单选模式,无论道具值如何,它总是**在选择一个选项后清除输入。
*/
clearOnSelect: {
type: Boolean,
default: false,
},
/**
* “×”按钮的标题。
*/
clearValueText: {
type: String,
default: 'Clear value',
},
/**
* 选择选项后是否关闭菜单?
* 仅当“multiple”为“true”时使用。
*/
closeOnSelect: {
type: Boolean,
default: true,
},
/**
* 加载时应自动展开多少级别的分支节点。
* 设置Infinity以使所有分支节点在默认情况下展开。
*/
defaultExpandLevel: {
type: Number,
default: 0,
},
/**
* 在用户开始搜索之前要显示的默认选项集。用于异步搜索模式。
* 当设置为“true”时,将自动加载作为空字符串的搜索查询结果。
* @type {boolean|node[]}
*/
defaultOptions: {
default: false,
},
/**
* 如果没有文本输入,按delete键是否删除最后一项。
*/
deleteRemoves: {
type: Boolean,
default: true,
},
/**
* 用于连接隐藏字段值的多个值的分隔符。
*/
delimiter: {
type: String,
default: ',',
},
/**
* 仅显示与搜索值直接匹配的节点,不包括其祖先。
*
* @type {Object}
*/
flattenSearchResults: {
type: Boolean,
default: false,
},
/**
* 是否阻止选择分支节点?
*/
disableBranchNodes: {
type: Boolean,
default: false,
},
/**
* 禁用控制?
*/
disabled: {
type: Boolean,
default: false,
},
/**
* 是否禁用模糊匹配功能?
*/
disableFuzzyMatching: {
type: Boolean,
default: false,
},
/**
*是否启用平面模式。非平面模式(默认)是指:
* - 每当检查分支节点时,它的所有子节点也将被检查
* - 每当一个分支节点检查了所有子节点时,该分支节点本身也会被检查
* 设置“true”以禁用此机制
*/
flat: {
type: Boolean,
default: false,
},
/**
* 将以所有事件作为最后一个参数进行传递。
* 有助于识别事件的起源。
*/
instanceId: {
// Add two trailing "$" to distinguish from explictly specified ids.
default: () => `${instanceId++}$$`,
type: [String, Number],
},
/**
* Joins multiple values into a single form field with the `delimiter` (legacy mode).
* 使用“分隔符”将多个值合并到一个表单字段中(传统模式)。
*/
joinValues: {
type: Boolean,
default: false,
},
/**
* 限制所选选项的显示。
* 其余部分将隐藏在limitText字符串中。
*/
limit: {
type: Number,
default: Infinity,
},
/**
* Function that processes the message shown when selected elements pass the defined limit.
* @type {function(number): string}
*/
limitText: {
type: Function,
default: function limitTextDefault(count) { // eslint-disable-line func-name-matching
return `and ${count} more`
},
},
/**
* Text displayed when loading options.
*/
loadingText: {
type: String,
default: 'Loading...',
},
/**
* Used for dynamically loading options.
* @type {function({action: string, callback: (function((Error|string)=): void), parentNode: node=, instanceId}): void}
*/
loadOptions: {
type: Function,
},
/**
* Which node properties to filter on.
*/
matchKeys: {
type: Array,
default: constant(['label']),
},
/**
* Sets `maxHeight` style value of the menu.
*/
maxHeight: {
type: Number,
default: 300,
},
/**
* Set `true` to allow selecting multiple options (a.k.a., multi-select mode).
*/
multiple: {
type: Boolean,
default: false,
},
/**
* Generates a hidden <input /> tag with this field name for html forms.
*/
name: {
type: String,
},
/**
* Text displayed when a branch node has no children.
*/
noChildrenText: {
type: String,
default: 'No sub-options.',
},
/**
* Text displayed when there are no available options.
*/
noOptionsText: {
type: String,
default: 'No options available.',
},
/**
* Text displayed when there are no matching search results.
*/
noResultsText: {
type: String,
default: 'No results found...',
},
/**
* Used for normalizing source data.
* @type {function(node, instanceId): node}
*/
normalizer: {
type: Function,
default: identity,
},
/**
* By default (`auto`), the menu will open below the control. If there is not
* enough space, vue-treeselect will automatically flip the menu.
* You can use one of other four options to force the menu to be always opened
* to specified direction.
* Acceptable values:
* - `"auto"`
* - `"below"`
* - `"bottom"`
* - `"above"`
* - `"top"`
*/
openDirection: {
type: String,
default: 'auto',
validator(value) {
const acceptableValues = ['auto', 'top', 'bottom', 'above', 'below']
return includes(acceptableValues, value)
},
},
/**
* Whether to automatically open the menu when the control is clicked.
*/
openOnClick: {
type: Boolean,
default: true,
},
/**
* Whether to automatically open the menu when the control is focused.
*/
openOnFocus: {
type: Boolean,
default: false,
},
/**
* Array of available options.
* @type {node[]}
*/
options: {
type: Array,
},
/**
* Field placeholder, displayed when there's no value.
*/
placeholder: {
type: String,
default: 'Select...',
},
/**
* Applies HTML5 required attribute when needed.
*/
required: {
type: Boolean,
default: false,
},
/**
* Text displayed asking user whether to retry loading children options.
*/
retryText: {
type: String,
default: 'Retry?',
},
/**
* Title for the retry button.
*/
retryTitle: {
type: String,
default: 'Click to retry',
},
/**
* Enable searching feature?
*/
searchable: {
type: Boolean,
default: true,
},
/**
* Search in ancestor nodes too.
*/
searchNested: {
type: Boolean,
default: false,
},
/**
* Text tip to prompt for async search.
*/
searchPromptText: {
type: String,
default: 'Type to search...',
},
/**
* Whether to show a children count next to the label of each branch node.
*/
showCount: {
type: Boolean,
default: false,
},
/**
* Used in conjunction with `showCount` to specify which type of count number should be displayed.
* Acceptable values:
* - "ALL_CHILDREN"
* - "ALL_DESCENDANTS"
* - "LEAF_CHILDREN"
* - "LEAF_DESCENDANTS"
*/
showCountOf: {
type: String,
default: ALL_CHILDREN,
validator(value) {
const acceptableValues = [ALL_CHILDREN, ALL_DESCENDANTS, LEAF_CHILDREN, LEAF_DESCENDANTS]
return includes(acceptableValues, value)
},
},
/**
* Whether to show children count when searching.
* Fallbacks to the value of `showCount` when not specified.
* @type {boolean}
*/
showCountOnSearch: null,
/**
* In which order the selected options should be displayed in trigger & sorted in `value` array.
* Used for multi-select mode only.
* Acceptable values:
* - "ORDER_SELECTED"
* - "LEVEL"
* - "INDEX"
*/
sortValueBy: {
type: String,
default: ORDER_SELECTED,
validator(value) {
const acceptableValues = [ORDER_SELECTED, LEVEL, INDEX]
return includes(acceptableValues, value)
},
},
/**
* Tab index of the control.
*/
tabIndex: {
type: Number,
default: 0,
},
/**
* The value of the control.
* Should be `id` or `node` object for single-select mode, or an array of `id` or `node` object for multi-select mode.
* Its format depends on the `valueFormat` prop.
* For most cases, just use `v-model` instead.
* @type {?Array}
*/
value: null,
/**
* Which kind of nodes should be included in the `value` array in multi-select mode.
* Acceptable values:
* - "ALL" - Any node that is checked will be included in the `value` array
* - "BRANCH_PRIORITY" (default) - If a branch node is checked, all its descendants will be excluded in the `value` array
* - "LEAF_PRIORITY" - If a branch node is checked, this node itself and its branch descendants will be excluded from the `value` array but its leaf descendants will be included
* - "ALL_WITH_INDETERMINATE" - Any node that is checked will be included in the `value` array, plus indeterminate nodes
*/
valueConsistsOf: {
type: String,
default: BRANCH_PRIORITY,
validator(value) {
const acceptableValues = [ALL, BRANCH_PRIORITY, LEAF_PRIORITY, ALL_WITH_INDETERMINATE]
return includes(acceptableValues, value)
},
},
/**
* Format of `value` prop.
* Note that, when set to `"object"`, only `id` & `label` properties are required in each `node` object in `value` prop.
* Acceptable values:
* - "id"
* - "object"
*/
valueFormat: {
type: String,
default: 'id',
},
/**
* z-index of the menu.
*/
zIndex: {
type: [Number, String],
default: 999,
}
}
项目代码 :
<!-- Vue SFC -->
<template>
<div id="app">
<Treeselect
v-model="selectedNodes"
:maxHeight="400"
:multiple="true"
:always-open="true"
:options="options"
:normalizer="normalize"
valueFormat="object"
placeholder="请输入搜索关键字"
noResultsText="未查询到您的搜索"
/>
<div class="tree-footer">
<van-button round type="info" @click="addInstitution"
>添加机构</van-button
>
<van-button round @click="closeAddInstitution">取消</van-button>
</div>
</div>
</template>
<script>
// import the component
import Treeselect from '@riophae/vue-treeselect';
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
export default {
// register the component
components: { Treeselect },
data() {
return {
// define the default selectedNodes
selectedNodes: null,
// define options
options: [
{
id: '研发中心本部',
label: '研发中心本部',
children: [
{
id: '中心办公室',
label: '中心办公室',
children: [
{ id: '张三', label: '张三' },
{ id: '李四', label: '李四' },
{ id: '王一一', label: '王一一' },
],
},
{
id: '中心技术二部',
label: '中心技术二部',
children: [
{ id: '赵六', label: '赵六' },
{ id: '七七', label: '七七' },
{ id: '八八', label: '八八' },
],
},
],
},
{
id: '资源管理中心',
label: '资源管理中心',
children: [
{
id: '外部资源部1',
label: '外部资源部1',
children: [
{
id: '外部资源部1.1.1',
label: '外部资源部1.1.1',
},
{
id: '外部资源部1.1.2',
label: '外部资源部1.1.2',
},
{
id: '外部资源部1.1.3',
label: '外部资源部1.1.3',
},
],
},
{
id: '外部资源部2',
label: '外部资源部2',
children: [
{
id: '外部资源部2.1.1',
label: '外部资源部2.1.1',
},
{
id: '外部资源部2.1.2',
label: '外部资源部2.1.2',
},
{
id: '外部资源部2.1.3',
label: '外部资源部2.1.3',
},
],
},
],
},
{
id: '园区其他机构',
label: '园区其他机构',
children: [
{
id: '园区其他机构1',
label: '园区其他机构1',
children: [
{
id: '园区其他机构1.1.1',
label: '园区其他机构1.1.1',
},
{
id: '园区其他机构1.1.2',
label: '园区其他机构1.1.2',
},
{
id: '园区其他机构1.1.3',
label: '园区其他机构1.1.3',
},
],
},
{
id: '园区其他机构2',
label: '园区其他机构2',
children: [
{
id: '园区其他机构2.1.1',
label: '园区其他机构2.1.1',
},
{
id: '园区其他机构2.1.2',
label: '园区其他机构2.1.2',
},
{
id: '园区其他机构2.1.3',
label: '园区其他机构2.1.3',
},
],
},
],
},
],
};
},
methods: {
// 自定义密钥名称
normalize(options) {
return {
id: options.id,
label: options.shortName,
children: options.branchs,
};
},
addInstitution() {
if (!this.selectedNodes.length) {
Notify('请先选择机构');
return;
}
this.$emit('addInstitution', this.selectedNodes);
},
closeAddInstitution() {
this.$emit('closeAddInstitution');
},
},
};
</script>
未完待续 。。。
推荐文章 🔗 : VueTreeselect_vue-treeselect_捡垃圾的小女孩的博客
版权归原作者 雨季mo浅忆 所有, 如有侵权,请联系我们删除。