Vue 实现展开折叠效果
一、目标实现效果

除了使用jQuery的方式实现上述效果,同样可以在Vue实现,下面是解决办法:
二、步骤
1.创建collapse.js文件
const elTransition ="0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out";const Transition = {"before-enter"(el) {el.style.transition = elTransition;if (!el.dataset) el.dataset = {};el.dataset.oldPaddingTop = el.style.paddingTop;el.dataset.oldPaddingBottom = el.style.paddingBottom;el.style.height = 0;el.style.paddingTop = 0;el.style.paddingBottom = 0;},enter(el) {el.dataset.oldOverflow = el.style.overflow;if (el.scrollHeight !== 0) {el.style.height = el.scrollHeight + "px";el.style.paddingTop = el.dataset.oldPaddingTop;el.style.paddingBottom = el.dataset.oldPaddingBottom;} else {el.style.height = "";el.style.paddingTop = el.dataset.oldPaddingTop;el.style.paddingBottom = el.dataset.oldPaddingBottom;}el.style.overflow = "hidden";},"after-enter"(el) {el.style.transition = "";el.style.height = "";el.style.overflow = el.dataset.oldOverflow;},"before-leave"(el) {if (!el.dataset) el.dataset = {};el.dataset.oldPaddingTop = el.style.paddingTop;el.dataset.oldPaddingBottom = el.style.paddingBottom;el.dataset.oldOverflow = el.style.overflow;el.style.height = el.scrollHeight + "px";el.style.overflow = "hidden";},leave(el) {if (el.scrollHeight !== 0) {el.style.transition = elTransition;el.style.height = 0;el.style.paddingTop = 0;el.style.paddingBottom = 0;}},"after-leave"(el) {el.style.transition = "";el.style.height = "";el.style.overflow = el.dataset.oldOverflow;el.style.paddingTop = el.dataset.oldPaddingTop;el.style.paddingBottom = el.dataset.oldPaddingBottom;}};export default {name: "collapseTransition",functional: true,render(h, { children }) {const data = {on: Transition};return h("transition", data, children);}};
2.再次封装组件,提供slot插槽
<template><view><collapse><div class="collapse-wrap" v-show="isActive"><!-- @slot default --><slot></slot></div></collapse></view></template><script>/*** @description 折叠收缩组件* @created by lishengsong* @date 2020-04-24*/import collapse from '@/plugins/uiTools/collapse.js'export default {components: {collapse},props: {isActive: {type: Boolean,default: true}},onLoad() {console.log(collapse);},watch: {isActive(val) {return val}}}</script><style></style>
递归组件-树形控件预览及问题

