Я новичок в Vue.js, делаю проект CRUD для своей курсовой работы.
У меня проблемы с поведением раскрывающегося меню. Может ли кто-нибудь мне помочь?
Я хочу :
Примечание:
активное состояние — это когда доступ пользователя: активный маршрут
isOpen — раскрывающееся меню значений открывается или закрывается (свернуть или не свернуть)
SidebarContent.vue, этот файл содержит все меню боковой панели.
SidebarCollapsibeItem.vue, ссылка на дескриптор этого файла для раскрывающегося меню.
SidebarCollapsible.vue — это контейнер для SidebarCollapsibleItem (создать раскрывающееся меню)
SidebarLink.vue похож на тег привязки в HTML для дескриптора меню без раскрывающегося списка.
Как я могу этого добиться?
Это мой код:
боковая панельCollapsible.vue
<script setup>
import { ref, watch } from 'vue';
import { sidebarState } from '@/Composables';
import SidebarLink from '@/Components/Sidebar/SidebarLink.vue';
import { EmptyCircleIcon } from '@/Components/Icons/Outline';
const props = defineProps({
title: {
type: String,
},
icon: {
required: false,
},
active: {
type: Boolean,
default: false,
},
});
const { active } = props;
const isOpen = ref(active);
const beforeEnter = (el) => {
el.style.maxHeight = `0px`;
};
const enter = (el) => {
el.style.maxHeight = `${el.scrollHeight}px`;
};
const beforeLeave = (el) => {
el.style.maxHeight = `${el.scrollHeight}px`;
};
const leave = (el) => {
el.style.maxHeight = `0px`;
};
watch(() => sidebarState.isHovered, (sideHover) => {
if (!sideHover && !sidebarState.isOpen && !active) {
isOpen.value = false;
};
});
</script>
<template>
<div class = "relative">
<SidebarLink @click = "isOpen = !isOpen" :title = "title" :active = "active">
<template #icon>
<slot name = "icon">
<EmptyCircleIcon aria-hidden = "true" class = "flex-shrink-0 w-6 h-6" />
</slot>
</template>
<template #arrow>
<span v-show = "sidebarState.isOpen || sidebarState.isHovered" aria-hidden = "true" class = "relative block w-6 h-6 ml-auto">
<span :class = "['absolute right-[9px] bg-gray-400 mt-[-5px] h-2 w-[2px] top-1/2 transition-all duration-200', {'-rotate-45': isOpen, 'rotate-45': !isOpen}]"></span>
<span :class = "['absolute left-[9px] bg-gray-400 mt-[-5px] h-2 w-[2px] top-1/2 transition-all duration-200', {'rotate-45': isOpen, '-rotate-45': !isOpen}]"></span>
</span>
</template>
</SidebarLink>
<transition
@before-enter = "beforeEnter"
@enter = "enter"
@before-leave = "beforeLeave"
@leave = "leave"
appear>
<div v-show = "isOpen && (sidebarState.isOpen || sidebarState.isHovered)" class = "overflow-hidden transition-all duration-200">
<ul class = "relative px-0 pt-2 pb-0 ml-5 before:w-0 before:block before:absolute before:inset-y-0 before:left-0 before:border-l-2 before:border-l-gray-200 dark:before:border-l-gray-600">
<slot />
</ul>
</div>
</transition>
</div>
</template>
SidebarCollapsibleItem.vue
<script setup>
import { defineProps } from 'vue';
import { Link } from '@inertiajs/vue3';
const props = defineProps({
href: String,
title: String,
active: {
type: Boolean,
default: false,
},
external: {
type: Boolean,
default: false,
},
});
const { external } = props;
const Tag = external ? 'a' : Link;
</script>
<template>
<li
:class = "[
'relative leading-8 m-0 pl-6',
'before:block before:w-4 before:h-0 before:absolute before:left-0 before:top-4 before:border-t-2 before:border-t-gray-200 before:-mt-0.5',
'last:before:bg-white last:before:h-auto last:before:top-4 last:before:bottom-0',
'dark:last:before:bg-dark-eval-1 dark:before:border-t-gray-600',
]"
>
<component
:is = "Tag"
:href = "href"
v-bind = "$attrs"
:class = "[
'transition-colors hover:text-gray-900 dark:hover:text-gray-100',
{
'text-purple-500 dark:text-purple-500 hover:text-purple-600 dark:hover:text-purple-600': active,
'text-gray-500 dark:text-gray-400': !active,
},
]"
>
{{ title }}
</component>
</li>
</template>
боковая панельContent.vue
<script setup>
import PerfrectScrollbar from '@/Components/PerfectScrollbar';
import SidebarLink from '@/Components/Sidebar/SidebarLink.vue';
import { DashboardIcon } from '@/Components/Icons/Outline';
import { UserGroupIcon, BuildingOffice2Icon } from '@heroicons/vue/24/outline';
import SidebarCollapsible from '@/Components/Sidebar/SidebarCollapsible.vue';
import SidebarCollapsibleItem from '@/Components/Sidebar/SidebarCollapsibleItem.vue';
</script>
<template>
<PerfrectScrollbar
tagname = "nav"
aria-label = "main"
class = "relative flex flex-col flex-1 max-h-full gap-4 px-3"
>
<SidebarLink
title = "Dashboard"
:href = "route('dashboard')"
:active = "route().current('dashboard')"
>
<template #icon>
<DashboardIcon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
</SidebarLink>
<SidebarCollapsible
title = "Management"
:active = "route().current('users.*') || route().current('teams.*') || route().current('divisions.*') || route().current('departments.*')"
>
<template #icon>
<UserGroupIcon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
<SidebarCollapsibleItem
title = "Users"
:href = "route('users.index')"
:active = "route().current('users.*')"
/>
<SidebarCollapsibleItem
title = "Roles"
:href = "route('teams.index')"
:active = "route().current('#')"
/>
<SidebarCollapsibleItem
:href = "route('teams.index')"
title = "Teams"
:active = "route().current('teams.*')"
/>
<SidebarCollapsibleItem
title = "Departments"
:href = "route('departments.index')"
:active = "route().current('departments.*')"
/>
<SidebarCollapsibleItem
title = "Divisions"
:href = "route('divisions.index')"
:active = "route().current('divisions.*')"
/>
</SidebarCollapsible>
<SidebarCollapsible
title = "Locations"
:active = "route().current('externals.*') || route().current('internals.*')"
>
<template #icon>
<BuildingOffice2Icon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
<SidebarCollapsibleItem
title = "Internals"
:href = "route('internals.index')"
:active = "route().current('internals.*')"
/>
<SidebarCollapsibleItem
title = "Externals"
:href = "route('externals.index')"
:active = "route().current('externals.*')"
/>
</SidebarCollapsible>
<SidebarCollapsible
title = "test"
:active = "route().current('api-tokens.*')"
>
<template #icon>
<BuildingOffice2Icon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
<SidebarCollapsibleItem
title = "Internals"
:href = "route('api-tokens.index')"
:active = "route().current('user.*')"
/>
<SidebarCollapsibleItem
title = "Externals"
:href = "route('api-tokens.index')"
:active = "route().current('api-tokens.*')"
/>
</SidebarCollapsible>
</PerfrectScrollbar>
</template>
Спасибо за любую помощь, извините за мой плохой английский.
ваш родительский компонент должен сохранять состояние, в котором складной объект открыт (используйте и индекс или какой-либо идентификатор), щелчок для открытия должен отправить событие обратно родительскому элементу, и родительский элемент решает, какой складной объект должен быть открыт. В исходном коде я не смог найти ничего, что сообщало бы боковой панели, какой элемент следует открыть/развернуть. например, при нажатии кнопки переключения: @click = "$emit('activeItem', activeItemIdentifier)"
Можете ли вы привести простой пример реализации этого??
документацию можно найти vuejs.org/guide/comComponents/events.html



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


