본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

Accordion 아코디언 — 페이지 내비게이션을 우아하게 정리하는 컴포넌트

원문 보기 위키 갱신

vue-devui의 d-accordion은 메뉴를 그룹으로 묶어 펼치고 접을 수 있는 아코디언형 내비게이션 컴포넌트예요. 데이터만 바꿔 끼우면 바로 쓸 수 있어서 왼쪽 메뉴 만들 때 특히 편해요.

출처: 문서

본문

페이지에 내비게이션을 제공하는 컴포넌트예요.

언제 사용하나요?

메뉴를 그룹으로 묶어서 정리하고 싶을 때 사용해요.

기본 사용법

메뉴 데이터를 넘겨 주고, 하위 항목을 펼칠 수 있는 메뉴의 열림/닫힘 이벤트(menuToggle)나 클릭 가능한 메뉴의 클릭 이벤트(itemClick)를 받아 보세요. 펼칠 수 있는 메뉴는 open 속성으로 기본 펼침 상태를, 클릭 가능한 메뉴는 active 속성으로 기본 활성 상태를, 비활성 항목은 disabled로 설정해요. restrictOneOpen으로 1단계 메뉴를 하나만 펼칠지 제한할 수도 있어요.

:::demo

<template>
  <div class="basic-menu">
    <d-accordion
      :data="menu"
      :accordionType="accordionTypeEmbed ? 'embed' : 'normal'"
      :restrictOneOpen="restrictOneOpen"
      @itemClick="itemClick"
      @menuToggle="menuToggle"
      :loadingTemplate="false"
    >
      <template #loadingTemplate="{ item, deepth }">
        <li class="devui-accordion-item">
          <div class="devui-accordion-item-title devui-over-flow-ellipsis" style="{{ textIndent: deepth * 20 + 'px' }}">自定义加载...</div>
        </li>
      </template>
    </d-accordion>
  </div>

  <div class="basic-option">
    <d-switch v-model="restrictOneOpen"></d-switch>
    Only one level-1 menu can be expanded.
    <!--限制只能展开一个一级菜单-->
  </div>
  <div class="basic-option">
    <d-switch v-model="accordionTypeEmbed"></d-switch>
    Embedded menu (no shadow)
    <!--内嵌菜单形式(无阴影)-->
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'accordion',
  setup() {
    const restrictOneOpen = ref(false);
    const accordionTypeEmbed = ref(false);
    const menu = ref([
      {
        title: 'Content 1',
        children: [{ title: 'Child Content 1' }, { title: 'Child Content 2' }, { title: 'Child Content ' }],
      },
      {
        title: 'Content 2(This is a long sentence for option display.)',
        children: [
          { title: 'Child Content 1 (This is a long sentence for option display.)' },
          { title: 'Child Content 2' },
          { title: 'Child Content 3' },
        ],
      },
      {
        title: 'Content 3 (Default Open)',
        open: true,
        children: [
          { title: 'Child Content 1 (Disabled)', disabled: true },
          { title: 'Child Content 2 (Default Active)', active: true },
          { title: 'Child Content 3' },
        ],
      },
      {
        title: 'Content 4 (No Child)',
        children: [],
      },
      {
        title: 'Content 5 (Disabled)',
        disabled: true,
        children: [],
      },
      {
        title: 'Content 6 (Dynamic Content)',
        needLoadChildren: true,
        loading: false,
        children: [],
      },
    ]);

    const itemClick = (event) => {
      console.log('item click' + JSON.stringify(event));
    };
    const menuToggle = (event) => {
      console.log('menu toggle' + JSON.stringify(event));
      if (event.open && event.item.needLoadChildren) {
        event.item.loading = true;
        setTimeout(() => {
          event.item.children = [{ title: 'Child Content 1' }, { title: 'Child Content 2' }];
          event.item.needLoadChildren = false;
          event.item.loading = false;
        }, 2000);
      }
    };

    return {
      menu,
      restrictOneOpen,
      accordionTypeEmbed,
      itemClick,
      menuToggle,
    };
  },
});
</script>
<style scoped lang="scss">
.basic-menu {
  width: 200px;
}

