从零搭建Vue3.0组件库之折叠菜单

一.Collapse组件

<template>
  <div class="z-collapse">
    <slot></slot>
  </div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";

export default defineComponent({
  name: "ZCollapse",
  props: {
    accordion: Boolean, // 是否是手风琴模式
    modelValue: {       // 当前展开的值
      type: [Array, String] as PropType<string | Array<string>>,
      default:()=>[]
    },
  },
  setup(props,{emit}){
      return {}
  }
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

二.CollapseItem组件

<template>
  <div class="z-collapse-item">
    <div class="z-collapse-item__header">
      <slot name="title">{{title}}</slot>
    </div>
    <div class="z-collapse-item__content">
      <slot></slot>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "ZCollapseItem",
  props: {
    title: {
      type: String,
      default: "",
    },
    name: {
      type: [String, Number],
      default: () => Math.floor(Math.random() * 10000),
    },
    disabled: Boolean,
  }
});
</script>
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

CollapseItem为折叠菜单中的每一项

@import "mixins/mixins";
@import "common/var";

@include b(collapse) {
    border-top: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    font-size: 13px;
    
}

@include b(collapse-item) {
    @include e(header) {
        display: flex;
        cursor: pointer;
        line-height: 48px;
        border-bottom: 1px solid #ccc;
    }
    @include e(content) {
        padding-bottom: 25px;
        border-bottom: 1px solid #ccc;
    }
    &:last-child {
        margin-bottom: -1px;
    }
}
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

三.组件间跨级通信

父组件用于收集子组件的状态。 collapse.vue

import mitt from "mitt";

const activeNames = ref([].concat(props.modelValue)); // 当前激活的列表
const collapseMitt = mitt(); // 用于子类触发事件

watch(()=>props.modelValue,()=>{ // 值变化后,更新当前激活的activeNames
    activeNames.value = [].concat(props.modelValue)
})
provide("collapse", { // 提供给子组件
    activeNames,
    collapseMitt
});
const handleItemClick = (name) => {}; // 监听子组件的点击事件

collapseMitt.on("item-click", handleItemClick);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

子组件注入父组件提供的数据

<template>
  <div class="z-collapse-item">
    <div class="z-collapse-item__header" @click="handleHeaderClick">
      <slot name="title">{{ title }}</slot>
    </div>
    <div class="z-collapse-item__content" v-show="isActive">
      <slot></slot>
    </div>
  </div>
</template>
<script lang="ts">
import mitt, { Emitter } from "mitt";
import { defineComponent, inject, Ref } from "vue";

export default defineComponent({
  name: "ZCollapseItem",
  props: {
    title: {
      type: String,
      default: "",
    },
    name: {
      type: [String, Number],
      default: () => Math.floor(Math.random() * 10000),
    },
    disabled: Boolean,
  },
  setup(props) {
    const collapse = inject<{ activeNames: Ref; collapseMitt: Emitter }>(
      "collapse"
    );
    const handleHeaderClick = () => { // 点击时触发
      if (props.disabled) return;
      collapse.collapseMitt.emit("item-click", props.name);
    };
    const isActive = computed(() => { // 当前是否展开
      return collapse.activeNames.value.includes(props.name);
    });
    return {
      handleHeaderClick,
      isActive,
    };
  },
});
</script>
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

四.手风琴效果

const setValue = (_activeNames) => {
    activeNames.value = [].concat(_activeNames);
    const value = props.accordion ? activeNames.value[0] : activeNames.value;
    emit("update:modelValue", value);
};

const handleItemClick = (name) => { 
    if (props.accordion) { // 手风琴效果
        let v = activeNames.value[0] === name ? "" : name;
        setValue(v);
    } else { // 默认展开效果
        let _activeNames = activeNames.value.slice(0);
        const index = _activeNames.indexOf(name);
        if (index > -1) {
            _activeNames.splice(index, 1);
        } else {
            _activeNames.push(name);
        }
        setValue(_activeNames);
    }
};

collapseMitt.on("item-click", handleItemClick);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

五.过渡动画组件

<template>
  <transition v-on="on">
    <slot></slot>
  </transition>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  setup() {
    return {
      on: {
        beforeEnter() {
          console.log("beforeEnter");
        },
        enter() {
          console.log("enter");
        },
        afterEnter() {
          console.log("afterEnter");
        },
        beforeLeave() {
          console.log("beforeLeave");
        },
        leave() {
          console.log("leave");
        },
        afterLeave() {
          console.log("afterLeave");
        },
      },
    };
  },
});
</script>
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

transition组件通过js控制动画

<ZTransition>
    <div v-show="isActive">
        <div class="z-collapse-item__content">
            <slot></slot>
        </div>
    </div>
</ZTransition>
1
2
3
4
5
6
7
<template>
  <transition v-on="on">
    <slot></slot>
  </transition>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  setup() {
    return {
      on: {
        beforeEnter(el) { // 进入前 添加过度 设为初始状态
          el.classList.add("collapse-transition");
          el.style.height = 0;
        },
        enter(el) { // 设置结束状态
          el.style.height = el.scrollHeight + "px";
          el.style.overflow = "hidden";
        },
        afterEnter(el) { // 完成后删除样式
          el.classList.remove("collapse-transition");
        },
        leave(el) { // 离开时 设置离开的目标
          el.classList.add("collapse-transition");
          el.style.height = 0;
        },
        afterLeave(el) { // 离开后删除样式即可
          el.classList.remove("collapse-transition");
        }
      }
    };
  }
});
</script>
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