спасибо за комментарий @danielricado
боковая панельCollapsible.vue
<script setup>
import { ref, watch } from 'vue';
import { sidebarState } from '@/Composables';
import SidebarLink from '@/Components/Sidebar/SidebarLink.vue';
import { EmptyCircleIcon } from '@/Components/Icons/Outline';
const props = defineProps({
id: {
type: String,
required: true
},
title: {
type: String,
},
icon: {
required: false,
},
active: {
type: Boolean,
default: false,
},
isOpen: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['toggle']);
const isOpen = ref(props.isOpen || props.active);
watch(() => props.isOpen, (openMenu) => {
if (!props.active) {
isOpen.value = openMenu;
}
});
watch(() => props.active, (activeMenu) => {
if (activeMenu) {
isOpen.value = true;
}
});
watch(() => sidebarState.isHovered, (sideHover) => {
if (!sideHover && !sidebarState.isOpen) {
if (!props.active) {
emit('toggle', null);
} else {
isOpen.value = true;
}
}
});
const beforeEnter = (el) => {
el.style.maxHeight = `0px`;
};
const enter = (el) => {
el.style.maxHeight = `${el.scrollHeight}px`;
};
const beforeLeave = (el) => {
el.style.maxHeight = `${el.scrollHeight}px`;
};
const leave = (el) => {
el.style.maxHeight = `0px`;
};
const toggle = () => {
isOpen.value = !isOpen.value;
if (!props.active) {
emit('toggle', props.id);
}
};
</script>
<template>
<div class = "relative">
<SidebarLink @click = "toggle" :title = "title" :active = "props.active">
<template #icon>
<slot name = "icon">
<EmptyCircleIcon aria-hidden = "true" class = "flex-shrink-0 w-6 h-6" />
</slot>
</template>
<template #arrow>
<span v-show = "sidebarState.isOpen || sidebarState.isHovered" aria-hidden = "true" class = "relative block w-6 h-6 ml-auto">
<span :class = "['absolute right-[9px] bg-gray-400 mt-[-5px] h-2 w-[2px] top-1/2 transition-all duration-200', {'-rotate-45': isOpen, 'rotate-45': !isOpen}]"></span>
<span :class = "['absolute left-[9px] bg-gray-400 mt-[-5px] h-2 w-[2px] top-1/2 transition-all duration-200', {'rotate-45': isOpen, '-rotate-45': !isOpen}]"></span>
</span>
</template>
</SidebarLink>
<transition
@before-enter = "beforeEnter"
@enter = "enter"
@before-leave = "beforeLeave"
@leave = "leave"
appear>
<div v-show = "isOpen && (sidebarState.isOpen || sidebarState.isHovered)" class = "overflow-hidden transition-all duration-200">
<ul class = "relative px-0 pt-2 pb-0 ml-5 before:w-0 before:block before:absolute before:inset-y-0 before:left-0 before:border-l-2 before:border-l-gray-200 dark:before:border-l-gray-600">
<slot />
</ul>
</div>
</transition>
</div>
</template>
боковая панельContent.vue
<script setup>
import { ref } from 'vue';
import PerfectScrollbar from '@/Components/PerfectScrollbar';
import SidebarLink from '@/Components/Sidebar/SidebarLink.vue';
import { DashboardIcon } from '@/Components/Icons/Outline';
import { UserGroupIcon, BuildingOffice2Icon } from '@heroicons/vue/24/outline';
import SidebarCollapsible from '@/Components/Sidebar/SidebarCollapsible.vue';
import SidebarCollapsibleItem from '@/Components/Sidebar/SidebarCollapsibleItem.vue';
const openCollapsibleId = ref(null);
const toggleCollapsible = (id) => {
openCollapsibleId.value = openCollapsibleId.value === id ? null : id;
};
</script>
<template>
<PerfectScrollbar
tagname = "nav"
aria-label = "main"
class = "relative flex flex-col flex-1 max-h-full gap-4 px-3"
>
<SidebarLink
title = "Dashboard"
:href = "route('dashboard')"
:active = "route().current('dashboard')"
>
<template #icon>
<DashboardIcon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
</SidebarLink>
<SidebarCollapsible
id = "management"
title = "Management"
:active = "route().current('users.*') || route().current('teams.*') || route().current('divisions.*') || route().current('departments.*')"
:is-open = "openCollapsibleId === 'management'"
@toggle = "toggleCollapsible"
>
<template #icon>
<UserGroupIcon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
<SidebarCollapsibleItem
title = "Users"
:href = "route('users.index')"
:active = "route().current('users.*')"
/>
<SidebarCollapsibleItem
title = "Roles"
:href = "route('teams.index')"
:active = "route().current('#')"
/>
<SidebarCollapsibleItem
:href = "route('teams.index')"
title = "Teams"
:active = "route().current('teams.*')"
/>
<SidebarCollapsibleItem
title = "Departments"
:href = "route('departments.index')"
:active = "route().current('departments.*')"
/>
<SidebarCollapsibleItem
title = "Divisions"
:href = "route('divisions.index')"
:active = "route().current('divisions.*')"
/>
</SidebarCollapsible>
<SidebarCollapsible
id = "locations"
title = "Locations"
:active = "route().current('externals.*') || route().current('internals.*')"
:is-open = "openCollapsibleId === 'locations'"
@toggle = "toggleCollapsible"
>
<template #icon>
<BuildingOffice2Icon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
<SidebarCollapsibleItem
title = "Internals"
:href = "route('internals.index')"
:active = "route().current('internals.*')"
/>
<SidebarCollapsibleItem
title = "Externals"
:href = "route('externals.index')"
:active = "route().current('externals.*')"
/>
</SidebarCollapsible>
<SidebarCollapsible
id = "test"
title = "test"
:active = "route().current('api-tokens.*')"
:is-open = "openCollapsibleId === 'test'"
@toggle = "toggleCollapsible"
>
<template #icon>
<BuildingOffice2Icon
class = "flex-shrink-0 w-6 h-6"
aria-hidden = "true"
/>
</template>
<SidebarCollapsibleItem
title = "Internals"
:href = "route('api-tokens.index')"
:active = "route().current('user.*')"
/>
<SidebarCollapsibleItem
title = "Externals"
:href = "route('api-tokens.index')"
:active = "route().current('api-tokens.*')"
/>
</SidebarCollapsible>
</PerfectScrollbar>
</template>
Просмотрите как спрашивать , а также более конкретно как спрашивать о домашнем задании. Объяснение того, что вы хотите, чтобы ваш код делал, не объясняет, что ваш код делает в данный момент и с какими реальными проблемами вы сталкиваетесь. Нам нужно гораздо больше информации: как вы ожидаете, что ваш код будет работать в настоящее время (познакомьте нас с ним), ожидаемые и фактические результаты, детали отладки (значения переменных, вызываются ли определенные функции и т. д.).