.basic-option {
  line-height: 20px;
  vertical-align: middle;
  margin-top: 20px;
}

.basic-option > d-toggle {
  display: inline-block;
  transform: translateY(3px);
}

@media (max-width: 250px) {
  .basic-menu {
    width: 80%;
  }
}
</style>

:::

내장 라우트와 링크 타입 사용하기

linkType을 설정하면 여러 내장 라우트/링크 타입을 바꿔가며 쓸 수 있어요: 기본 타입 ''; 라우트 타입 'routerLink'; 외부 링크 타입 'hrefLink'; 데이터에 따라 라우트나 링크 타입을 판단하는 'dependOnLinkTypeKey'.

:::demo

<template>
  <div class="menu-container">
    <div class="menu-wrapper">
      <div>
        router link (recommended)
        <!--路由链接(推荐使用)-->
      </div>
      <d-accordion :data="menu1" class="menu" linkType="routerLink"></d-accordion>
    </div>
    <div class="menu-wrapper">
      <div>
        original hyperlink (cross site scene)
        <!--原生超链接(涉及跨站场景使用)-->
      </div>
      <d-accordion :data="menu2" class="menu" linkType="hrefLink"></d-accordion>
    </div>
    <div class="menu-wrapper">
      <div>
        mix link (partial cross site scene)
        <!--混合链接(部分涉及跨站场景使用)-->
      </div>
      <d-accordion :data="menu3" class="menu" linkType="dependOnLinkTypeKey"></d-accordion>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'accordion',
  setup() {
    const menu1 = ref([
      {
        title: 'Basic',
        children: [
          { title: 'Accordion', link: '/components/accordion/' },
          { title: 'Anchor', link: '/components/anchor/' },
          { title: 'Button', link: '/components/button/' },
        ],
      },
      {
        title: 'Advanced',
        children: [
          { title: 'Data Table (disabled)', disabled: true, link: '/components/datatable/' },
          {
            title: 'Drag Drop (parameter example)',
            link: '/components/dragdrop/?query=foo#bar',
          },
        ],
      },
      {
        title: 'Others',
        children: [
          { title: 'Icon Library', link: '/components/icon/' },
          { title: 'Home Page', disabled: true, link: '/' },
        ],
      },
    ]);

    const menu2 = ref([
      {
        title: 'Basic',
        children: [
          { title: 'Alert', link: '/components/alert/', target: '_self' },
          { title: 'Anchor', link: '/components/anchor/', target: '_self' },
          { title: 'Accordion', link: '/components/accordion/', target: '_self' },
        ],
      },
      {
        title: 'Advanced',
        children: [
          {
            title: 'Data Table (disabled)',
            disabled: true,
            link: '/components/datatable/',
            target: '_self',
          },
          { title: 'Drag Drop', link: '/components/dragdrop/', target: '_self' },
        ],
      },
      {
        title: 'Others',
        children: [
          { title: 'Icon Library', link: '/components/icon/', target: '_self' },
          { title: 'Home Page(open blank page)', link: '/', target: '_blank' },
        ],
      },
    ]);

    const menu3 = ref([
      {
        title: 'Basic',
        children: [
          { title: 'Alert', link: '/components/alert/', linkType: 'routerLink' },
          { title: 'Anchor', link: '/components/anchor/', linkType: 'routerLink' },
          { title: 'Button', link: '/components/button/', linkType: 'routerLink' },
        ],
      },
      {
        title: 'Advanced',
        children: [
          {
            title: 'Data Table (disabled)',
            disabled: true,
            link: '/components/datatable/',
            linkType: 'routerLink',
          },
          { title: 'Drag Drop', link: '/components/dragdrop/', linkType: 'routerLink' },
        ],
      },
      {
        title: 'Others',
        children: [
          { title: 'Icon Library', link: '/components/icon/', linkType: 'routerLink' },
          { title: 'Home Page(External link open in this window)', link: '/', linkType: 'hrefLink' },
        ],
      },
    ]);

    return {
      menu1,
      menu2,
      menu3,
    };
  },
});
</script>
<style scoped>
.menu {
  width: 200px;
}
.menu-container {
  overflow: auto;
  padding-left: 2px;
}
.menu-wrapper {
  float: left;
}

