手风琴组件
大纲链接 §
[toc]
需求分析
- 可展开显示项目内容
- 可折叠隐藏内容区
- 可禁用
- 可配置一次只可展开一个项目,折叠其他项目
- 默认折叠时显示向右箭头,展开时显示向下箭头
项目>标题>内容
|
|
API设计
创建手风琴组件
- 父组件
VueCollapse.vue - 子组件
VueCollapseItem.vue - demo展示组件
Collapses.vue
显示/折叠内容区
VueCollapse.vue
|
|
VueCollapseItem.vue
点击切换
@click="toggle"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102<template> <div class="collapse-item"> <header class="title" :class="{ 'title-show': isOpen}" @click="toggle"> {{ title }} </header> <article class="content" :class="{ 'content-show': isOpen}" v-show="isOpen && !isDisabled"> <slot> VueCollapseItem </slot> </article> </div> </template> <script lang="ts"> import {Component, Prop, Vue} from 'vue-property-decorator'; @Component export default class VueCollapseItem extends Vue { name = 'VueCollapseItem'; isOpen = false; @Prop({ type: String, default: '', required: true }) title!: string; toggle() { this.isOpen = !this.isOpen; } } </script> <style lang="scss" scoped> $grey: #999; $border-radius: 4px; @mixin border-bottom-radius($radius: 0px) { border-bottom-left-radius: $radius; border-bottom-right-radius: $radius; } .collapse-item { display: flex; align-items: center; flex-wrap: wrap; width: 100%; border-bottom: 1px solid $grey; > .title { width: 100%; min-height: 32px; display: flex; align-items: center; padding: 0 8px; } > .content { width: 100%; padding: 18px; &.content-show { border-top: 1px solid $grey; } } // .collapse-item &:first-child { > .title { // v-show = true &.title-show { border-top: none; } } } // .collapse-item &:last-child { border-bottom: none; // 覆盖 border-bottom: 1px solid $grey; > .title { // v-show = true &.title-show { @include border-bottom-radius; } } > .content { @include border-bottom-radius($border-radius); } } } </style>
Collapses.vue
|
|
禁用显示/折叠
子组件添加
@Prop({type: Boolean, default: false}) isDisabled!: boolean;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79<template> <div class="collapse-item"> <header class="title" :class="{ 'title-show': isOpen && !isDisabled, 'disabled': isDisabled}" @click="toggle"> {{ title }} </header> <article class="content" :class="{ 'content-show': isOpen && !isDisabled}" v-show="isOpen && !isDisabled"> <slot> VueCollapseItem </slot> </article> </div> </template> <script lang="ts"> import {Component, Inject, Prop, Vue} from 'vue-property-decorator'; @Component export default class VueCollapseItem extends Vue { name = 'VueCollapseItem'; isOpen = false; @Prop({ type: String, default: '', required: true }) title!: string; @Prop({ type: Boolean, default: false }) isDisabled!: boolean; @Inject() readonly eventBus!: Vue; @Inject() readonly isAllShowSingle!: boolean; toggle() { if (this.isOpen) { this.eventBus.$emit('remove:selected', this.title); } else { this.eventBus.$emit('add:selected', this.title); } } // listen to parent addBusListener() { this.eventBus?.$on('update:selected', (titleList: Array<string>) => { if (titleList.includes(this.title)) { this.isOpen = true; } else { this.isOpen = false; } }); } initShow() { this.eventBus.$once('update:selected', (titleList: Array<string>) => { if (titleList.includes(this.title)) { this.isOpen = true; } }); } mounted() { // listen to parent once this.initShow(); // listen to parent this.addBusListener(); } } </script> <style lang="scss" scoped>...</style>
一种实现single的方式
- 使用事件总线
eventBus - 父组件提供数据
@Provide() eventBus = new Vue(); - 子组件注入数据
@Inject() readonly eventBus!: Vue;
VueCollapse.vue
|
|
VueCollapseItem.vue
|
|
实现 单向通知 执行回调
- 子组件不直接变更状态
- 子组件将变更通知发布给
eventBus
组件经验总结
- 单向数据流
- 子组件不直接改变数据
- 点击子组件不直接改变
this.isOpen
- 子组件监听 父组件传递给eventBus的事件,然后执行相应的回调函数
- 子组件听从 父组件发布给eventBus的事件 间接改变数据
参考: