自定义手风琴在 Vue 中一次切换一个项目

Custom Accordion Toggle one Item at a Time in Vue

我将 Vue 与 Nuxt 2 一起使用,但我的自定义手风琴组件出现问题。

我想一次切换一个手风琴。但实际发生的情况是,如果我单击第一个手风琴 Link,第一个 Link(accordion) 的正文将打开。在不关闭第一个的情况下,如果我在第二个link(手风琴)上打开,第二个手风琴体也会打开。我想避免。如果我想打开其他手风琴,打开的应该先关闭。那么我如何在不使用任何库的情况下以 vue 方式实现这一点。

手风琴切换过程: 我正在使用 ACTIVE 值通过在我的锚标签上添加 aria-expended = "true" 来切换手风琴。同时,我根据 ACTIVE 值显示或隐藏 accordion-item-body

我的组件手风琴:

<template>
  <div id="accordion" ref="accordion" class="accordion">
    <slot />
  </div>
</template>

<script lang="ts">
import Vue from "vue"
import { props } from "./props"

export default Vue.extend({
  name: "Accordion",
  props,
  created() {
    this.$nuxt.$off("eventName")
    this.$nuxt.$on("eventName", ($event: any) => this.toggleActive($event))
  },
  methods: {
    toggleActive(e: any) {
      console.log(e)
    },
  },
})
</script>

我的 AccordionItem 组件:

<template>
  <div class="accordion-item" role="tab">
    <div class="accordion-item-header">
      <a
        :aria-expanded="active"
        @click.prevent=";(active = !active), $nuxt.$emit('eventName', active)"
      >
        {{ title }}

        <span
          class="material-icons"
        >
          expand_more
        </span>
      </a>
    </div>
    <transition name="accordion">
      <div v-show="active" class="accordion-item-body" role="tabpanel">
        <slot />
      </div>
    </transition>
  </div>
</template>

<script lang="ts">
import Vue from "vue"
export default Vue.extend({
  name: "AccordionItem",
  props: {
    title: {
      type: String,
      required: true,
    },
    id: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      active: false,
    }
  },
})
</script>

父项中 Accordion 和 AccordionItem 的使用:

<Accordion :items="items">
  <AccordionItem
    v-for="item in items"
    :id="item.id"
    :key="item.id"
    :title="item.title"
  >
    <div v-html="item.description"></div>
  </AccordionItem>
</Accordion>

这是一个简单但有效的解决方案(我当然没有实现样式,但功能运行良好)。

对于包装器

<template>
  <div>
    <accordion
      v-for="(item, index) in list"
      :key="item.id"
      :item="item"
      :active-index="currentlyActiveIndex"
      :item-index="index"
      @update:itemIndex="currentlyActiveIndex = $event"
    ></accordion>
  </div>
</template>

<script>
export default {
  name: 'AccordionList',
  data() {
    return {
      list: [
        { id: 1, name: 'hello' },
        { id: 2, name: 'world' },
        { id: 3, name: 'swag' },
      ],
      currentlyActiveIndex: null,
    }
  },
}
</script>

Accordion.vue

<template>
  <div>
    <button @click="updateIndex">get this index: {{ itemIndex }}</button>
    <span v-show="activeIndex === itemIndex">{{ item.name }}</span>
  </div>
</template>

<script>
export default {
  name: 'Accordion',
  props: {
    item: {
      type: Object,
      default: () => {},
    },
    activeIndex: {
      type: Number,
      default: null,
    },
    itemIndex: {
      type: Number,
      default: null,
    },
  },
  methods: {
    updateIndex() {
      console.log('index updated:', this.itemIndex)
      this.$emit('update:itemIndex', this.itemIndex)
    },
  },
}
</script>

这也可以通过类似 { id: 1, name: 'hello', isActive: false } 的方法来实现,更简洁一些,但这两种方法在这里都有效。

PS:我也不会用TS,但是我相信你可以自己想出这部分(我猜不是这里的拦截器)。