.menu-wrapper + .menu-wrapper {
  margin-left: 20px;
}

:host::after {
  content: '';
  display: block;
  clear: both;
}

@media (max-width: 800px), (min-width: 1024px) and (max-width: 1100px), (min-width: 1280px) and (max-width: 1480px) {
  .menu-wrapper {
    float: none;
  }

  .menu-wrapper + .menu-wrapper {
    margin-top: 20px;
    margin-left: 0;
  }
}

@media (max-width: 250px) {
  .menu {
    width: 80%;
  }
}

.menu-wrapper > div {
  margin-bottom: 8px;
}
</style>

:::

템플릿 사용하기

펼칠 수 있는 메뉴와 클릭 가능한 메뉴에 각각 다른 템플릿을 쓸 수 있어요. 펼칠 수 있는 메뉴는 menuItemTemplate을, 클릭 가능한 메뉴는 itemTemplate을 지정해요. 데이터가 없을 때는 noContentTemplate을 지정하고, showNoContent로 데이터가 없을 때 펼치지 않도록 조절할 수 있어요. 로딩 중 템플릿은 loadingTemplate으로 지정하고, item의 loadingKey에 해당하는 속성 값으로 로딩 중 표시 여부를 조절해요.

:::demo

<template>
  <div class="use-template-menu">
    <d-accordion
      :data="menu"
      :accordionType="accordionTypeEmbed ? 'embed' : 'normal'"
      :restrictOneOpen="restrictOneOpen"
      @itemClick="itemClick"
      @menuToggle="menuToggle"
    >
      <template #menuItemTemplate="{ item, deepth, parent }">
        {{ item.title }}
        <span class="use-template-badge" v-if="item.children?.length">
          {{ item.children.length }}
        </span>
        <span class="operation-clear" v-if="item.needLoadChildren === false" @click="clearChildrenData($event, item)">
          reset
          <!--重置-->
        </span>
      </template>
      <template #itemTemplate="{ item, deepth, parent }">
        {{ item.title }}
        <span class="use-template-info">(Click Count: {{ item.clicktimes || '0' }})</span>
      </template>
      <template #noContentTemplate="{ deepth }">
        <li class="devui-accordion-item disabled">
          <div class="devui-accordion-item-title devui-over-flow-ellipsis disabled" style="{{ textIndent: deepth * 20 + 'px' }}">
            Not available yet. Please wait.
            <!--尚未开放,敬请期待...-->
          </div>
        </li>
      </template>
      <template #loadingTemplate="{ item }">
        <li class="devui-accordion-item">
          <div class="use-template-loading">
            <span class="use-template-circle-spinner"></span>
            loading...
            <!--正在用力地加载中...-->
          </div>
        </li>
      </template>
    </d-accordion>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'accordion',
  setup() {
    const restrictOneOpen = ref(false);
    const accordionTypeEmbed = ref(false);
    const menu = ref([
      {
        title: 'Content 1',
        open: true,
        children: [{ title: 'Child Content 1' }, { title: 'Child Content 2' }],
      },
      {
        title: 'Content 2',
        children: [{ title: 'Child Content 1' }, { title: 'Child Content 2' }, { title: 'Child Content 3' }, { title: 'Child Content 4' }],
      },
      {
        title: 'Content 3',
        children: [{ title: 'Child Content 1 (disabled)', disabled: true }, { title: 'Child Content 2' }, { title: 'Child Content 3' }],
      },
      {
        title: 'Content 4 (Custom No Data Template)',
        children: [], // 可展开菜单节点必须有children非undefined
      },
      {
        title: 'Content 5 (Custom loading Template)',
        needLoadChildren: true,
        loading: false,
        children: [],
      },
    ]);
    const childrenData = ref([
      { title: 'Child Content 1' },
      { title: 'Child Content 2' },
      { title: 'Child Content 3' },
      { title: 'Child Content 4' },
      { title: 'Child Content 5' },
      { title: 'Child Content 6' },
      { title: 'Child Content 7' },
    ]);

    const itemClick = (event) => {
      event.item.clicktimes = (event.item.clicktimes || 0) + 1;
    };

    const menuToggle = (event) => {
      if (event.open && event.item.needLoadChildren) {
        event.item.loading = true;
        setTimeout(() => {
          event.item.children = childrenData.value;
          event.item.needLoadChildren = false;
          event.item.loading = false;
        }, 1000);
      }
    };

    const clearChildrenData = (event, item) => {
      event.stopPropagation();
      item.children = [];
      item.needLoadChildren = true;
      item.open = false;
    };

    return {
      menu,
      restrictOneOpen,
      accordionTypeEmbed,
      itemClick,
      menuToggle,
      clearChildrenData,
    };
  },
});
</script>
<style scoped>
.use-template-menu {
  width: 400px;
}

