Collapse组件

一.完成静态的Collapse

Collapse.vue:

js 复制代码
<template>
    <div class="lu-collapse">
        <slot></slot>
    </div>
</template>
<script setup lang="ts">
defineOptions({
    name: 'LuCollapse'
})
</script>

CollapseItem.vue:

js 复制代码
<template>
    <div class="lu-collapse-item" :class="{
        'is-disabled': disabled
    }">
        <div class="lu-collapse-item__header" :id="`item-header-${name}`">
            <slot name="title">{{ title }}</slot>

        </div>
        <div class="lu-collapse-item__content" :id="`item-content-${name}`">
            <slot></slot>
        </div>
    </div>
</template>
<script setup lang="ts">
import type { CollapseItemProps } from './types'
defineProps<CollapseItemProps>()
defineOptions({
    name: 'LuCollapseItem'
})
</script>

相应的types.ts:

js 复制代码
export interface CollapseItemProps {
    name: string | number;
    title?: string; //可选的可以不传
    disabled?: boolean;

}

App.vue中使用:

js 复制代码
<Collapse>
      <CollapseItem name="a">
        <template #title>
          <h1>a title</h1>
        </template>
        <div> this is content a aaa </div>
      </CollapseItem>
      <CollapseItem name="b" title="nice title b item b">
        <div> this is bbbbb test </div>
      </CollapseItem>
      <CollapseItem name="c" title="nice cccc">
        <div> this is cccc test </div>
      </CollapseItem>
    </Collapse>

初步实现效果:

二.添加折叠效果

因为这里的子组件是在父组件的slot中的,不能通过prop属性来实现信息传递,可以使用provide和inject

Collapse 组件中
  • 使用 ref 创建一个响应式变量 activeNames,用于存储当前激活的折叠项的名称。
  • 定义一个方法 handleItemClick,用于处理点击事件,切换折叠项的激活状态。
  • 使用 provide 函数将 activeNameshandleItemClick 方法提供给子孙组件。这里使用了 collapseContextKey 作为键,这是一个通过 Symbol 创建的唯一标识符,用于确保提供和注入的上下文是唯一的。
javascript 复制代码
const activeNames = ref<NameType[]>([]);
const handleItemClick = (item: NameType) => {
    const index = activeNames.value.indexOf(item);
    if (index > -1) {
        activeNames.value.splice(index, 1);
    } else {
        activeNames.value.push(item);
    }
};
provide(collapseContextKey, {
    activeNames,
    handleItemClick
});
CollapseItem 组件中
  • 使用 inject 函数注入从父组件 Collapse 提供的上下文。这允许访问 activeNameshandleItemClick
  • 定义一个计算属性 isActive,用于确定当前项是否激活,这是基于 activeNames 的内容。
  • 定义一个方法 handleClick,当点击折叠项的头部时调用,如果项未被禁用,则调用注入的 handleItemClick 方法。
javascript 复制代码
const collapseContext = inject(collapseContextKey);
const isActive = computed(() => collapseContext?.activeNames.value.includes(props.name));
const handleClick = () => {
    if (props.disabled) return;
    collapseContext?.handleItemClick(props.name);
};

代码:

types.ts:

js 复制代码
export interface CollapseContext {
    activeNames: Ref<NameType[]>;
    handleItemClick: (name: NameType) => void
}

//使用symbol创建独一无二的key
export const collapseContextKey: InjectionKey<CollapseContext> = Symbol('collapseContextKey')

CollapseItem.vue:

js 复制代码
<template>
    <div class="lu-collapse-item" :class="{
        'is-disabled': disabled
    }">
        <div class="lu-collapse-item__header" :id="`item-header-${name}`" @click="handleClick">
            <slot name="title">{{ title }}</slot>

        </div>
        <div class="lu-collapse-item__content" :id="`item-content-${name}`" v-show="isActive">
            <slot></slot>
        </div>
    </div>
