Files
dcalc/frontendNew/src/components/DcalcBreadcrumb.vue
T
2026-05-11 00:32:03 +08:00

115 lines
3.9 KiB
Vue

<script setup lang="ts">
import { useRoute, useRouter } from "vue-router";
import { computed, ref, watch } from "vue";
import Breadcrumb from "@/components/ui/breadcrumb/Breadcrumb.vue";
import BreadcrumbList from "@/components/ui/breadcrumb/BreadcrumbList.vue";
import BreadcrumbItem from "@/components/ui/breadcrumb/BreadcrumbItem.vue";
import BreadcrumbLink from "@/components/ui/breadcrumb/BreadcrumbLink.vue";
import BreadcrumbSeparator from "@/components/ui/breadcrumb/BreadcrumbSeparator.vue";
import BreadcrumbEllipsis from "@/components/ui/breadcrumb/BreadcrumbEllipsis.vue";
import BreadcrumbPage from "@/components/ui/breadcrumb/BreadcrumbPage.vue";
const router = useRouter();
const route = useRoute();
type PathCrumb = { label: string; to: string };
type EllipsisCrumb = { type: "ellipsis" };
type DisplayCrumb = PathCrumb | EllipsisCrumb;
const routeTrail = ref<PathCrumb[]>([]);
function labelFromPath(path: string): string {
if (path === "/dcalc" || path === "/dcalc/home") return "首页";
if (path.startsWith("/dcalc/dnfhelper")) return "助手导入";
if (path.endsWith("/equipment")) return "装备词典";
if (path.endsWith("/skill")) return "技能面板";
if (path.endsWith("/forge")) return "打造";
if (path.includes("/character/")) {
const alter = path.split("/")[3];
return alter ? `角色 ${alter}` : "角色";
}
return "页面";
}
watch(
() => route.fullPath,
(fullPath) => {
if (!fullPath.startsWith("/dcalc")) {
routeTrail.value = [];
return;
}
const existedIndex = routeTrail.value.findIndex((item) => item.to === fullPath);
if (existedIndex >= 0) {
routeTrail.value = routeTrail.value.slice(0, existedIndex + 1);
return;
}
routeTrail.value = [
...routeTrail.value,
{
label: labelFromPath(fullPath),
to: fullPath,
},
];
},
{ immediate: true },
);
const crumbs = computed(() => routeTrail.value);
const maxCrumbs = 4; // 超过则折叠
const showEllipsis = computed(() => crumbs.value.length > maxCrumbs);
const visibleCrumbs = computed(() => {
if (!showEllipsis.value) return crumbs.value;
// 只显示第一个、...、最后两个
return [crumbs.value[0], { type: "ellipsis" }, ...crumbs.value.slice(-2)] as DisplayCrumb[];
});
function go(to: string) {
if (to && typeof to === "string" && to !== route.fullPath) router.push(to);
}
</script>
<template>
<header
v-if="crumbs.length"
class="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)"
>
<div class="max-w-7xl mx-auto px-6 py-3 flex items-center min-h-[56px]">
<Breadcrumb>
<BreadcrumbList>
<template
v-for="(crumb, idx) in visibleCrumbs"
:key="'type' in crumb ? 'ellipsis' + idx : crumb.to"
>
<BreadcrumbSeparator v-if="idx !== 0" />
<template v-if="'type' in crumb">
<BreadcrumbEllipsis />
</template>
<template v-else>
<BreadcrumbItem>
<template v-if="idx !== visibleCrumbs.length - 1">
<BreadcrumbLink
as="a"
@click.prevent="go(crumb.to)"
class="cursor-pointer font-normal text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
>{{ crumb.label }}</BreadcrumbLink
>
</template>
<template v-else>
<BreadcrumbPage class="font-semibold text-gray-900 dark:text-gray-100">{{
crumb.label
}}</BreadcrumbPage>
</template>
</BreadcrumbItem>
</template>
</template>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
</template>
<style scoped></style>