.use-template-badge {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 8px;
  font-size: 12px;
  line-height: 16px;
  color: #ffffff;
  text-align: center;
  background: #5e7ce0;
  font-weight: normal;
}

.use-template-loading {
  position: relative;
  line-height: 40px;
  padding-left: 64px;
}

.use-template-circle-spinner {
  top: 10px;
  left: 36px;
  position: absolute;
  display: block;
  width: 20px;
  height: 20px;
  transform-origin: center;
  animation: circle infinite 0.75s linear;
  border: 2px solid #5e7ce0;
  border-top-color: transparent;
  border-radius: 50%;
}

@keyframes circle {
  0% {
    transform: rotate(0);
  }

  100% {
    transform: rotate(360deg);
  }
}

@media (max-width: 500px) {
  .use-template-menu {
    width: 80%;
  }
}
</style>

:::

내부 리스트는 innerListTemplate을 지정해서 템플릿을 사용해요.

:::demo

<template>
  <div class="inner-template-menu">
    <d-accordion :data="menu" :accordionType="accordionTypeEmbed ? 'embed' : 'normal'" :restrictOneOpen="restrictOneOpen">
      <template #innerListTemplate="{ item, deepth }">
        <div class="devui-accordion-submenu devui-accordion-show-animate inner-template-my-menu">
          {{ item.content }}
        </div>
      </template>
    </d-accordion>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'accordion',
  setup() {
    const restrictOneOpen = ref(false);
    const accordionTypeEmbed = ref(false);
    const menu = ref([
      {
        title: 'Content 1',
        children: [],
        content: 'Child Content of Content 1',
      },
      {
        title: 'Content 2',
        children: [],
        content: 'Child Content of Content 2',
      },
    ]);

    return {
      menu,
      restrictOneOpen,
      accordionTypeEmbed,
    };
  },
});
</script>
<style scoped>
.inner-template-menu {
  width: 400px;
}
.inner-template-my-menu {
  min-height: 60px;
  line-height: 40px;
  padding: 12px;
}

@media (max-width: 500px) {
  .inner-template-menu {
    width: 80%;
  }
}
</style>

:::

복합 계층과 자동 펼침

여러 계층을 지원하고 중첩 계층 수에 제한이 없어요. autoOpenActiveMenu를 단독으로 사용하면 활성화된 메뉴의 부모 계층을 자동으로 펼쳐 줘요.

:::demo

<template>
  <div class="multi-child-menu">
    <d-accordion :data="menu" :autoOpenActiveMenu="autoOpenActiveMenu"></d-accordion>
  </div>

  <div class="multi-child-option">
    <d-switch v-model="autoOpenActiveMenu"></d-switch>
    auto expend active menu
    <!--활성화된 메뉴 자동 펼치기-->
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'accordion',
  setup() {
    const autoOpenActiveMenu = ref(false);
    const menu = ref([
      {
        title: 'Content 1 (as a leaf menu)',
      },
      {
        title: 'Content 2 (as a parent menu, has children)',
        children: [{ title: 'Child Content 1' }, { title: 'Child Content 2' }, { title: 'Child Content 3' }],
      },
      {
        title: 'Content 3 (as a parent menu, has children)',

        children: [
          {
            title: 'Child Content 1 (has children)',
            children: [{ title: 'Child Content 1' }, { title: 'Child Content 2', active: true }, { title: 'Child Content 3' }],
          },
          {
            title: 'Child Content 2 (has children',
            children: [
              { title: 'Child Content 1' },
              {
                title: 'Child Content 2 (has children',
                children: [{ title: 'Child Content 1' }, { title: 'Child Content 2' }, { title: 'Child Content 3' }],
              },
              { title: 'Child Content 3' },
            ],
          },
          { title: 'Child Content 2' },
          { title: 'Child Content 3' },
        ],
      },
      {
        title: 'Content 4 (as a parent menu, has no child)',
        children: [],
      },
    ]);

    return {
      menu,
      autoOpenActiveMenu,
    };
  },
});
</script>
<style scoped>
.multi-child-menu {
  width: 400px;
}