</template>
<script setup lang="ts">
import type { CollapseItemProps } from './types'
import { inject, computed } from 'vue'
import { collapseContextKey } from './types'
const props = defineProps<CollapseItemProps>()
defineOptions({
    name: 'LuCollapseItem'
})
const collapseContext = inject(collapseContextKey)
const isActive = computed(() => collapseContext?.activeNames.value.includes(props.name))
const handleClick = () => {
    if (props.disabled) { return }
    collapseContext?.handleItemClick(props.name)
}
</script>
<style>
.lu-collapse-item__header {
    font-size: 30px;
}
</style>

Collapse.vue:

js 复制代码
<template>
    <div class="lu-collapse">
        <slot></slot>
    </div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue' //创建响应式数组的时候会用到
import type { NameType } from './types'
import { collapseContextKey } from './types'
defineOptions({
    name: 'LuCollapse'
})

//这里使用provide和inject传到子组件中,因为目标位置是在是在子组件中的slot中,
//所以无法使用props
const activeNames = ref<NameType[]>([])
const handleItemClick = (item: NameType) => {
    const index = activeNames.value.indexOf(item)
    if (index > -1) {
        activeNames.value.splice(index, 1)
    }
    else {
        activeNames.value.push(item)
    }
}
provide(collapseContextKey, {
    activeNames,
    handleItemClick
})
</script>

三.实现v-model

官网的v-model: 组件 v-model | Vue.js 做两件事,添加属性,添加对应的事件 types.ts中先定义属性和事件

js 复制代码
export interface CollapseProps {
    modelValue: NameType[];
    according?: boolean;
}
export interface CollapseEmits {
    (e: 'update:modelValue', values: NameType[]): void;
    (e: 'change', values: NameType[]): void;
}

Collapse.vue中使用:

js 复制代码
<template>
    <div class="lu-collapse">
        <slot></slot>
    </div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue' //创建响应式数组的时候会用到
import type { NameType, CollapseProps, CollapseEmits } from './types'
import { collapseContextKey } from './types'
defineOptions({
    name: 'LuCollapse'
})

//v-model相关:使用在types中定义好的属性和事件
const props = defineProps<CollapseProps>()
const emits = defineEmits<CollapseEmits>()


//这里使用provide和inject传到子组件中,因为目标位置是在是在子组件中的slot中,
//所以无法使用props
const activeNames = ref<NameType[]>(props.modelValue)  //传递modelValue给子组件
const handleItemClick = (item: NameType) => {
    const index = activeNames.value.indexOf(item)
    if (index > -1) {
        activeNames.value.splice(index, 1)
    }
    else {
        activeNames.value.push(item)
    }
    emits('update:modelValue', activeNames.value)
    emits('change', activeNames.value)
}
provide(collapseContextKey, {
    activeNames,
    handleItemClick
})
</script>

App.vue中:

js 复制代码
const openValue = ref(['a'])
...省略中间
 <Collapse v-model="openValue">
      <CollapseItem name="a">
        <template #title>
          <div>a title</div>
        </template>
        <div> this is content a aaa </div>
      </CollapseItem>
      <CollapseItem name="b" title="nice title b item b">
        <div> this is bbbbb test </div>
      </CollapseItem>
      <CollapseItem name="c" title="nice cccc">
        <div> this is cccc test </div>
      </CollapseItem>
    </Collapse>

为什么 openValue 的值可以传递给 modelValue

在 Vue 3 中,v-model 本质上是一种语法糖,用于创建一个双向绑定。在自定义组件中,v-model 通常通过 propsemit 事件来实现。在你的 Collapse 组件中,你已经定义了 modelValue 作为 props 并设置了 update:modelValue 事件,这是实现 v-model 的标准方式。

  1. Props 传递 : 当使用 v-model 时,Vue 会自动将绑定的值(在你的示例中是 openValue)传递给组件的 modelValue prop。这是 Vue 3 中 v-model 的默认行为,它简化了父子组件之间的数据传递。
  2. 事件触发 : 在 CollapseItem 组件中,当用户点击某个折叠项时,会触发 handleItemClick 方法。这个方法会更新 activeNames(一个响应式引用),然后通过 emit 触发 update:modelValue 事件,并将新的 activeNames 作为参数传递。这样,父组件就可以接收到这个事件,并更新其绑定的 openValue

