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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。