.multi-child-option {
  line-height: 20px;
  vertical-align: middle;
  margin-top: 20px;
}

.multi-child-option > d-toggle {
  display: inline-block;
  transform: translateY(3px);
}

@media (max-width: 500px) {
  .multi-child-menu {
    width: 80%;
  }
}
</style>

:::

키 값 바꾸기

titleKey, childrenKey, disabledKey, activeKey 등으로 배열의 키 값을 바꿔서 서로 다른 인터페이스 타입 정의에 맞출 수 있어요.

:::demo

<template>
  <div class="change-key-menu">
    <d-accordion
      :data="menu"
      :titleKey="key?.titleKey"
      :activeKey="key?.activeKey"
      :disabledKey="key?.disabledKey"
      :openKey="key?.openKey"
      :childrenKey="key?.childrenKey"
    ></d-accordion>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from 'vue';

export default defineComponent({
  name: 'accordion',
  setup() {
    const key = reactive({
      titleKey: 'value',
      activeKey: '$selected',
      disabledKey: 'forbidden',
      openKey: '$show',
      childrenKey: 'subs',
    });
    const menu = ref([
      {
        value: 'Content 1',
      },
      {
        value: 'Content 2 (expended)',
        $show: true,
        subs: [
          { value: 'Child Content 1 (disabled)', forbidden: true },
          { value: 'Child Content 2 (active)', $selected: true },
          { value: 'Child Content 3' },
        ],
      },
    ]);

    return {
      menu,
      key,
    };
  },
});
</script>
<style scoped>
.change-key-menu {
  width: 400px;
}

@media (max-width: 500px) {
  .change-key-menu {
    width: 80%;
  }
}
</style>

:::

Accordion 파라미터