四.设置样式

js 复制代码
.lu-collapse {
    --lu-collapse-border-color: var(--lu-border-color-light);
    --lu-collapse-header-height: 48px;
    --lu-collapse-header-bg-color: var(--lu-fill-color-blank);
    --lu-collapse-header-text-color: var(--lu-text-color-primary);
    --lu-collapse-header-font-size: 13px;
    --lu-collapse-content-bg-color: var(--lu-fill-color-blank);
    --lu-collapse-content-font-size: 13px;
    --lu-collapse-content-text-color: var(--lu-text-color-primary);
    --lu-collapse-disabled-text-color: var(--lu-disabled-text-color);
    --lu-collapse-disabled-border-color: var(--lu-border-color-lighter);
    border-top: 1px solid var(--lu-collapse-border-color);
    border-bottom: 1px solid var(--lu-collapse-border-color);
}

.lu-collapse-item__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--lu-collapse-header-height);
    line-height: var(--lu-collapse-header-height);
    background-color: var(--lu-collapse-header-bg-color);
    color: var(--lu-collapse-header-text-color);
    cursor: pointer;
    font-size: var(--lu-collapse-header-font-size);
    font-weight: 500;
    transition: border-bottom-color var(--lu-transition-duration);
    outline: none;
    border-bottom: 1px solid var(--lu-collapse-border-color);

    .is-disabled {
        color: var(--lu-collapse-disabled-text-color);
        cursor: not-allowed;
        background-image: none;
    }

    .is-active {
        border-bottom-color: transparent;
    }
}

.lu-collapse-item__content {
    will-change: height;
    background-color: var(--lu-collapse-content-bg-color);
    overflow: hidden;
    box-sizing: border-box;
    font-size: var(--lu-collapse-content-font-size);
    color: var(--lu-collapse-content-text-color);
    border-bottom: 1px solid var(--lu-collapse-border-color);
    padding-bottom: 25px;
}

给item展示的时候添加一些动画:使用transition

js 复制代码
<Transition name="fade">
            <div class="lu-collapse-item__content" :id="`item-content-${name}`" v-show="isActive">
                <slot></slot>
            </div>
        </Transition>
js 复制代码
.fade-enter-from,
.fade-leave-to {
    opacity: 0;
}

.fade-enter-active,
.fade-leave-active {
    transition: opacity 1s ease-in-out;
}

.fade-enter-to,
.fade-leave-from {
    opacity: 1;
}

实现滑动的效果

相关推荐
好_快16 分钟前
Lodash源码阅读-pull
前端·javascript·源码阅读
好_快17 分钟前
Lodash源码阅读-baseRest
前端·javascript·源码阅读
好_快18 分钟前
Lodash源码阅读-overRest
前端·javascript·源码阅读
BillKu1 小时前
Vue3 + TypeScript,使用祖先传后代模式重构父传子模式
前端·javascript·重构
恋猫de小郭1 小时前
AI 傻傻分不清楚?那么多 AI 变体究竟怎么选?这里快速简单理清!
android·前端·ai编程
魔云连洲6 小时前
详细解释浏览器是如何渲染页面的?
前端·css·浏览器渲染
Kx…………6 小时前
Day2—3:前端项目uniapp壁纸实战
前端·css·学习·uni-app·html
培根芝士8 小时前
Electron打包支持多语言
前端·javascript·electron
mr_cmx8 小时前
Nodejs数据库单一连接模式和连接池模式的概述及写法
前端·数据库·node.js
东部欧安时9 小时前
研一自救指南 - 07. CSS面向面试学习
前端·css