dev
This commit is contained in:
@@ -2,8 +2,12 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { useConfigStore, useInfoStore } from "@/stores";
|
||||
import { Button } from "@/components/ui/button";
|
||||
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 { getImageURL } from "@/utils";
|
||||
import type { ISkill } from "@/api/info/type";
|
||||
import ResultPanel from "@/pages/Dcalc/Character/Result/Index.vue";
|
||||
|
||||
const infoStore = useInfoStore();
|
||||
const configStore = useConfigStore();
|
||||
@@ -14,6 +18,10 @@ const activeSkillId = ref("");
|
||||
const notice = ref("");
|
||||
const noticeType = ref<"error" | "info">("info");
|
||||
const openUpPickerSlot = ref<number | null>(null);
|
||||
const rightPanelTab = ref<"detail" | "result">("result");
|
||||
const leftPanelWidthClass = computed(() =>
|
||||
rightPanelTab.value === "result" ? "xl:w-[460px]" : "xl:w-[550px]",
|
||||
);
|
||||
|
||||
/* ─── Constants ─── */
|
||||
const VP_LIMIT = 5;
|
||||
@@ -34,14 +42,11 @@ interface ITreeRow {
|
||||
}
|
||||
|
||||
const getSkillsInRange = (lvStart: number, lvEnd: number): ISkill[] =>
|
||||
infoStore.skills.filter(
|
||||
(s) => s.line > lvStart - 5 && s.line <= Math.max(lvEnd - 5, 1),
|
||||
);
|
||||
infoStore.skills.filter((s) => s.line > lvStart - 5 && s.line <= Math.max(lvEnd - 5, 1));
|
||||
|
||||
const getSkillAt = (lvStart: number, lvEnd: number, col: number): ISkill | null =>
|
||||
infoStore.skills.find(
|
||||
(s) =>
|
||||
s.line > lvStart - 5 && s.line <= Math.max(lvEnd - 5, 1) && s.position === col,
|
||||
(s) => s.line > lvStart - 5 && s.line <= Math.max(lvEnd - 5, 1) && s.position === col,
|
||||
) ?? null;
|
||||
|
||||
const treeRows = computed((): ITreeRow[] => {
|
||||
@@ -93,8 +98,12 @@ const lvColor = (id: string, maxLearnLv: number): string => {
|
||||
const selectedSkill = computed(() =>
|
||||
activeSkillId.value ? skillById(activeSkillId.value) : undefined,
|
||||
);
|
||||
const chooseSkill = (id: string) => { activeSkillId.value = id; };
|
||||
const clearActive = () => { activeSkillId.value = ""; };
|
||||
const chooseSkill = (id: string) => {
|
||||
activeSkillId.value = id;
|
||||
};
|
||||
const clearActive = () => {
|
||||
activeSkillId.value = "";
|
||||
};
|
||||
|
||||
/* ─── Level actions ─── */
|
||||
const updateLv = (id: string, value: number) => {
|
||||
@@ -115,11 +124,11 @@ const setMaxLv = (id: string) => updateLv(id, skillById(id)?.maxLearnLv ?? 0);
|
||||
const resetLv = (id: string) => updateLv(id, 0);
|
||||
|
||||
/* ─── VP ─── */
|
||||
const usedVPCount = computed(() =>
|
||||
infoStore.skills
|
||||
.filter((s) => s.hasVP)
|
||||
.filter((s) => (configStore.config.skills[s.id.toString()]?.vp ?? 0) > 0)
|
||||
.length,
|
||||
const usedVPCount = computed(
|
||||
() =>
|
||||
infoStore.skills
|
||||
.filter((s) => s.hasVP)
|
||||
.filter((s) => (configStore.config.skills[s.id.toString()]?.vp ?? 0) > 0).length,
|
||||
);
|
||||
|
||||
const vpSkills = computed(() =>
|
||||
@@ -129,7 +138,10 @@ const vpSkills = computed(() =>
|
||||
const setVPMode = (id: string, mode: 0 | 1 | 2) => {
|
||||
const skill = skillById(id);
|
||||
if (!skill) return;
|
||||
if (!skill.hasVP && mode !== 0) { setNotice(`${skill.name} 不支持 VP。`, "error"); return; }
|
||||
if (!skill.hasVP && mode !== 0) {
|
||||
setNotice(`${skill.name} 不支持 VP。`, "error");
|
||||
return;
|
||||
}
|
||||
const target = skillCfg(id);
|
||||
if (target.vp === 0 && mode > 0 && usedVPCount.value >= VP_LIMIT) {
|
||||
setNotice(`VP 最多选 ${VP_LIMIT} 个技能。`, "error");
|
||||
@@ -187,11 +199,18 @@ const setUPSlotSkill = (slotIdx: number, nextId: string) => {
|
||||
}
|
||||
const target = skillById(nextId);
|
||||
if (!target) return;
|
||||
if ((skillCfg(nextId).lv ?? 0) <= 0) { setNotice(`${target.name} 等级为 0,无法 UP。`, "error"); return; }
|
||||
if ((skillCfg(nextId).lv ?? 0) <= 0) {
|
||||
setNotice(`${target.name} 等级为 0,无法 UP。`, "error");
|
||||
return;
|
||||
}
|
||||
if (upSlots.value.some((s, i) => i !== slotIdx && s.skillId === nextId)) {
|
||||
setNotice(`${target.name} 已在其他槽位。`, "error"); return;
|
||||
setNotice(`${target.name} 已在其他槽位。`, "error");
|
||||
return;
|
||||
}
|
||||
if (!prevId && usedUPCount.value >= UP_LIMIT) {
|
||||
setNotice(`UP 最多选 ${UP_LIMIT} 个技能。`, "error");
|
||||
return;
|
||||
}
|
||||
if (!prevId && usedUPCount.value >= UP_LIMIT) { setNotice(`UP 最多选 ${UP_LIMIT} 个技能。`, "error"); return; }
|
||||
if (prevId && prevId !== nextId) skillCfg(prevId).up = 0;
|
||||
skillCfg(nextId).up = slot.mode;
|
||||
track(`${target.name} 设置 UP 槽位 ${slotIdx + 1} 形态 ${slot.mode}`);
|
||||
@@ -200,7 +219,10 @@ const setUPSlotSkill = (slotIdx: number, nextId: string) => {
|
||||
|
||||
const setUPSlotMode = (slotIdx: number, mode: 1 | 2) => {
|
||||
const skillId = upSlots.value[slotIdx]?.skillId;
|
||||
if (!skillId) { setNotice(`请先为槽位 ${slotIdx + 1} 选择技能。`, "error"); return; }
|
||||
if (!skillId) {
|
||||
setNotice(`请先为槽位 ${slotIdx + 1} 选择技能。`, "error");
|
||||
return;
|
||||
}
|
||||
skillCfg(skillId).up = mode;
|
||||
track(`${skillById(skillId)?.name} UP 形态 → ${mode}`);
|
||||
};
|
||||
@@ -220,9 +242,15 @@ const resetAllSkills = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full overflow-hidden" @click.self="clearActive">
|
||||
<div class="flex h-full flex-col overflow-hidden xl:flex-row" @click.self="clearActive">
|
||||
<!-- ══ LEFT PANEL ══ -->
|
||||
<div class="w-[550px] shrink-0 flex flex-col border-r border-border/50 overflow-hidden">
|
||||
<div
|
||||
:class="[
|
||||
'max-h-[52vh] w-full shrink-0 border-b border-border/50 xl:max-h-none xl:border-b-0 xl:border-r',
|
||||
leftPanelWidthClass,
|
||||
]"
|
||||
>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<!-- Tab bar -->
|
||||
<div
|
||||
class="h-10 shrink-0 flex border-b border-border/50 bg-muted/20 text-[15px] font-bold select-none"
|
||||
@@ -279,8 +307,12 @@ const resetAllSkills = () => {
|
||||
@click.stop="chooseSkill(skill.id.toString())"
|
||||
>
|
||||
<div class="badge-row">
|
||||
<span v-if="skillCfg(skill.id.toString()).vp > 0" class="badge badge-vp">VP</span>
|
||||
<span v-if="skillCfg(skill.id.toString()).up > 0" class="badge badge-up">UP</span>
|
||||
<span v-if="skillCfg(skill.id.toString()).vp > 0" class="badge badge-vp"
|
||||
>VP</span
|
||||
>
|
||||
<span v-if="skillCfg(skill.id.toString()).up > 0" class="badge badge-up"
|
||||
>UP</span
|
||||
>
|
||||
</div>
|
||||
<img
|
||||
:src="getImageURL(skill.icon)"
|
||||
@@ -295,8 +327,12 @@ const resetAllSkills = () => {
|
||||
/>
|
||||
<div v-if="activeSkillId === skill.id.toString()" class="action-row" @click.stop>
|
||||
<button class="skill-btn" @click.stop="resetLv(skill.id.toString())">«</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(skill.id.toString(), -1)">‹</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(skill.id.toString(), 1)">›</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(skill.id.toString(), -1)">
|
||||
‹
|
||||
</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(skill.id.toString(), 1)">
|
||||
›
|
||||
</button>
|
||||
<button class="skill-btn" @click.stop="setMaxLv(skill.id.toString())">»</button>
|
||||
</div>
|
||||
<div
|
||||
@@ -321,8 +357,12 @@ const resetAllSkills = () => {
|
||||
>
|
||||
<template v-if="cell">
|
||||
<div class="badge-row">
|
||||
<span v-if="skillCfg(cell.id.toString()).vp > 0" class="badge badge-vp">VP</span>
|
||||
<span v-if="skillCfg(cell.id.toString()).up > 0" class="badge badge-up">UP</span>
|
||||
<span v-if="skillCfg(cell.id.toString()).vp > 0" class="badge badge-vp"
|
||||
>VP</span
|
||||
>
|
||||
<span v-if="skillCfg(cell.id.toString()).up > 0" class="badge badge-up"
|
||||
>UP</span
|
||||
>
|
||||
</div>
|
||||
<img
|
||||
:src="getImageURL(cell.icon)"
|
||||
@@ -337,9 +377,15 @@ const resetAllSkills = () => {
|
||||
/>
|
||||
<div v-if="activeSkillId === cell.id.toString()" class="action-row" @click.stop>
|
||||
<button class="skill-btn" @click.stop="resetLv(cell.id.toString())">«</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(cell.id.toString(), -1)">‹</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(cell.id.toString(), 1)">›</button>
|
||||
<button class="skill-btn" @click.stop="setMaxLv(cell.id.toString())">»</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(cell.id.toString(), -1)">
|
||||
‹
|
||||
</button>
|
||||
<button class="skill-btn" @click.stop="stepLv(cell.id.toString(), 1)">
|
||||
›
|
||||
</button>
|
||||
<button class="skill-btn" @click.stop="setMaxLv(cell.id.toString())">
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -360,7 +406,9 @@ const resetAllSkills = () => {
|
||||
<div v-show="tab === 1" class="flex-1 overflow-y-auto flex flex-col min-h-0">
|
||||
<!-- UP section -->
|
||||
<div class="shrink-0 border-b border-border/50">
|
||||
<div class="px-3 pt-3 pb-1 text-xs font-semibold text-muted-foreground flex justify-between">
|
||||
<div
|
||||
class="px-3 pt-3 pb-1 text-xs font-semibold text-muted-foreground flex justify-between"
|
||||
>
|
||||
<span>技能突破 (UP)</span>
|
||||
<span>{{ usedUPCount }}/{{ UP_LIMIT }}</span>
|
||||
</div>
|
||||
@@ -370,7 +418,9 @@ const resetAllSkills = () => {
|
||||
:key="idx"
|
||||
class="flex items-center gap-2 rounded-md border border-border/50 bg-muted/10 p-2"
|
||||
>
|
||||
<div class="text-[10px] text-muted-foreground w-6 shrink-0 text-center">{{ idx + 1 }}</div>
|
||||
<div class="text-[10px] text-muted-foreground w-6 shrink-0 text-center">
|
||||
{{ idx + 1 }}
|
||||
</div>
|
||||
|
||||
<!-- Mode 1 icon -->
|
||||
<button
|
||||
@@ -387,8 +437,15 @@ const resetAllSkills = () => {
|
||||
"
|
||||
>
|
||||
<template v-if="slot.skillId">
|
||||
<img :src="getImageURL(skillById(slot.skillId)?.icon ?? '')" class="w-7 h-7 rounded object-cover" />
|
||||
<span v-if="slot.mode === 1" class="absolute -bottom-1 -right-1 text-[8px] bg-purple-500 text-white rounded-sm px-0.5 leading-3">1</span>
|
||||
<img
|
||||
:src="getImageURL(skillById(slot.skillId)?.icon ?? '')"
|
||||
class="w-7 h-7 rounded object-cover"
|
||||
/>
|
||||
<span
|
||||
v-if="slot.mode === 1"
|
||||
class="absolute -bottom-1 -right-1 text-[8px] bg-purple-500 text-white rounded-sm px-0.5 leading-3"
|
||||
>1</span
|
||||
>
|
||||
</template>
|
||||
<span v-else class="text-[10px] text-muted-foreground">1</span>
|
||||
</button>
|
||||
@@ -399,7 +456,9 @@ const resetAllSkills = () => {
|
||||
:disabled="!slot.skillId"
|
||||
title="切换形态"
|
||||
@click="exchangeUPMode(idx)"
|
||||
>⇄</button>
|
||||
>
|
||||
⇄
|
||||
</button>
|
||||
|
||||
<!-- Mode 2 icon -->
|
||||
<button
|
||||
@@ -416,15 +475,25 @@ const resetAllSkills = () => {
|
||||
"
|
||||
>
|
||||
<template v-if="slot.skillId">
|
||||
<img :src="getImageURL(skillById(slot.skillId)?.icon ?? '')" class="w-7 h-7 rounded object-cover" />
|
||||
<span v-if="slot.mode === 2" class="absolute -bottom-1 -right-1 text-[8px] bg-purple-500 text-white rounded-sm px-0.5 leading-3">2</span>
|
||||
<img
|
||||
:src="getImageURL(skillById(slot.skillId)?.icon ?? '')"
|
||||
class="w-7 h-7 rounded object-cover"
|
||||
/>
|
||||
<span
|
||||
v-if="slot.mode === 2"
|
||||
class="absolute -bottom-1 -right-1 text-[8px] bg-purple-500 text-white rounded-sm px-0.5 leading-3"
|
||||
>2</span
|
||||
>
|
||||
</template>
|
||||
<span v-else class="text-[10px] text-muted-foreground">2</span>
|
||||
</button>
|
||||
|
||||
<!-- Skill name -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-xs truncate" :title="slot.skillId ? (skillById(slot.skillId)?.name ?? '') : ''">
|
||||
<div
|
||||
class="text-xs truncate"
|
||||
:title="slot.skillId ? (skillById(slot.skillId)?.name ?? '') : ''"
|
||||
>
|
||||
{{ slot.skillId ? skillById(slot.skillId)?.name : "未选择技能" }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -433,16 +502,23 @@ const resetAllSkills = () => {
|
||||
<button
|
||||
class="text-[10px] border border-border/50 rounded px-1.5 py-0.5 text-muted-foreground hover:text-foreground shrink-0"
|
||||
@click="openUpPickerSlot = openUpPickerSlot === idx ? null : idx"
|
||||
>{{ slot.skillId ? "更换" : "选择" }}</button>
|
||||
>
|
||||
{{ slot.skillId ? "更换" : "选择" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Icon picker -->
|
||||
<div v-if="openUpPickerSlot !== null" class="rounded-md border border-border/50 bg-muted/20 p-2">
|
||||
<div
|
||||
v-if="openUpPickerSlot !== null"
|
||||
class="rounded-md border border-border/50 bg-muted/20 p-2"
|
||||
>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<button
|
||||
class="w-7 h-7 rounded border border-dashed border-border/60 text-[9px] text-muted-foreground hover:bg-muted/50"
|
||||
@click="setUPSlotSkill(openUpPickerSlot!, '')"
|
||||
>清</button>
|
||||
>
|
||||
清
|
||||
</button>
|
||||
<button
|
||||
v-for="item in upOptionsForSlot(openUpPickerSlot!)"
|
||||
:key="item.id"
|
||||
@@ -455,7 +531,11 @@ const resetAllSkills = () => {
|
||||
:title="item.name"
|
||||
@click="setUPSlotSkill(openUpPickerSlot!, item.id.toString())"
|
||||
>
|
||||
<img :src="getImageURL(item.icon)" :alt="item.name" class="w-full h-full rounded object-cover" />
|
||||
<img
|
||||
:src="getImageURL(item.icon)"
|
||||
:alt="item.name"
|
||||
class="w-full h-full rounded object-cover"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -464,19 +544,32 @@ const resetAllSkills = () => {
|
||||
|
||||
<!-- VP section -->
|
||||
<div class="flex-1 overflow-y-auto min-h-0">
|
||||
<div class="px-3 pt-3 pb-1 text-xs font-semibold text-muted-foreground flex justify-between sticky top-0 bg-background/80 backdrop-blur-sm z-10">
|
||||
<div
|
||||
class="px-3 pt-3 pb-1 text-xs font-semibold text-muted-foreground flex justify-between sticky top-0 bg-background/80 backdrop-blur-sm z-10"
|
||||
>
|
||||
<span>技能进化 (VP)</span>
|
||||
<span>{{ usedVPCount }}/{{ VP_LIMIT }}</span>
|
||||
</div>
|
||||
<div class="divide-y divide-border/30">
|
||||
<div v-for="skill in vpSkills" :key="skill.id" class="px-3 py-2.5 flex items-start gap-2">
|
||||
<div
|
||||
v-for="skill in vpSkills"
|
||||
:key="skill.id"
|
||||
class="px-3 py-2.5 flex items-start gap-2"
|
||||
>
|
||||
<!-- Lv + icon -->
|
||||
<div class="shrink-0 flex flex-col items-center gap-1 pt-0.5">
|
||||
<span class="text-[10px] text-muted-foreground">{{ skill.learnLv }}</span>
|
||||
<img :src="getImageURL(skill.icon)" :alt="skill.name" class="w-7 h-7 rounded object-cover" />
|
||||
<img
|
||||
:src="getImageURL(skill.icon)"
|
||||
:alt="skill.name"
|
||||
class="w-7 h-7 rounded object-cover"
|
||||
/>
|
||||
</div>
|
||||
<!-- Name -->
|
||||
<div class="shrink-0 w-[4.5rem] text-[11px] text-muted-foreground truncate pt-1.5" :title="skill.name">
|
||||
<div
|
||||
class="shrink-0 w-[4.5rem] text-[11px] text-muted-foreground truncate pt-1.5"
|
||||
:title="skill.name"
|
||||
>
|
||||
{{ skill.name }}
|
||||
</div>
|
||||
<!-- Two VP choice cards -->
|
||||
@@ -490,12 +583,23 @@ const resetAllSkills = () => {
|
||||
? 'border-purple-400 bg-purple-500/10'
|
||||
: 'border-border/50'
|
||||
"
|
||||
@click="setVPMode(skill.id.toString(), skillCfg(skill.id.toString()).vp === mode ? 0 : mode)"
|
||||
@click="
|
||||
setVPMode(
|
||||
skill.id.toString(),
|
||||
skillCfg(skill.id.toString()).vp === mode ? 0 : mode,
|
||||
)
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="text-[10px] font-semibold"
|
||||
:class="skillCfg(skill.id.toString()).vp === mode ? 'text-purple-400' : 'text-muted-foreground'"
|
||||
>形态 {{ mode }}</div>
|
||||
:class="
|
||||
skillCfg(skill.id.toString()).vp === mode
|
||||
? 'text-purple-400'
|
||||
: 'text-muted-foreground'
|
||||
"
|
||||
>
|
||||
形态 {{ mode }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-[10px] text-muted-foreground leading-tight line-clamp-2">
|
||||
{{ skill.vps[mode - 1]?.name || "—" }}
|
||||
</div>
|
||||
@@ -505,104 +609,158 @@ const resetAllSkills = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ RIGHT PANEL: Skill Detail ══ -->
|
||||
<div class="flex-1 flex flex-col overflow-y-auto" @click.self="clearActive">
|
||||
<!-- Notice bar -->
|
||||
<div
|
||||
v-if="notice"
|
||||
class="px-4 py-2 text-xs border-b shrink-0"
|
||||
:class="
|
||||
noticeType === 'error'
|
||||
? 'bg-destructive/10 text-destructive border-destructive/30'
|
||||
: 'bg-muted/30 text-muted-foreground border-border/40'
|
||||
"
|
||||
>{{ notice }}</div>
|
||||
<div class="flex-1 min-h-0 flex flex-col overflow-hidden" @click.self="clearActive">
|
||||
<div class="border-b border-border/50 px-3 py-2">
|
||||
<Tabs :model-value="rightPanelTab" class="gap-0">
|
||||
<TabsList>
|
||||
<TabsTrigger value="result" @click="rightPanelTab = 'result'">结果面板</TabsTrigger>
|
||||
<TabsTrigger value="detail" @click="rightPanelTab = 'detail'">技能详情</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<!-- Skill detail -->
|
||||
<div v-if="selectedSkill" class="p-5 space-y-5">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
:src="getImageURL(selectedSkill.icon)"
|
||||
:alt="selectedSkill.name"
|
||||
class="w-14 h-14 rounded-lg object-cover border border-border/60"
|
||||
/>
|
||||
<div v-if="rightPanelTab === 'result'" class="min-h-0 flex-1 overflow-hidden p-3">
|
||||
<ResultPanel />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- Notice bar -->
|
||||
<div
|
||||
v-if="notice"
|
||||
class="px-4 py-2 text-xs border-b shrink-0"
|
||||
:class="
|
||||
noticeType === 'error'
|
||||
? 'bg-destructive/10 text-destructive border-destructive/30'
|
||||
: 'bg-muted/30 text-muted-foreground border-border/40'
|
||||
"
|
||||
>
|
||||
{{ notice }}
|
||||
</div>
|
||||
|
||||
<!-- Skill detail -->
|
||||
<div v-if="selectedSkill" class="flex-1 overflow-y-auto p-5 space-y-5">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
:src="getImageURL(selectedSkill.icon)"
|
||||
:alt="selectedSkill.name"
|
||||
class="w-14 h-14 rounded-lg object-cover border border-border/60"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-base font-semibold">{{ selectedSkill.name }}</div>
|
||||
<div class="text-xs text-muted-foreground mt-1">
|
||||
{{ selectedSkill.type === "passive" ? "被动" : "主动" }} · Lv
|
||||
{{ selectedSkill.learnLv }} 解锁
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Level control -->
|
||||
<div>
|
||||
<div class="text-base font-semibold">{{ selectedSkill.name }}</div>
|
||||
<div class="text-xs text-muted-foreground mt-1">
|
||||
{{ selectedSkill.type === "passive" ? "被动" : "主动" }} ·
|
||||
Lv {{ selectedSkill.learnLv }} 解锁
|
||||
<div class="flex items-baseline gap-2 mb-2.5">
|
||||
<span
|
||||
class="text-4xl font-bold tabular-nums"
|
||||
:style="{ color: lvColor(selectedSkill.id.toString(), selectedSkill.maxLearnLv) }"
|
||||
>{{ skillCfg(selectedSkill.id.toString()).lv }}</span
|
||||
>
|
||||
<span class="text-xs text-muted-foreground">/ 满 {{ selectedSkill.maxLearnLv }}</span>
|
||||
</div>
|
||||
<div class="flex gap-1.5">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-7 px-2.5 text-xs"
|
||||
@click="resetLv(selectedSkill.id.toString())"
|
||||
>归零</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-7 px-2.5 text-xs"
|
||||
@click="stepLv(selectedSkill.id.toString(), -1)"
|
||||
>−1</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-7 px-2.5 text-xs"
|
||||
@click="stepLv(selectedSkill.id.toString(), 1)"
|
||||
>+1</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-7 px-2.5 text-xs"
|
||||
@click="setMaxLv(selectedSkill.id.toString())"
|
||||
>满级</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VP -->
|
||||
<div v-if="selectedSkill.hasVP">
|
||||
<div class="text-sm font-medium mb-2">VP 进化形态</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="mode in [1, 2] as const"
|
||||
:key="mode"
|
||||
class="flex-1 rounded-md border p-2.5 text-left text-xs transition-colors hover:bg-muted/30"
|
||||
:class="
|
||||
skillCfg(selectedSkill.id.toString()).vp === mode
|
||||
? 'border-purple-400 bg-purple-500/10'
|
||||
: 'border-border/50'
|
||||
"
|
||||
@click="
|
||||
setVPMode(
|
||||
selectedSkill.id.toString(),
|
||||
skillCfg(selectedSkill.id.toString()).vp === mode ? 0 : mode,
|
||||
)
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="font-semibold"
|
||||
:class="
|
||||
skillCfg(selectedSkill.id.toString()).vp === mode
|
||||
? 'text-purple-400'
|
||||
: 'text-muted-foreground'
|
||||
"
|
||||
>
|
||||
形态 {{ mode }}
|
||||
</div>
|
||||
<div class="mt-1.5 text-muted-foreground leading-snug">
|
||||
{{ selectedSkill.vps[mode - 1]?.name || "—" }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UP -->
|
||||
<div v-if="selectedSkill.hasUP">
|
||||
<div class="text-sm font-medium mb-1">UP 突破状态</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
{{
|
||||
skillCfg(selectedSkill.id.toString()).up === 0
|
||||
? "未配置"
|
||||
: `已配置 形态 ${skillCfg(selectedSkill.id.toString()).up}`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Level control -->
|
||||
<div>
|
||||
<div class="flex items-baseline gap-2 mb-2.5">
|
||||
<span
|
||||
class="text-4xl font-bold tabular-nums"
|
||||
:style="{ color: lvColor(selectedSkill.id.toString(), selectedSkill.maxLearnLv) }"
|
||||
>{{ skillCfg(selectedSkill.id.toString()).lv }}</span>
|
||||
<span class="text-xs text-muted-foreground">/ 满 {{ selectedSkill.maxLearnLv }}</span>
|
||||
</div>
|
||||
<div class="flex gap-1.5">
|
||||
<Button size="sm" variant="outline" class="h-7 px-2.5 text-xs" @click="resetLv(selectedSkill.id.toString())">归零</Button>
|
||||
<Button size="sm" variant="outline" class="h-7 px-2.5 text-xs" @click="stepLv(selectedSkill.id.toString(), -1)">−1</Button>
|
||||
<Button size="sm" variant="outline" class="h-7 px-2.5 text-xs" @click="stepLv(selectedSkill.id.toString(), 1)">+1</Button>
|
||||
<Button size="sm" variant="outline" class="h-7 px-2.5 text-xs" @click="setMaxLv(selectedSkill.id.toString())">满级</Button>
|
||||
<!-- Empty state -->
|
||||
<div v-else class="flex-1 flex flex-col items-center justify-center gap-4 p-6 text-center">
|
||||
<div class="text-sm text-muted-foreground">点击左侧技能查看详情</div>
|
||||
<div class="text-xs text-muted-foreground space-y-1">
|
||||
<div>VP 已用: {{ usedVPCount }} / {{ VP_LIMIT }}</div>
|
||||
<div>UP 已用: {{ usedUPCount }} / {{ UP_LIMIT }}</div>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" @click="resetAllSkills">重置全部技能</Button>
|
||||
</div>
|
||||
|
||||
<!-- VP -->
|
||||
<div v-if="selectedSkill.hasVP">
|
||||
<div class="text-sm font-medium mb-2">VP 进化形态</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="mode in [1, 2] as const"
|
||||
:key="mode"
|
||||
class="flex-1 rounded-md border p-2.5 text-left text-xs transition-colors hover:bg-muted/30"
|
||||
:class="
|
||||
skillCfg(selectedSkill.id.toString()).vp === mode
|
||||
? 'border-purple-400 bg-purple-500/10'
|
||||
: 'border-border/50'
|
||||
"
|
||||
@click="setVPMode(selectedSkill.id.toString(), skillCfg(selectedSkill.id.toString()).vp === mode ? 0 : mode)"
|
||||
>
|
||||
<div
|
||||
class="font-semibold"
|
||||
:class="skillCfg(selectedSkill.id.toString()).vp === mode ? 'text-purple-400' : 'text-muted-foreground'"
|
||||
>形态 {{ mode }}</div>
|
||||
<div class="mt-1.5 text-muted-foreground leading-snug">
|
||||
{{ selectedSkill.vps[mode - 1]?.name || "—" }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UP -->
|
||||
<div v-if="selectedSkill.hasUP">
|
||||
<div class="text-sm font-medium mb-1">UP 突破状态</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
{{
|
||||
skillCfg(selectedSkill.id.toString()).up === 0
|
||||
? "未配置"
|
||||
: `已配置 形态 ${skillCfg(selectedSkill.id.toString()).up}`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else class="flex-1 flex flex-col items-center justify-center gap-4 p-6 text-center">
|
||||
<div class="text-sm text-muted-foreground">点击左侧技能查看详情</div>
|
||||
<div class="text-xs text-muted-foreground space-y-1">
|
||||
<div>VP 已用: {{ usedVPCount }} / {{ VP_LIMIT }}</div>
|
||||
<div>UP 已用: {{ usedUPCount }} / {{ UP_LIMIT }}</div>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" @click="resetAllSkills">重置全部技能</Button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -635,8 +793,12 @@ const resetAllSkills = () => {
|
||||
line-height: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.badge-vp { color: #c084fc; } /* purple-400 */
|
||||
.badge-up { color: #fb923c; } /* amber-400 */
|
||||
.badge-vp {
|
||||
color: #c084fc;
|
||||
} /* purple-400 */
|
||||
.badge-up {
|
||||
color: #fb923c;
|
||||
} /* amber-400 */
|
||||
|
||||
/* 28×28px icon at y=14px */
|
||||
.skill-icon {
|
||||
|
||||
Reference in New Issue
Block a user