파라미터 타입 기본값 설명 Demo 링크
data Array<any> | AccordionMenuType -- 필수, 데이터 소스, 직접 만든 배열이나 미리 정의된 AccordionMenuType을 사용할 수 있어요 기본 사용법
titleKey string 'title' 선택, 제목의 속성 이름, item[titleKey] 타입은 string, 제목 표시 내용이에요 키 값 바꾸기
loadingKey string 'loading' 선택, 하위 메뉴가 로딩 중인지 판단하는 속성 이름, item[loadingKey] 타입은 boolean 키 값 바꾸기
childrenKey string 'children' 선택, 하위 메뉴의 속성 이름, item[childrenKey] 타입은 Array<any> 키 값 바꾸기
disabledKey string 'disabled' 선택, 비활성 여부의 속성 이름, item[disabledKey] 타입은 boolean 키 값 바꾸기
activeKey string 'active' 선택, 하위 메뉴가 활성화됐는지의 속성 이름, item[activeKey] 타입은 boolean 키 값 바꾸기
openKey string 'open' 선택, 메뉴가 펼쳐졌는지의 속성 이름, item[openKey] 타입은 boolean 키 값 바꾸기
restrictOneOpen boolean false 선택, 1단계 메뉴를 동시에 하나만 열도록 제한, 기본은 제한 없음 기본 사용법
menuItemTemplate boolean true 선택, 펼칠 수 있는 메뉴 내용 항목 템플릿, 쓸 수 있는 변수는 아래 참고 템플릿 사용하기
itemTemplate boolean true 선택, 클릭 가능한 메뉴 내용 항목 템플릿, 쓸 수 있는 변수는 아래 참고 템플릿 사용하기
noContentTemplate boolean true 선택, 내용이 없을 때 사용자 지정 템플릿 사용, 쓸 수 있는 변수는 아래 참고 템플릿 사용하기
loadingTemplate boolean true 선택, 로딩 중 사용자 지정 템플릿, 쓸 수 있는 변수는 아래 참고 템플릿 사용하기
innerListTemplate boolean true 선택, 하위 리스트 내용을 완전히 직접 꾸미기, 접이식 패널로 활용, 쓸 수 있는 변수는 아래 참고 템플릿 사용하기
linkType 'routerLink'|'hrefLink'|'dependOnLinkTypeKey'|'' '' 선택, 'routerLink'는 라우트 시나리오; 'hrefLink'는 외부 링크 시나리오; 'dependOnLinkTypeKey'는 동적 라우트 또는 외부 링크 시나리오; ''는 기본 비링크 타입 (우클릭으로 새 탭 열기 불가) 내장 라우트와 링크 타입 사용하기
linkTypeKey string 'linkType' 선택, 링크 내용 타입의 key 값, linkType이 'dependOnLinkTypeKey'일 때 객체의 링크 타입 속성 이름을 지정해요. item[linkTypeKey] 타입은 'routerLink'|'hrefLink'| string이고, 'routerLink'는 라우트 링크, 'hrefLink'는 외부 링크, 그 외는 기본 비링크 타입이에요
linkKey string 'link' 선택, 링크 내용의 key, linkType이 ''가 아닌 링크 타입일 때 링크 값을 가져오는 속성 값, item[linkKey]는 라우트 주소나 하이퍼링크 주소예요
linkTargetKey string 'target' 선택, 링크 대상 창의 key, 링크 타입에서 사용, item[linkTargetKey]는 개별 링크의 대상 창이에요
linkDefaultTarget string '_self' 선택, target을 설정하지 않았을 때의 target 기본값은 '_self', 링크 타입에서 사용, a 링크의 target 속성과 같은 값을 가져요 내장 라우트와 링크 타입 사용하기
autoOpenActiveMenu boolean false 선택, 활성화된 하위 항목이 있는 메뉴를 자동으로 펼칠지 여부 복합 계층과 자동 펼침
accordionType 'normal'|'embed' 'normal' 선택, 메뉴 형태가 일반(그림자 있음)인지 내장(그림자 없음)인지 기본 사용법
showAnimation boolean true 선택, 애니메이션을 표시할지 여부 내장 라우트와 링크 타입 사용하기

Accordion 이벤트

이벤트명 타입 설명 Demo 링크
menuToggle EventEmitter<AccordionMenuToggleEvent> 선택, 펼칠 수 있는 메뉴의 펼침 이벤트, 반환 객체의 item 속성은 클릭한 객체 데이터, open이 true면 펼칠 예정 false면 닫을 예정, parent는 부모 객체 데이터, event는 클릭 이벤트의 원본 이벤트 기본 사용법
itemClick EventEmitter<AccordionItemClickEvent> 선택, 클릭 가능한 메뉴의 클릭 이벤트, 반환 객체의 item 속성은 클릭한 객체 데이터, preActive 객체는 이전에 펼쳐졌던 객체, parent는 부모 객체 데이터, event는 클릭 이벤트의 원본 이벤트 기본 사용법
activeItemChange EventEmitter<any> 선택, 하위 항목이 바뀔 때 새로 활성화된 하위 항목의 데이터를 발행해요 기본 사용법

Accordion 타입 정의

/* 基础数据类型 */
type AccordionMenuItemLinkType = 'routerLink' | 'hrefLink' | string;
export interface AccordionBase {
  title: string;
  disabled?: boolean;
  [prop: string]: any;
}
interface IAccordionActiveable {
  active?: boolean;
}
interface IAccordionFoldable<T> {
  open?: boolean;
  loading?: boolean;
  children?: Array<T>;
}

interface IAccordionLinkable {
  link?: string;
  target?: boolean;
  linkType?: AccordionMenuItemLinkType;
}
export interface AccordionBaseItem extends AccordionBase, IAccordionActiveable {}
export interface AccordionBaseMenu<T> extends AccordionBase, IAccordionFoldable<T> {}

