Выпадающее меню закрывается при открытии другого

Я новичок в Vue.js, делаю проект CRUD для своей курсовой работы.

У меня проблемы с поведением раскрывающегося меню. Может ли кто-нибудь мне помочь?

Я хочу :

  1. раскрывающееся меню без активного состояния автоматически закрывается (сворачивается), когда пользователь пытается открыть другое раскрывающееся меню без активного состояния.
  2. выпадающее меню с активным состоянием не может закрываться (сворачиваться) автоматически, закрывать или открывать может только пользователь
  3. когда раскрывающееся меню закрытия (свертывания) пользователя имеет активное состояние, я хочу, чтобы раскрывающееся меню без активного состояния все еще было открыто (не сворачивалось) (если оно уже открыто)

Примечание:

  • активное состояние — это когда доступ пользователя: активный маршрут

  • 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>

Спасибо за любую помощь, извините за мой плохой английский.

Просмотрите как спрашивать , а также более конкретно как спрашивать о домашнем задании. Объяснение того, что вы хотите, чтобы ваш код делал, не объясняет, что ваш код делает в данный момент и с какими реальными проблемами вы сталкиваетесь. Нам нужно гораздо больше информации: как вы ожидаете, что ваш код будет работать в настоящее время (познакомьте нас с ним), ожидаемые и фактические результаты, детали отладки (значения переменных, вызываются ли определенные функции и т. д.).

yoduh 29.04.2024 15:33

ваш родительский компонент должен сохранять состояние, в котором складной объект открыт (используйте и индекс или какой-либо идентификатор), щелчок для открытия должен отправить событие обратно родительскому элементу, и родительский элемент решает, какой складной объект должен быть открыт. В исходном коде я не смог найти ничего, что сообщало бы боковой панели, какой элемент следует открыть/развернуть. например, при нажатии кнопки переключения: @click = "$emit('activeItem', activeItemIdentifier)"

danielRICADO 09.05.2024 02:00

Можете ли вы привести простой пример реализации этого??

Reynaldo 10.05.2024 08:49

документацию можно найти vuejs.org/guide/comComponents/events.html

danielRICADO 14.05.2024 05:52
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
4
179
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

спасибо за комментарий @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>

Другие вопросы по теме

Похожие вопросы

Захват пар ключ/значение объектной строки (аналогично JSON) с использованием регулярного выражения
Нужно ли мне возвращать что-то важное из случаев переключения или этого достаточно, чтобы различать разные PHP?
Как исправить данные, которые не отображаются на странице, но отображаются при предварительном просмотре в сети
Преобразовать список массивов внутри объекта в элементы div, содержащие до четырех элементов в React
Пользовательский клик передается только один раз в JavaScript вместе с другим поведением
Vue Quill css применяется только к моему первому компоненту Quill Editor + как настроить панель инструментов
Ошибка Alpine.js «paginationData не определена» в компоненте Laravel Jetstream
Показывать всплывающее окно разрешения для буфера обмена
Загрузка изображений React, повторная отрисовка загруженных изображений при добавлении/удалении
Элемент не обновляется в DOM после изменения его состояния реакции