Files
dcalc/frontendNew/src/pages/Dcalc/Character/Index.vue
T
2026-05-20 18:25:21 +08:00

180 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { LoaderCircle, PanelRight, RefreshCcw } from "lucide-vue-next";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
import Tabs from "@/components/ui/tabs/Tabs.vue";
import TabsList from "@/components/ui/tabs/TabsList.vue";
import TabsTrigger from "@/components/ui/tabs/TabsTrigger.vue";
import { useConfigStore, useInfoStore } from "@/stores";
import ResultPanel from "@/pages/Dcalc/Character/Result/Index.vue";
const props = defineProps<{
alter: string;
}>();
const route = useRoute();
const router = useRouter();
const infoStore = useInfoStore();
const configStore = useConfigStore();
const loading = ref(false);
const loadError = ref("");
const isExecuting = ref(false);
const isMobileLayout = ref(false);
const isResultSheetOpen = ref(false);
let mediaQuery: MediaQueryList | null = null;
const updateMobileLayout = () => {
isMobileLayout.value = mediaQuery?.matches ?? false;
if (!isMobileLayout.value) {
isResultSheetOpen.value = false;
}
};
const tabs = [
{ label: "装备", key: "equipment" },
{ label: "技能", key: "skill" },
{ label: "打造", key: "forge" },
] as const;
const activeTab = computed(() => {
const path = route.path;
if (path.endsWith("/skill")) return "skill";
if (path.endsWith("/forge")) return "forge";
return "equipment";
});
// Result panel is now embedded inside Equipment/Skill/Forge pages to avoid duplicate rendering.
const showGlobalResultPanel = computed(() => false);
const loadCharacter = async () => {
loading.value = true;
loadError.value = "";
try {
await infoStore.createCharacter(props.alter);
} catch (error) {
loadError.value = error instanceof Error ? error.message : "角色数据加载失败";
} finally {
loading.value = false;
}
};
const switchTab = (tab: (typeof tabs)[number]["key"]) => {
router.push(`/dcalc/character/${props.alter}/${tab}`);
};
onMounted(() => {
loadCharacter();
window.addEventListener("beforeunload", configStore.saveConfig);
mediaQuery = window.matchMedia("(max-width: 1279px)");
updateMobileLayout();
mediaQuery.addEventListener("change", updateMobileLayout);
});
watch(
() => props.alter,
() => {
loadCharacter();
},
);
watch(showGlobalResultPanel, (show) => {
if (!show) {
isResultSheetOpen.value = false;
}
});
const stopConfigWatch = watch(
() => JSON.stringify(configStore.config),
async () => {
if (isExecuting.value) return;
isExecuting.value = true;
try {
await configStore.result.execute();
configStore.saveConfig();
} finally {
isExecuting.value = false;
}
},
{ deep: true },
);
onUnmounted(() => {
stopConfigWatch();
mediaQuery?.removeEventListener("change", updateMobileLayout);
window.removeEventListener("beforeunload", configStore.saveConfig);
configStore.saveConfig();
});
</script>
<template>
<section class="mx-auto flex min-h-full w-full max-w-7xl flex-col gap-4 px-4 py-6 md:px-8">
<Card class="flex-none">
<CardHeader>
<CardTitle class="flex items-center justify-between">
<span>角色配置</span>
<Button variant="outline" size="sm" :disabled="loading" @click="loadCharacter">
<RefreshCcw :class="{ 'animate-spin': loading }" />
刷新
</Button>
</CardTitle>
</CardHeader>
<CardContent class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<Tabs :model-value="activeTab" class="gap-0">
<TabsList>
<TabsTrigger
v-for="tab in tabs"
:key="tab.key"
:value="tab.key"
@click="switchTab(tab.key)"
>
{{ tab.label }}
</TabsTrigger>
</TabsList>
</Tabs>
<div class="text-sm text-muted-foreground">
<span v-if="loading" class="inline-flex items-center gap-2">
<LoaderCircle class="size-4 animate-spin" />
正在同步角色数据...
</span>
<span v-else-if="loadError">{{ loadError }}</span>
<span v-else>数据源已就绪,可继续编辑配置。</span>
</div>
</CardContent>
</Card>
<div
:class="[
'grid min-h-0 flex-1 grid-cols-1 gap-4',
showGlobalResultPanel ? 'xl:grid-cols-[minmax(0,1fr)_24rem]' : 'xl:grid-cols-1',
]"
>
<div class="min-w-0 overflow-hidden">
<router-view />
</div>
<aside v-if="!isMobileLayout && showGlobalResultPanel" class="min-h-0">
<ResultPanel />
</aside>
</div>
<Sheet v-if="isMobileLayout && showGlobalResultPanel" v-model:open="isResultSheetOpen">
<SheetTrigger as-child>
<Button class="fixed bottom-5 right-4 z-30 shadow-lg" size="sm">
<PanelRight class="size-4" />
查看结果
</Button>
</SheetTrigger>
<SheetContent side="bottom" class="h-[78vh] overflow-hidden px-3 pb-3">
<SheetHeader class="pb-2">
<SheetTitle>实时计算结果</SheetTitle>
</SheetHeader>
<div class="h-[calc(78vh-4.5rem)] overflow-hidden">
<ResultPanel />
</div>
</SheetContent>
</Sheet>
</section>
</template>