在编写树形组件时遇到的问题:
**
- 组件如何才能递归调用?
- 递归组件点击事件如何传递?
一、树形控件基本结构及样式
<template><ul class="vue-tree"><li class="tree-item"><div class="tree-content"><!--节点内容--><div class="expand-arrow"></div><!--展开或收缩节点按钮--><div class="tree-label">小学</div><!--节点文本内容--></div><ul class="sub-tree"><!--子节点--><li class="tree-item expand"><div class="tree-content"><div class="expand-arrow"></div><div class="tree-label">语文</div></div></li><li class="tree-item"><div class="tree-content"><div class="expand-arrow"></div><div class="tree-label">数学</div></div></li></ul></li></ul></template><style lang="stylus">.vue-tree{list-style: none;padding: 0;margin: 0;.tree-item{cursor: pointer;transition: background-color .2s;.tree-content{position: relative;padding-left: 28px;&:hover{background-color: #f0f7ff;}}.expand-arrow{position: absolute;top: 0;left: 0;width: 28px;height: 28px;cursor: pointer;&::after{position: absolute;top: 50%;left: 50%;display: block;content: ' ';border-width: 5px;border-style: solid;border-color: transparent;border-left-color: #ccc;margin: -5px 0 0 -2.5px;transition: all .2s;}}&.expand{&>.tree-content{background-color: #f0f7ff;&>.expand-arrow{&::after{transform: rotate(90deg);margin: -2.5px 0 0 -5px;}}}}.tree-label{height: 28px;line-height: 28px;font-size: 14px;}.sub-tree{display: none;list-style: none;padding: 0 0 0 28px;margin: 0;}&.expand>.sub-tree{display: block;}&.no-child{&>.tree-content{&>.expand-arrow{display: none;}}}}}</style>
二、组件目录及数据结构
目录结构
vue-tree
- VueTree.vue 树形控件父组件
- TreeItem.vue 树形控件递归组件
树形控件数据结构
**
let treeData = [{text: "一级", // 显示的文字expand: false, // 默认是否展开children: [ // 子节点{text: "一级-1",expand: false,},{text: "一级-2",expand: false,children: [{text: "一级-2-1",expand: false,},{text: "一级-2-2",expand: false,}]}]}];
1.TreeItem.vue 代码
<template><li class="tree-item" :class="{expand: isExpand, 'no-child': !treeItemData.children || treeItemData.children.length === 0}"><div class="tree-content" @click="_clickEvent"><div class="expand-arrow" @click.stop="expandTree()"></div><div class="tree-label">{{treeItemData.text}}</div></div><ul class="sub-tree" v-if="treeItemData.children && treeItemData.children.length > 0"><!--TreeItem组件中调用TreeItem组件--><TreeItemv-for="item in treeItemData.children":tree-item-data="item":key="uuid()":tree-click-event="treeClickEvent"></TreeItem></ul></li></template><script>export default {name: "TreeItem",props: {treeItemData: {type: Object,default(){return {};}},// 节点点击事件treeClickEvent: {type: Function,default() {return function () {};}}},data(){return {// 节点是否展开isExpand: this.treeItemData.expand || false}},methods: {// 展开/收缩expandTree(flag){if(!this.treeItemData.children || this.treeItemData.children.length === 0){return;}if(typeof flag === 'undefined'){flag = !this.isExpand;}else{flag = !!flag;}this.isExpand = flag;},// 创建一个唯一iduuid(){let str = Math.random().toString(32);str = str.substr(2);return str;},// 节点点击事件_clickEvent(){// 如果有传递事件函数,则调用事件函数并传递当前节点数据及组件if(this.treeClickEvent && typeof this.treeClickEvent === 'function'){this.treeClickEvent(this.treeItemData, this);}}}}</script>
(一)解决组件如何才能递归调用问题
在组件模板内调用自身必须明确定义组件的name属性,并且递归调用时组件名称就是name属性。如在TreeItem.vue组件中组件的name名称为’TreeItem’,那么在template中调用时组件名称就必须是
当然也可以全局注册组件,具体可以查看vue官方文档 递归组件
(二)解决递归组件点击事件如何传递问题
我这里的解决方案是使用props将事件函数传递进来,在点击节点的时候调用事件函数,并把相应的数据传递进去。
之前也尝试过使用$emit的形式并把数据传递过去,由于是递归组件,这样一直$emit,到最外层时传递的数据就变了,比如传递是第3层节点的数据,到最后执行时数据就变成第1层节点的数据了
2.VueTree.vue 组件
<template><ul class="vue-tree"><TreeItemv-for="(item, index) in treeData":key="index":treeItemData="item":tree-click-event="treeClickEvent"></TreeItem></ul></template><script>import TreeItem from "./TreeItem";export default {name: "VueTreeMenu",components: {TreeItem},props: {// 树形控件数据treeData: {type: Array,default(){return [];}},// 节点点击事件treeClickEvent: {type: Function,default() {return function () {};}}}}</script><style lang="stylus">.vue-tree{list-style: none;padding: 0;margin: 0;.tree-item{cursor: pointer;transition: background-color .2s;.tree-content{position: relative;padding-left: 28px;&:hover{background-color: #f0f7ff;}}.expand-arrow{position: absolute;top: 0;left: 0;width: 28px;height: 28px;cursor: pointer;&::after{position: absolute;top: 50%;left: 50%;display: block;content: ' ';border-width: 5px;border-style: solid;border-color: transparent;border-left-color: #ccc;margin: -5px 0 0 -2.5px;transition: all .2s;}}&.expand{&>.tree-content{background-color: #f0f7ff;&>.expand-arrow{&::after{transform: rotate(90deg);margin: -2.5px 0 0 -5px;}}}}.tree-label{height: 28px;line-height: 28px;font-size: 14px;}.sub-tree{display: none;list-style: none;padding: 0 0 0 28px;margin: 0;}&.expand>.sub-tree{display: block;}&.no-child{&>.tree-content{/*padding-left: 0;*/&>.expand-arrow{display: none;}}}}}</style>
3.使用树形组件
<template><div class="app" id="app"><VueTree :tree-data="treeData2" :tree-click-event="treeClickEvent"></VueTree></div></template><script>import VueTree from "./components/vue-tree/VueTree";export default {name: 'app',data(){return {treeData2: [{text: "一级", // 显示的文字expand: false, // 默认是否展开children: [{text: "二级-1",expand: false,},{text: "二级-2",expand: false,children: [{text: "三级-1",expand: false,},{text: "三级-2",expand: false,children: [{text: "四级-1",expand: false,}]}]}]},{text: "一级-2",expand: false}]}},methods: {treeClickEvent(item, treeItem){console.log(item);}},components: {VueTree}}</script>
