手风琴效果 递归组件实战(vue)

show 效果

图片描述

说说我的思路

数据结构

{"flag":1,"data":[{"id":1,"name":"书法类型","child":[{"id":2,"name":"硬笔"},{"id":3,"name":"软笔"}]},{"id":4,"name":"奖品类型","child":[{"id":5,"name":"文房四宝"}]}]}

本来刚开始做的时候, 说是做个两级的菜单, 为了加深自己的理解, 特意用递归组件模式开发。做成无限的。减少下次开发的代码量。
原理:
假设本节点有childs 属性, 就无限递归下去, 直到本节点没有childs,结束递归。
大家想想一想:

  • 这个组件最终效果

形成一个树形dom结构(里面有相同的模块 spreadComp.vue)这个是 手风琴组件 中 最小的组件单元。

  • 里面的组件通信:

我采用 root级组件与子孙级组件通信(子孙组件的 事件 会分发到 root级组件, root 级组件通过更改自身状态响应事件, 同时向子孙组件发送事件),相当于 中央集权, 再从中央分发.

  • 重点 怎么知道 当前节点是展开的, 还是关闭我采用 codeList 及 '01-02-03' 代表 节点 01 、02 的树节点是展开的, 03 是结束节点。以此类推。。。

当点击 01-02-03 中 02节点, 02 节点 就会关闭子树。 再次点击 02节点 就会开启子树。

  • 展开动画 关闭动画.. 仿照 elem 过渡动画效果。(我感觉最难)

show 代码

图片描述

事件分发代码

// 父子事件 交互
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
// 借鉴 饿了吗 过渡组件库

<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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值