vue造轮子之封装选择下拉树
vue造轮子之封装选择下拉树
vue造轮子之封装选择下拉树最近公司有个新需求,希望能有一个下拉选择树的功能,大概的功能和样式如下所示:
然后我的第一反应就是上elementui上找现成的组件,但是挺遗憾的就是element并没有提供这样的组件,所以只能自己动手造一个了。
1. 组件需求
(1) 支持单选和多选功能
(2) 叶子节点控制是否能选择
(3) 数据回显到选择框支持多选和单选显示
(4) 支持树节点搜索功能
(5) 基本样式应与elementUi样式保持一致
2.布局和样式代码编写
由于这个地方需要用到弹出下拉框,所以我就借助了el-popover来实现这个功能,代码如下:
<template> <div class="ka-tree-select" :style="{width:width+'px'}"> <el-popover placement="bottom" :width="width" trigger="click"> <div class="ka-select-box" slot="reference"> <div class="tag-box"> <div v-show="selecteds.length>0"> 显示的内容 </div> <p class="ka-placeholder-box" v-show="selecteds.length===0">请输入内容</p> </div> </div> </el-popover> </div> </template> <script> export default { name: "treeSelect", props: { width: { type: [String, Number], default: 200 } }, data() { return { selecteds: [] // 选择到的数据 }; } }; </script> <style lang="scss" scoped> .ka-tree-select{ position: relative; display: inline-block; width: 100%; vertical-align: middle; outline: none; .ka-select-box { display: flex; border: 1px solid #dcdfe6; padding: 0 5px 0 8px; width: 100%; min-height: 36px; // height: 36px; line-height: 34px; box-sizing: border-box; border-radius: 4px; cursor: pointer; outline: none; &:focus { border-color: #409eff; } > .tag-box { display: inline-block; width: calc(100% - 20px); text-align: left; } > .icon-box { float: right; display: flex; width: 20px; justify-content: center; align-items: Center; color: #c0c4cc; // transform: rotateZ(45deg); .el-icon-arrow-down { transition: all 0.2s; } .down { transform: rotateZ(180deg); } } .ka-placeholder-box { color: #c0c4cc; margin: 0; } } } </style>
样式和基本结构也已经构建好了,值得注意一点就是HTML结构中,.tag-box这个类名容器中会存放所选择到的数据,是用来存放单选和多选的页签。
3.树结构制作
这里的树结构我主要使用到了el-tree这个组件,为了让它有个好看的滚动条,这里我也选择了el-scrollBar滚动条组件,值得一提的就是使用el-scrollbar组件时候,必须给父容器添加一个高度,然后el-scrollBar的高度为100%,要不然滚动条为出不来。代码如下:
<template> <div class="ka-tree-select" :style="{width:width+'px'}"> <el-popover placement="bottom" :width="width" trigger="click"> <el-input v-if="filterable" v-model="filterText" :size="size" placeholder="请输入关键词"></el-input> <el-scrollbar class="ka-treeselect-popover"> <el-tree :data="data" :props="selfProps" :node-key="nodeKey" highlight-current :default-checked-keys="checked_keys" :default-expanded-keys="expandedKeys" :show-checkbox="checkbox" check-strictly ref="tree-select" :filter-node-method="filterNode" ></el-tree> <!-- @check="handleCheckChange" --> <!-- @node-click="handleNodeClick" --> </el-scrollbar> <div class="ka-select-box" slot="reference"> <div class="tag-box"> <div v-show="selecteds.length>0"> 显示的内容 </div> <p class="ka-placeholder-box" v-show="selecteds.length===0">请输入内容</p> </div> </div> </el-popover> </div> </template> <script> export default { name: "treeSelect", props: { width: { // 宽度 type: [String, Number], default: 200 }, size: { // 尺寸 type: String, default: "mini" }, data: { // 树结构的数据 type: Array, default: () => [] }, nodeKey: { // 树结构的唯一标识 type: String, default: "id" }, // 是否使用搜索 filterable: { type: Boolean, default: true }, // 显示的字段 props: { type: Object, default: () => ({ label: "label", children: "children", }) }, // 是否可多选 checkbox: { type: Boolean, default: false }, }, data() { return { selecteds: [], // 选择到的数据 checked_keys: [], // 默认选中的数据 expandedKeys: [], // 默认展开的数据 filterText: "" // 筛选的数据 }; }, computed: { selfProps () { return { label: "label", children: "children", disabled: data => data.disabled, ...this.props }; }, }, methods: { // 树节点筛选 filterNode (value, data) { if (!value) { return true; } return data[this.selfProps.label].indexOf(value) !== -1; } }, watch: { filterText (val) { this.$refs["tree-select"].filter(val); } } }; </script> <style lang="scss" scoped> .ka-tree-select{ position: relative; display: inline-block; width: 100%; vertical-align: middle; outline: none; .ka-select-box { display: flex; border: 1px solid #dcdfe6; padding: 0 5px 0 8px; width: 100%; min-height: 36px; // height: 36px; line-height: 34px; box-sizing: border-box; border-radius: 4px; cursor: pointer; outline: none; &:focus { border-color: #409eff; } > .tag-box { display: inline-block; width: calc(100% - 20px); text-align: left; } > .icon-box { float: right; display: flex; width: 20px; justify-content: center; align-items: Center; color: #c0c4cc; // transform: rotateZ(45deg); .el-icon-arrow-down { transition: all 0.2s; } .down { transform: rotateZ(180deg); } } .ka-placeholder-box { color: #c0c4cc; margin: 0; } } } .ka-treeselect-popover { height: 360px; /deep/ .el-scrollbar__wrap { overflow-x: hidden; } } </style>
4.处理显示的值
接下来的话就轮到控制显示输入框的值了,我们要考虑一点,作为公共组件是如何提供给业务组件使用的问题,我假设在业务组件A中,使用了这个值,并且期望它的使用方式如下:
<template> <div style="padding:10px"> <el-row> <el-col :span="12"> <treeSelect :data="treeData" v-model="select" checkbox :width="500" ref="treeSelect" ></treeSelect> </el-col> </el-row> </div> </template> <script> import treeSelect from "@/components/treeSelect/test"; export default { components: { treeSelect }, data () { return { treeData: [{ 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" }, { id: 18, label: "二级 3-2" }, { id: 82, label: "二级 3-2" }, { id: 84, label: "二级 3-2" }, { id: 842, label: "二级 3-2" }, { id: 847, label: "二级 3-2" }] }, { id: 11, label: "最外面" } ], select: [9, 5] }; } }; </script> <style> </style>
select这个变量代表我选中了这两个节点,还可以默认展开,并且是在输入框中显示所对应的label值,那么就回到treeSelect组件中编写对应的逻辑。在created中定义处理默认值的方法handDefaultValue
// 处理默认值的方法 handDefaultValue(value) { if (!Array.isArray(value) || value.length === 0) { return; } this.expandedKeys = []; if (!this.checkbox) { // 单选的情况 this.$nextTick(() => { this.$refs["tree-select"].setCurrentNode({ id: value[0] }); let currentNode = this.$refs["tree-select"].getCurrentNode(); this.expandedKeys.push(value[0]); this.selecteds = [currentNode]; this.$emit("change", this.selecteds); }); } else { // 多选的情况 this.$nextTick(() => { this.$refs["tree-select"].setCheckedKeys(value); let currentAllNode = this.$refs["tree-select"].getCheckedNodes(); value.forEach(v => { this.expandedKeys.push(v); }); this.selecteds = currentAllNode; this.$emit("change", this.selecteds); }); } }
这个时候我们的selecteds数组中已经初始化了选中当前树节点的数据,这个时候我们只需要把树节点对应的label渲染出来即可,这里我使用到el-tag这个组件,我把文字的显示分成了两种情况一种是平铺展示,一种折叠显示,代码如下:
<template v-if="!collapseTags"> <el-tag closable :size="size" v-for="item in selecteds" :title="item[selfProps.label]" :key="item[nodeKey]" class="ka-select-tag" @close="tabClose(item[nodeKey])" >{{ item[selfProps.label] }}</el-tag> </template> <template v-else> <el-tag closable :size="size" class="ka-select-tag" :title="collapseTagsItem[selfProps.label]" @close="tabClose(collapseTagsItem[nodeKey])" >{{ collapseTagsItem[selfProps.label] }}</el-tag> <el-tag v-if="this.selecteds.length>1" :size="size" class="ka-select-tag" >+{{ this.selecteds.length-1}}</el-tag> </template>
此时完整的代码:
<template> <div class="ka-tree-select" :style="{width:width+'px'}"> <el-popover placement="bottom" :width="width" trigger="click"> <el-input v-if="filterable" v-model="filterText" :size="size" placeholder="请输入关键词"></el-input> <el-scrollbar class="ka-treeselect-popover"> <el-tree :data="data" :props="selfProps" :node-key="nodeKey" highlight-current :default-checked-keys="checked_keys" :default-expanded-keys="expandedKeys" :show-checkbox="checkbox" check-strictly ref="tree-select" :filter-node-method="filterNode" ></el-tree> <!-- @check="handleCheckChange" --> <!-- @node-click="handleNodeClick" --> </el-scrollbar> <div class="ka-select-box" slot="reference"> <div class="tag-box"> <div v-show="selecteds.length>0"> <template v-if="!collapseTags"> <el-tag closable :size="size" v-for="item in selecteds" :title="item[selfProps.label]" :key="item[nodeKey]" class="ka-select-tag" @close="tabClose(item[nodeKey])" >{{ item[selfProps.label] }}</el-tag> </template> <template v-else> <el-tag closable :size="size" class="ka-select-tag" :title="collapseTagsItem[selfProps.label]" @close="tabClose(collapseTagsItem[nodeKey])" >{{ collapseTagsItem[selfProps.label] }}</el-tag> <el-tag v-if="this.selecteds.length>1" :size="size" class="ka-select-tag" >+{{ this.selecteds.length-1}}</el-tag> </template> </div> <p class="ka-placeholder-box" v-show="selecteds.length===0">请输入内容</p> </div> </div> </el-popover> </div> </template> <script> export default { name: "treeSelect", props: { width: { // 宽度 type: [String, Number], default: 200 }, size: { // 尺寸 type: String, default: "mini" }, data: { // 树结构的数据 type: Array, default: () => [] }, nodeKey: { // 树结构的唯一标识 type: String, default: "id" }, // 是否使用搜索 filterable: { type: Boolean, default: true }, // 显示的字段 props: { type: Object, default: () => ({ label: "label", children: "children", }) }, // 是否可多选 checkbox: { type: Boolean, default: false }, // 选中数据 value: [Array], // 多选时是否将选中值按文字的形式展示 collapseTags: { type: Boolean, default: false }, }, created () { this.handDefaultValue(this.value); }, data() { return { selecteds: [], // 选择到的数据 checked_keys: [], // 默认选中的数据 expandedKeys: [], // 默认展开的数据 filterText: "" // 筛选的数据 }; }, computed: { selfProps () { return { label: "label", children: "children", disabled: data => data.disabled, ...this.props }; }, }, methods: { // 树节点筛选 filterNode (value, data) { if (!value) { return true; } return data[this.selfProps.label].indexOf(value) !== -1; }, // 处理默认值的方法 handDefaultValue(value) { if (!Array.isArray(value) || value.length === 0) { return; } this.expandedKeys = []; if (!this.checkbox) { // 单选的情况 this.$nextTick(() => { this.$refs["tree-select"].setCurrentNode({ id: value[0] }); let currentNode = this.$refs["tree-select"].getCurrentNode(); this.expandedKeys.push(value[0]); this.selecteds = [currentNode]; this.$emit("change", this.selecteds); }); } else { // 多选的情况 this.$nextTick(() => { this.$refs["tree-select"].setCheckedKeys(value); let currentAllNode = this.$refs["tree-select"].getCheckedNodes(); value.forEach(v => { this.expandedKeys.push(v); }); this.selecteds = currentAllNode; this.$emit("change", this.selecteds); }); } } }, watch: { filterText (val) { this.$refs["tree-select"].filter(val); } } }; </script> <style lang="scss" scoped> .ka-tree-select{ position: relative; display: inline-block; width: 100%; vertical-align: middle; outline: none; .ka-select-box { display: flex; border: 1px solid #dcdfe6; padding: 0 5px 0 8px; width: 100%; min-height: 36px; // height: 36px; line-height: 34px; box-sizing: border-box; border-radius: 4px; cursor: pointer; outline: none; &:focus { border-color: #409eff; } > .tag-box { display: inline-block; width: calc(100% - 20px); text-align: left; } > .icon-box { float: right; display: flex; width: 20px; justify-content: center; align-items: Center; color: #c0c4cc; // transform: rotateZ(45deg); .el-icon-arrow-down { transition: all 0.2s; } .down { transform: rotateZ(180deg); } } .ka-placeholder-box { color: #c0c4cc; margin: 0; } .ka-select-tag { max-width: 100%; text-overflow: ellipsis; overflow: hidden; word-wrap: break-word; word-break: break-all; vertical-align: middle; } .ka-select-tag + .ka-select-tag { margin-left: 4px; } } } .ka-treeselect-popover { height: 360px; /deep/ .el-scrollbar__wrap { overflow-x: hidden; } } </style>
这个时候只需关注点击树节点所触发的函数,点击树节点el-checkBox和关闭el-tag时的函数即可。
// 点击checkbox变化 handleCheckChange (val) { let nodes = this.$refs["tree-select"].getCheckedNodes(false); this.selecteds = nodes; this.$emit("change", this.selecteds); }, // 点击列表树 handleNodeClick (item, node) { if (this.checkbox) { return; } this.selecteds = [item]; // this.options_show = false; this.$emit("change", this.selecteds); }, // tag标签关闭 tabClose (id) { if (this.disabled) { return; } if (!this.checkbox) { // 单选 this.selecteds = []; this.$refs["tree-select"].setCurrentKey(null); } else { // 多选 this.$refs["tree-select"].setChecked(id, false, true); this.selecteds = this.$refs["tree-select"].getCheckedNodes(); } this.$emit("change", this.selecteds); },
那么最后还有最后一个小的功能点,就是实现只能选择叶子节点的功能,那么我们就定义一个变量isLeaf来进行标识。在el-tree中只需要给节点添加上disabled字段即可标识当前节点是否可选了,那么我们只需要遍历这个树节点,循坏遍历,判断当前节点的children字段是否是大于0,大于0,disable则为true。
// 转换treedata changeTreeData (data) { if (!data) { return; } let stack = []; data.forEach(v => { stack.push(v); }); while (stack.length) { const result = stack.shift(); if (result.children && result.children.length > 0) { result.disabled = true; stack = stack.concat(result.children); } else { result.disabled = false; } } return data; }, created(){ this.isLeaf && this.changeTreeData(this.data); }
这个时候已经完成了下拉选择树的功能,功能要点如下:
(1) 支持单选和多选功能
(2) 叶子节点控制是否能选择
(3) 数据回显到选择框支持多选和单选显示
(4) 支持树节点搜索功能
(5) 基本样式应与elementUi样式保持一致
源码地址为:https://github.com/whenTheMorningDark/vue-kai-admin/blob/master/src/components/treeSelect/index.vue
vue造轮子之封装选择下拉树相关教程
-
Vue Element Ui中 Table 表格更改某一列的样式,比如说背景色
Vue Element Ui中 Table 表格更改某一列的样式,比如说背景色 先在Table 表格 上加 上属性 el-table :data=tableList border :cell-style=columnStyle el-table-column type=index label=第一列 width=50px/el-table-column el-table-column type=index label
-
Vue通过eventBut实现组件全局通信
Vue通过eventBut实现组件全局通信 一、组件之间的层级关系如下图: 现要在test_page_1.vue 组件中改变,MyHeader.vue组件中的某个属性值。 二、eventBus简介: EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用
-
RxJS结合vue-rx, Akita的介绍和使用
RxJS结合vue-rx, Akita的介绍和使用 介绍 : 解决异步事件。它将一切数据包括HTTP请求,DOM事件或者普通数据等包装成流的形式,然后利用强大丰富的操作符(Operators )对流进行处理,使得用户可以用同步编程的方式处理异步数据。 基本概念 :Observable 类
-
vue Element 利用Cascader 级联选择器实现三层选择器
vue Element 利用Cascader 级联选择器实现三层选择器 在这次项目需求中要实现的功能和选择地址的过程是很相似的,就用选择地址的过程来举例:首先是选择省,然后是市,再是某个区。在实际的开发过程中这种场景是很常见的,所以我就分享一下我的实现过程。 效
-
nginx部署多个vue项目如何配置
nginx部署多个vue项目如何配置 使用同一域名或者ip去部署访问多个前端项目,比如域名/ip直接访问官网,域名/ip后面带路径去访问其它项目 官网访问地址: http://192.168.27.119/login项目二访问地址:http://192.168.27.119/biz/login项目三访问地址:http://1
-
在 vue 中使用 echarts 的详细步骤
在 vue 中使用 echarts 的详细步骤 年龄大了,果然脑力是跟不上了,这个图表已经用过几次了,每次还是或多或少的出现问题,现在把 echarts 在 vue 中的详细使用步骤记录下来,以备不时之需吧。 echarts 图表绘制的思路是: 1 获取一个有宽高的 DOM 元素 -- 2
-
Jenkins + Maven + Github/Gitlab + Springboot/Vue.js 实现自动
Jenkins + Maven + Github/Gitlab + Springboot/Vue.js 实现自动化部署 Jenkins用户文档地址 Jenkins在docker环境下安装非常简单。只需要执行命令 docker run \ -u root \ --rm \ -d \ -p 8080:8080 \ -p 50000:50000 \ -v jenkins-data:/var/jenkins_home \
-
VuePress 侧边栏使用详解
VuePress 侧边栏使用详解 前言 官网写的侧边栏教程真的是让人很糟心,经过一番摸索,总算大致弄清楚了,下面详细说一下用法: 目录结构: docs根目录下有一个README.md、chinese文件夹、english文件夹 最简侧边栏: sidebar: { '/language/chinese/': [ '', /