數據結構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>