1
This commit is contained in:
@@ -4,6 +4,12 @@ import type { IResultSkill } from "@/api/calc/type";
|
||||
import { useConfigStore, useInfoStore } from "@/stores";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import Tabs from "@/components/ui/tabs/Tabs.vue";
|
||||
@@ -240,6 +246,7 @@ const equippedParts = computed(() =>
|
||||
/* ─── Change sources ─── */
|
||||
const changeSources = computed(() => configStore.changeSourceLogs.slice(0, 8));
|
||||
const clearChangeSources = () => configStore.clearChangeSource();
|
||||
const leftPanelOpenSections = ["equipped", "stats", "suit", "equipped-list"];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -248,152 +255,166 @@ const clearChangeSources = () => configStore.clearChangeSource();
|
||||
<div
|
||||
class="flex w-full flex-none flex-col gap-3 lg:w-72 lg:overflow-y-auto lg:overflow-x-hidden"
|
||||
>
|
||||
<!-- Equipment / Oath grid -->
|
||||
<Card>
|
||||
<CardContent class="space-y-2 p-3">
|
||||
<Tabs :model-value="displayTab">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="equ" @click="displayTab = 'equ'">装备</TabsTrigger>
|
||||
<TabsTrigger value="oath" @click="displayTab = 'oath'">誓约</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div v-if="displayTab === 'equ'">
|
||||
<EquList
|
||||
v-model:part="equippedPart"
|
||||
:detail="configStore.config.equips"
|
||||
:with-sub-weapon="infoStore.infos?.alter?.includes('vegabond') ?? false"
|
||||
:with-pet="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<OathList v-model:part="equippedOathPart" :detail="configStore.config.oaths" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<CardContent class="py-2">
|
||||
<Accordion type="multiple" :default-value="leftPanelOpenSections" class="w-full">
|
||||
<AccordionItem value="equipped">
|
||||
<AccordionTrigger>穿戴概览</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div class="space-y-2 px-1">
|
||||
<Tabs :model-value="displayTab">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="equ" @click="displayTab = 'equ'">装备</TabsTrigger>
|
||||
<TabsTrigger value="oath" @click="displayTab = 'oath'">誓约</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div v-if="displayTab === 'equ'">
|
||||
<EquList
|
||||
v-model:part="equippedPart"
|
||||
:detail="configStore.config.equips"
|
||||
:with-sub-weapon="infoStore.infos?.alter?.includes('vegabond') ?? false"
|
||||
:with-pet="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<OathList v-model:part="equippedOathPart" :detail="configStore.config.oaths" />
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<!-- Character stats -->
|
||||
<Card v-if="resultState?.info?.length">
|
||||
<CardHeader class="pb-1 pt-3">
|
||||
<CardTitle class="text-xs text-muted-foreground">角色属性</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="pb-3">
|
||||
<div class="grid grid-cols-2 gap-x-3 gap-y-0.5 text-xs">
|
||||
<template v-for="info in resultState.info" :key="info.id">
|
||||
<div
|
||||
class="flex justify-between gap-1"
|
||||
:class="
|
||||
typeof info.value === 'string' && info.value.includes('/') ? 'col-span-2' : ''
|
||||
"
|
||||
>
|
||||
<span class="shrink-0 text-muted-foreground">{{ info.name }}</span>
|
||||
<span class="text-right font-medium text-emerald-600">{{ info.value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AccordionItem v-if="resultState?.info?.length" value="stats">
|
||||
<AccordionTrigger>角色属性</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div class="grid grid-cols-2 gap-x-3 gap-y-0.5 px-1 text-xs">
|
||||
<template v-for="info in resultState.info" :key="info.id">
|
||||
<div
|
||||
class="flex justify-between gap-1"
|
||||
:class="
|
||||
typeof info.value === 'string' && info.value.includes('/')
|
||||
? 'col-span-2'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<span class="shrink-0 text-muted-foreground">{{ info.name }}</span>
|
||||
<span class="text-right font-medium text-emerald-600">{{ info.value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<!-- Suit info + oath skills -->
|
||||
<Card v-if="suit || oathSuit">
|
||||
<CardContent class="space-y-1 px-3 py-2 text-xs">
|
||||
<div v-if="suit" class="flex items-center gap-1.5">
|
||||
<img
|
||||
:src="getImageURL(`/equipment${suit.imageUrl}`)"
|
||||
alt=""
|
||||
class="size-5 object-contain"
|
||||
/>
|
||||
<span :class="rarityColor(suit.rarity)" class="flex-1">{{ suit.name }} 套装</span>
|
||||
<span class="shrink-0 text-muted-foreground">{{ suit.rarity }}</span>
|
||||
<span class="shrink-0 font-medium">{{ suit.point }}点</span>
|
||||
</div>
|
||||
<div v-if="oathSuit" class="flex items-center gap-1.5">
|
||||
<img
|
||||
:src="getImageURL(`/equipment${oathSuit.imageUrl}`)"
|
||||
alt=""
|
||||
class="size-5 object-contain"
|
||||
/>
|
||||
<span :class="rarityColor(oathSuit.rarity)" class="flex-1"
|
||||
>{{ oathSuit.name }} 誓约</span
|
||||
>
|
||||
<span class="shrink-0 text-muted-foreground">{{ oathSuit.rarity }}</span>
|
||||
<span class="shrink-0 font-medium">{{ oathSuit.point }}点</span>
|
||||
</div>
|
||||
<!-- Oath skill selector -->
|
||||
<div v-if="oathSkillsSorted.length" class="flex items-center justify-center gap-4 pt-1">
|
||||
<button
|
||||
v-for="skill in oathSkillsSorted"
|
||||
:key="skill.id"
|
||||
type="button"
|
||||
:disabled="!skill.enable"
|
||||
class="rounded-full transition-opacity"
|
||||
:class="[
|
||||
skill.enable ? 'cursor-pointer opacity-100' : 'cursor-not-allowed opacity-40',
|
||||
skill.active ? 'ring-2 ring-amber-400 ring-offset-1' : '',
|
||||
]"
|
||||
@click="selectOathSkill(skill)"
|
||||
>
|
||||
<img
|
||||
:src="getImageURL(skill.imageUrl)"
|
||||
:alt="skill.name"
|
||||
class="size-8 rounded-full object-contain"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AccordionItem v-if="suit || oathSuit" value="suit">
|
||||
<AccordionTrigger>套装与誓约</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div class="space-y-1 px-1 text-xs">
|
||||
<div v-if="suit" class="flex items-center gap-1.5">
|
||||
<img
|
||||
:src="getImageURL(`/equipment${suit.imageUrl}`)"
|
||||
alt=""
|
||||
class="size-5 object-contain"
|
||||
/>
|
||||
<span :class="rarityColor(suit.rarity)" class="flex-1"
|
||||
>{{ suit.name }} 套装</span
|
||||
>
|
||||
<span class="shrink-0 text-muted-foreground">{{ suit.rarity }}</span>
|
||||
<span class="shrink-0 font-medium">{{ suit.point }}点</span>
|
||||
</div>
|
||||
<div v-if="oathSuit" class="flex items-center gap-1.5">
|
||||
<img
|
||||
:src="getImageURL(`/equipment${oathSuit.imageUrl}`)"
|
||||
alt=""
|
||||
class="size-5 object-contain"
|
||||
/>
|
||||
<span :class="rarityColor(oathSuit.rarity)" class="flex-1"
|
||||
>{{ oathSuit.name }} 誓约</span
|
||||
>
|
||||
<span class="shrink-0 text-muted-foreground">{{ oathSuit.rarity }}</span>
|
||||
<span class="shrink-0 font-medium">{{ oathSuit.point }}点</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="oathSkillsSorted.length"
|
||||
class="flex items-center justify-center gap-4 pt-1"
|
||||
>
|
||||
<button
|
||||
v-for="skill in oathSkillsSorted"
|
||||
:key="skill.id"
|
||||
type="button"
|
||||
:disabled="!skill.enable"
|
||||
class="rounded-full transition-opacity"
|
||||
:class="[
|
||||
skill.enable
|
||||
? 'cursor-pointer opacity-100'
|
||||
: 'cursor-not-allowed opacity-40',
|
||||
skill.active ? 'ring-2 ring-amber-400 ring-offset-1' : '',
|
||||
]"
|
||||
@click="selectOathSkill(skill)"
|
||||
>
|
||||
<img
|
||||
:src="getImageURL(skill.imageUrl)"
|
||||
:alt="skill.name"
|
||||
class="size-8 rounded-full object-contain"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<!-- Equipped items list -->
|
||||
<Card v-if="equippedParts.length">
|
||||
<CardHeader class="pb-1 pt-3">
|
||||
<CardTitle class="text-xs text-muted-foreground">穿戴装备信息</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="pb-2">
|
||||
<ScrollArea class="max-h-64">
|
||||
<div class="space-y-0.5 text-xs">
|
||||
<div
|
||||
v-for="part in equippedParts"
|
||||
:key="part"
|
||||
class="flex items-center gap-1.5 rounded px-1 py-0.5 hover:bg-muted/40"
|
||||
>
|
||||
<img
|
||||
:src="getImageURL(`/equipment/part/${part}.png`)"
|
||||
:alt="part"
|
||||
class="size-4 shrink-0 object-contain"
|
||||
/>
|
||||
<img
|
||||
v-if="adaptation(part) > 0"
|
||||
:src="getImageURL(`/equipment/adaptation/${adaptation(part)}.png`)"
|
||||
alt=""
|
||||
class="h-2.5 w-3 shrink-0 object-contain"
|
||||
/>
|
||||
<span
|
||||
v-if="(configStore.config.equips[part]?.reinforce ?? 0) > 0"
|
||||
class="shrink-0 font-medium"
|
||||
:class="
|
||||
configStore.config.equips[part]?.reinforceType === 1
|
||||
? 'text-[#ef28c1]'
|
||||
: 'text-[#68d5ed]'
|
||||
"
|
||||
>+{{ configStore.config.equips[part]?.reinforce }}</span
|
||||
>
|
||||
<span
|
||||
v-if="(configStore.config.equips[part]?.refine ?? 0) > 0"
|
||||
class="shrink-0 text-[#9879da]"
|
||||
>({{ configStore.config.equips[part]?.refine }})</span
|
||||
>
|
||||
<span
|
||||
class="truncate"
|
||||
:class="
|
||||
rarityColor(equById(configStore.config.equips[part]?.id ?? '')?.rarity ?? '')
|
||||
"
|
||||
>{{ equById(configStore.config.equips[part]?.id ?? "")?.name ?? "—" }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<template #scrollbar>
|
||||
<ScrollBar orientation="vertical" />
|
||||
</template>
|
||||
</ScrollArea>
|
||||
<AccordionItem v-if="equippedParts.length" value="equipped-list">
|
||||
<AccordionTrigger>穿戴装备信息</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<ScrollArea class="max-h-64 px-1">
|
||||
<div class="space-y-0.5 text-xs">
|
||||
<div
|
||||
v-for="part in equippedParts"
|
||||
:key="part"
|
||||
class="flex items-center gap-1.5 rounded px-1 py-0.5 hover:bg-muted/40"
|
||||
>
|
||||
<img
|
||||
:src="getImageURL(`/equipment/part/${part}.png`)"
|
||||
:alt="part"
|
||||
class="size-4 shrink-0 object-contain"
|
||||
/>
|
||||
<img
|
||||
v-if="adaptation(part) > 0"
|
||||
:src="getImageURL(`/equipment/adaptation/${adaptation(part)}.png`)"
|
||||
alt=""
|
||||
class="h-2.5 w-3 shrink-0 object-contain"
|
||||
/>
|
||||
<span
|
||||
v-if="(configStore.config.equips[part]?.reinforce ?? 0) > 0"
|
||||
class="shrink-0 font-medium"
|
||||
:class="
|
||||
configStore.config.equips[part]?.reinforceType === 1
|
||||
? 'text-[#ef28c1]'
|
||||
: 'text-[#68d5ed]'
|
||||
"
|
||||
>+{{ configStore.config.equips[part]?.reinforce }}</span
|
||||
>
|
||||
<span
|
||||
v-if="(configStore.config.equips[part]?.refine ?? 0) > 0"
|
||||
class="shrink-0 text-[#9879da]"
|
||||
>({{ configStore.config.equips[part]?.refine }})</span
|
||||
>
|
||||
<span
|
||||
class="truncate"
|
||||
:class="
|
||||
rarityColor(
|
||||
equById(configStore.config.equips[part]?.id ?? '')?.rarity ?? '',
|
||||
)
|
||||
"
|
||||
>{{ equById(configStore.config.equips[part]?.id ?? "")?.name ?? "—" }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<template #scrollbar>
|
||||
<ScrollBar orientation="vertical" />
|
||||
</template>
|
||||
</ScrollArea>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user