数据结构css
{"flag":1,"data":[{"id":1,"name":"书法类型","child":[{"id":2,"name":"硬笔"},{"id":3,"name":"软笔"}]},{"id":4,"name":"奖品类型","child":[{"id":5,"name":"文房四宝"}]}]}
原本刚开始作的时候, 说是作个两级的菜单, 为了加深本身的理解, 特地用递归组件模式开发。作成无限的。减小下次开发的代码量。
原理:
假设本节点有childs 属性, 就无限递归下去, 直到本节点没有childs,结束递归。
你们想一想一想:vue
造成一个树形dom结构(里面有相同的模块 spreadComp.vue)这个是 手风琴组件 中 最小的组件单元。数组
我采用 root级组件与子孙级组件通讯(子孙组件的 事件 会分发到 root级组件, root 级组件经过更改自身状态响应事件, 同时向子孙组件发送事件),至关于 中央集权, 再从中央分发.数据结构
当点击 01-02-03 中 02节点, 02 节点 就会关闭子树。 再次点击 02节点 就会开启子树。dom
// 父子事件 交互 const eventMixin = {} eventMixin.install = (Vue, options) => { Vue.mixin({ methods: { // 向父组件 分发事件 sendFather (cpName , {event, playLoad}) { // 子向父节点 let parent = this.$parent const root = this.$root while (parent.$options.name !== cpName && parent !== root) { parent = parent.$parent } parent.$emit(event, playLoad) }, // 向子孙组件分发事件 sendInfiniteCd(cpName, {event, playLoad}) { // 最小组件 const sendChildMsg = (item) => { let mainC = item.$children mainC.map(cmp => { // 获取组件姓名 const name = cmp.$options.name if (name === cpName) { cmp.$emit(event, playLoad) sendChildMsg(cmp) } return }) } // 初始化函数 sendChildMsg(this) } } }) } export default eventMixin
spreadComp index.vue函数
<template> <transition name="dialog-fade"> <div class="spread-main-box"> <div class="all-sort" @click="selectAll" :class="{active: idList === ''}"> 所有 </div> <spread-comp v-for="item, index in list" :list="item" :key="index" :idList="idList" > </spread-comp> </div> </transition> </template> <script> import SpreadComp from "./spreadComp.vue" export default { name: "spreadMain", props: { // 初始化数组数据 list: { type: Array }, // '01-02-03'相似码 value: { type: String } }, watch: { value () { this.idList = this.value } }, data () { return { // 本地可操做码列表对应属性 value idList: this.value } }, created () { // 监听子组件点击事件 this.$on('son:tg', (e) => { const {close, idList, v} = e // close true 表明终止节点 及树形最后一级 if (close) { this.idList = e.idList this.$emit('input', e.idList) this.$emit('submit', v) } else { // 表明 有子树节点 点击事件, 向子树 分发事件 this.sendInfiniteCd("spreadComp", { event: 'sp:child', playLoad: idList }) } }) this.$nextTick(() => { const {idList} = this this.sendInfiniteCd("spreadComp", { event: 'sp:child', playLoad: idList }) }) }, methods: { selectAll () { this.idList = '' this.$emit('input', '') this.$emit('submit', '') } }, components: {SpreadComp} } </script> <style lang="scss"> .spread-main-box { position: fixed; width: 10rem; top: 0; height: 100%; left: 50%; transform: translate(-50%, 0); background: #f3f1f1; z-index: 999; padding: 0 20px; .all-sort { height: 80px; line-height: 80px; font-size: 30px;; /*px*/ margin-bottom: 10px; background: #fff; padding-left: 20px; &.active { color: #c70002; } } } </style>
spreadComp spreadComp.vue动画
<template> <section class="spread-comp-child"> <template v-if="list.child"> <div class="spread-comp-child-item-title" @click="selectTg" :class="{active: isToggle}">{{list.name}}</div> <spread-transition> <div class="spread-comp-child-item-con" v-if="isToggle"> <div v-for="item, index in list.child " :key="index" v-if="item.child"> <spread-comp :list="item" :idList="idList" :fId= `${cfId}${list.id}` > </spread-comp> </div> <div :key="index" class="spread-comp-child-item-select" v-else @click="selectEnd(item.id, item.name)" :class="{active: idList === `${cfId}${list.id}-${item.id}`}" > {{item.name}} </div> </div> </spread-transition> </template> <template v-else> <div class="spread-comp-child-item-select" @click="selectEnd(false, item.name)" :class="{active: idList === `${cfId}${list.id}`}" > {{item.name}} </div> </template> </section> </template> <script> import spreadTransition from "../spread/spreadTransition.vue" export default { components: {spreadTransition}, name: 'spreadComp', props: { list: { type: Object }, idList: { type: String }, //上级id列表 fId: { default : function () { return '' } } }, data () { return { isToggle: false } }, created () { // 监听子事件 this.$on('sp:child', (idList) => { const {cfId, list, isToggle} = this if (new RegExp(`${cfId}${list.id}`).exec(idList)) { if (isToggle) { this.isToggle = false } else { this.isToggle = true } } else { this.isToggle = false } }) }, computed: { cfId () { return this.fId === '' ? '' : `${this.fId}-` }, isFtg () { const {idList, cfId, list} = this return new RegExp(`${cfId}${list.id}`).exec(idList) } }, methods: { selectEnd (id, v) { if (id === false) { this.sendFather('spreadMain', { event: "son:tg", playLoad: { idList: `${this.cfId}${this.list.id}`.replace(/^-/, ''), close: true, v } }) } else { this.sendFather('spreadMain', { event: "son:tg", playLoad: { idList: `${this.cfId}${this.list.id}-${id}`.replace(/^-/, ''), close: true, v } }) } }, selectTg () { this.sendFather('spreadMain', { event: "son:tg", playLoad: { idList: `${this.cfId}${this.list.id}`.replace(/^-/, ''), close: false } }) } } } </script> <style lang="scss"> .spread-comp-child { font-size: 30px; /*px*/ color: #333333; .spread-comp-child-item-con { padding-left: 20px; } .spread-comp-child-item-title,.spread-comp-child-item-select { position: relative; height: 70px; line-height: 70px; text-align: left; margin-top: 10px; background: #fff; padding-left: 20px; } .spread-comp-child-item-select { &.active { color: #c70002; } } .spread-comp-child-item-title { &::after { position: absolute; content: ''; display: block; top: 50%; right: 20px; width: 36px; height: 36px; background: url(../../assert/img/arrow-gray.png) 0 0 no-repeat; background-size: 36px 36px; transform: translate(0, -50%); transition: all ease 300ms; } &.active { &::after { transform: translate(0, -50%) rotate(-180deg); } } } } </style>
spread spreadTransition.vue
// 借鉴 饿了吗 过渡组件库this
<script> import {addClass, removeClass, on, off} from '../../utils/dom' export default { functional: true, render(h, ct) { let height return h('transition', { props: { css: false }, on: { beforeEnter(el) { addClass(el, 'transition-am') el.style.height = 0 el.style.overflow = 'hidden' }, enter(el, done) { const fn = () => { done() removeClass(el, 'transition-am') off({el, type: 'transitionend', fn}) el.style.height = '' el.style.overflow = '' } on({el, type: 'transitionend', fn}) setTimeout(() => { height = el.scrollHeight el.style.height = `${height}px` el.style.overflow = 'hidden' }, 5) }, beforeLeave(el) { const height = el.scrollHeight addClass(el, 'transition-am') el.style.height = `${height}px` el.style.overflow = 'hidden' el.setAttribute('data-state', 'beforeLeave') }, leave(el, done) { const fn = () => { done() removeClass(el, 'transition-am') off( { el, type: 'transitionend', fn } ) el.style.height = '' el.style.overflow = '' el.style.opacity = '' } on({ el, type: 'transitionend', fn }) setTimeout(() => { el.style.height = '0' el.style.opacity = 0 }, 5) } } }, ct.children) } } </script> <style lang="scss"> .transition-am { transition: all 500ms; transform: translate3d(0, 0, 0); } </style>