export interface AccordionLinkableItem extends AccordionBase, IAccordionActiveable, IAccordionLinkable {}
export interface AccordionMenuItem extends AccordionBase, IAccordionActiveable, IAccordionFoldable<AccordionMenuItem>, IAccordionLinkable {}

export type AccordionMenuType = Array<AccordionMenuItem>;

/* 基础事件类型 */
export interface AccordionMenuToggleEvent {
  item: any;
  open: boolean;
  parent: any;
  event: MouseEvent;
}
export interface AccordionItemClickEvent {
  item: any;
  prevActiveItem?: any;
  parent: any;
  event: MouseEvent;
}

/* 废弃接口 */
/** @deprecated  merge into `AccordionMenuItem`*/
export interface AccordionSubMenuItem {
  title: string;
  active?: boolean;
  disabled?: boolean;
  [prop: string]: any;
}
/** @deprecated  use `AccordionLinkableItem` instead*/
export interface AccordionSubMenuItemHrefLink {
  title: string;
  link: string;
  target?: string;
  active?: boolean;
  disabled?: boolean;
  [prop: string]: any;
}
/** @deprecated  use `AccordionLinkableItem` instead*/
export interface AccordionSubMenuItemRouterLink {
  title: string;
  link: string;
  target?: string;
  active?: boolean;
  disabled?: boolean;
  [prop: string]: any;
}
/** @deprecated  use `AccordionLinkableItem` instead*/
export interface AccordionSubMenuItemDynamicLink {
  title: string;
  link: string;
  linkType: 'routerLink' | 'hrefLink' | string;
  target?: string;
  active?: boolean;
  disabled?: boolean;
  [prop: string]: any;
}

AccordionProps

import { ExtractPropTypes } from 'vue';
import { AccordionMenuType } from './accordion.type';

export const accordionProps = {
  data: {
    type: Array as () => Array<any> | AccordionMenuType,
    default: null,
  },
  /* Key值定义, 用于自定义数据结构 */
  titleKey: { type: String, default: 'title' }, // 标题的key,item[titleKey]类型为string,为标题显示内容
  loadingKey: { type: String, default: 'loading' }, // 子菜单动态加载item[loadingKey]类型为boolean
  childrenKey: { type: String, default: 'children' }, // 子菜单Key
  disabledKey: { type: String, default: 'disabled' }, // 是否禁用Key
  activeKey: { type: String, default: 'active' }, // 菜单是否激活/选中
  openKey: { type: String, default: 'open' }, // 菜单是否打开

  /* 菜单模板 */
  menuItemTemplate: { type: Boolean, default: true }, // 可展开菜单内容条模板
  itemTemplate: { type: Boolean, default: true }, // 可点击菜单内容条模板

  menuToggle: {
    type: Function as unknown as () => (event: MouseEvent) => void,
    default: null,
  }, // 可展开菜单展开事件
  itemClick: {
    type: Function as unknown as () => (event: MouseEvent) => void,
    default: null,
  }, // 可点击菜单点击事件
  activeItemChange: {
    type: Function as unknown as () => (event: MouseEvent) => void,
    default: null,
  },

  /** 高级选项和模板 */
  restrictOneOpen: { type: Boolean, default: false }, // 限制一级菜单同时只能打开一个
  autoOpenActiveMenu: { type: Boolean, default: false }, // 自动展开活跃菜单
  showNoContent: { type: Boolean, default: true }, // 没有内容的时候是否显示没有数据
  noContentTemplate: { type: Boolean, default: true }, // 没有内容的时候使用自定义模板
  loadingTemplate: { type: Boolean, default: true }, // 加载中使用自定义模板
  innerListTemplate: { type: Boolean, default: true }, // 可折叠菜单内容完全自定义,用做折叠面板

  /* 内置路由/链接/动态判断路由或链接类型 */
  linkType: {
    type: String as () => 'routerLink' | 'hrefLink' | 'dependOnLinkTypeKey' | '' | string,
    default: '',
  },
  linkTypeKey: { type: String, default: 'linkType' }, // linkType为'dependOnLinkTypeKey'时指定对象linkType定义区
  linkKey: { type: String, default: 'link' }, // 链接内容的key
  linkTargetKey: { type: String, default: 'target' }, // 链接目标窗口的key
  linkDefaultTarget: { type: String, default: '_self' }, // 不设置target的时候target默认值

  accordionType: {
    type: String as () => 'normal' | 'embed',
    default: 'normal',
  },
} as const;

export type AccordionProps = ExtractPropTypes<typeof accordionProps>;

템플릿에서 쓸 수 있는 변수 값 (사용 방법은 아래와 같아요)

<template #templateName="{item}">{{item}}</template>
변수 타입 변수 의미 설명
item any 펼침 타입 메뉴 데이터
deepth number 중첩 구조 계층을 나타내요
parent any 상위 부모 메뉴 데이터
~~~titleKey~~~ string 이미 폐기됨~~~컴포넌트의 titleKey 값~~~
~~~disabledKey~~~ string 이미 폐기됨~~~컴포넌트의 disabledKey 값~~~
~~~openKey~~~ string 이미 폐기됨~~~컴포넌트의 openKey 값~~~
~~~menuToggleFn~~~ Function 이미 폐기됨~~~인자는 item이어야 하고, 1단계 메뉴가 클릭됐음을 나타내요~~~

itemTemplate에서 쓸 수 있는 변수 값

변수 타입 변수 의미 설명
item any 클릭 타입 메뉴 데이터
deepth number 값은 중첩 구조 계층을 나타내요
parent any 상위 부모 메뉴 데이터
~~~titleKey~~~ string 이미 폐기됨~~~ 컴포넌트의 titleKey 값~~~
~~~disabledKey~~~ string 이미 폐기됨~~~ 컴포넌트의 disabledKey 값~~~
~~~activeKey~~~ string 이미 폐기됨~~~ 컴포넌트의 activeKey 값~~~
~~~itemClickFn~~~ Function 이미 폐기됨~~~인자는 item이어야 하고, 2단계 메뉴가 클릭됐음을 나타내요~~~

noContentTemplate에서 쓸 수 있는 변수 값

변수 타입 변수 의미 설명
item any 부모 메뉴의 개별 데이터
deepth number 값은 중첩 구조 계층을 나타내요

loadingTemplate에서 쓸 수 있는 변수 값

변수 타입 변수 의미 설명
item any 부모 메뉴의 개별 데이터
deepth number 값은 중첩 구조 계층을 나타내요
~~~loadingKey~~~ string ~~~컴포넌트의 loadingKey 값~~~

innerListTemplate에서 쓸 수 있는 변수 값

변수 타입 변수 의미 설명
item any 부모 메뉴의 개별 데이터
deepth number 값은 중첩 구조 계층을 나타내요
~~~titleKey~~~ string 이미 폐기됨~~~컴포넌트의 titleKey 값~~~
~~~loadingKey~~~ string 이미 폐기됨~~~컴포넌트의 loadingKey 값~~~
~~~childrenKey~~~ string 이미 폐기됨~~~컴포넌트의 childrenKey 값~~~
~~~disabledKey~~~ string 이미 폐기됨~~~컴포넌트의 disabledKey 값~~~
~~~openKey~~~ string 이미 폐기됨~~~컴포넌트의 openKey 값~~~
~~~activeKey~~~ string 이미 폐기됨~~~컴포넌트의 activeKey 값, 2단계 메뉴에서 사용~~~
menuToggleFn Function 인자는 item이어야 하고, 메뉴가 펼쳐졌음을 나타내요. 선택 인자 event는 원본 이벤트예요
itemClickFn Function 인자는 클릭 가능한 메뉴의 item이어야 하고, 메뉴가 클릭됐음을 나타내요. 선택 인자 event는 원본 이벤트예요

더 알아보기 (Learn more)

d-accordion은 데이터 주도로 메뉴를 그리기 때문에 라우트 링크, 사용자 지정 템플릿, 키 값 변경 같은 확장 포인트가 넉넉한 편이에요. 실제 사이드바를 만들 때는 linkType과 각종 템플릿 슬롯을 조합해 보는 걸 추천해요.