1
This commit is contained in:
@@ -188,8 +188,18 @@ const selectOathSkill = (skill: { skillId: string; enable: boolean }) => {
|
||||
|
||||
/* ─── Equipped items list ─── */
|
||||
const partOrder = [
|
||||
"武器", "上衣", "下装", "头肩", "腰带", "鞋",
|
||||
"项链", "手镯", "戒指", "辅助装备", "魔法石", "耳环",
|
||||
"武器",
|
||||
"上衣",
|
||||
"下装",
|
||||
"头肩",
|
||||
"腰带",
|
||||
"鞋",
|
||||
"项链",
|
||||
"手镯",
|
||||
"戒指",
|
||||
"辅助装备",
|
||||
"魔法石",
|
||||
"耳环",
|
||||
];
|
||||
|
||||
const equById = (id: string) => infoStore.equips.find((item) => item.id === id);
|
||||
@@ -201,16 +211,25 @@ const adaptation = (part: string) => {
|
||||
|
||||
const rarityColor = (rarity: string): string => {
|
||||
switch (rarity) {
|
||||
case "太初": return "text-[#5aff62]";
|
||||
case "太初":
|
||||
return "text-[#5aff62]";
|
||||
case "史诗":
|
||||
case "智慧产物": return "text-[#fcb600]";
|
||||
case "神话": return "text-[#f8c63d]";
|
||||
case "神器": return "text-[#ef28c1]";
|
||||
case "稀有": return "text-[#9879da]";
|
||||
case "传说": return "text-[#ff7700]";
|
||||
case "勇者": return "text-[#ff6666]";
|
||||
case "高级": return "text-[#68d5ed]";
|
||||
default: return "text-[#fcb600]";
|
||||
case "智慧产物":
|
||||
return "text-[#fcb600]";
|
||||
case "神话":
|
||||
return "text-[#f8c63d]";
|
||||
case "神器":
|
||||
return "text-[#ef28c1]";
|
||||
case "稀有":
|
||||
return "text-[#9879da]";
|
||||
case "传说":
|
||||
return "text-[#ff7700]";
|
||||
case "勇者":
|
||||
return "text-[#ff6666]";
|
||||
case "高级":
|
||||
return "text-[#68d5ed]";
|
||||
default:
|
||||
return "text-[#fcb600]";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -300,10 +319,7 @@ const clearChangeSources = () => configStore.clearChangeSource();
|
||||
<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"
|
||||
>
|
||||
<div v-if="oathSkillsSorted.length" class="flex items-center justify-center gap-4 pt-1">
|
||||
<button
|
||||
v-for="skill in oathSkillsSorted"
|
||||
:key="skill.id"
|
||||
@@ -370,7 +386,7 @@ const clearChangeSources = () => configStore.clearChangeSource();
|
||||
:class="
|
||||
rarityColor(equById(configStore.config.equips[part]?.id ?? '')?.rarity ?? '')
|
||||
"
|
||||
>{{ equById(configStore.config.equips[part]?.id ?? '')?.name ?? "—" }}</span
|
||||
>{{ equById(configStore.config.equips[part]?.id ?? "")?.name ?? "—" }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,7 +115,7 @@ const hasVisibleResult = computed(
|
||||
<EquList
|
||||
v-model:part="equippedPart"
|
||||
:detail="configStore.config.equips"
|
||||
:with-sub-weapon="(infoStore.infos?.alter?.includes('vegabond') ?? false)"
|
||||
:with-sub-weapon="infoStore.infos?.alter?.includes('vegabond') ?? false"
|
||||
:with-pet="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -25,9 +25,7 @@ const basic = computed(() => {
|
||||
},
|
||||
{
|
||||
label: "施放时间",
|
||||
value: !info.attribute.castingTime
|
||||
? "瞬发"
|
||||
: `${info.attribute.castingTime.toFixed(1)}秒`,
|
||||
value: !info.attribute.castingTime ? "瞬发" : `${info.attribute.castingTime.toFixed(1)}秒`,
|
||||
},
|
||||
{ label: "魔法值", value: info.attribute.consumeMp },
|
||||
{
|
||||
@@ -40,8 +38,7 @@ const basic = computed(() => {
|
||||
const descLines = computed(() => skillInfo.value?.descDetail.split("\n") ?? []);
|
||||
|
||||
const detailLines = computed(() => {
|
||||
const items =
|
||||
skillInfo.value?.attribute.optionDesc?.split("\n").filter((item) => !!item) ?? [];
|
||||
const items = skillInfo.value?.attribute.optionDesc?.split("\n").filter((item) => !!item) ?? [];
|
||||
return items.map((a) => {
|
||||
// highlight value placeholder prefix
|
||||
const beforeReplace = a.match(/^(.+?)\s*(\{value\d+\}.*)$/);
|
||||
@@ -53,8 +50,7 @@ const detailLines = computed(() => {
|
||||
matches.forEach((match) => {
|
||||
const key = match.replace(/[{}]/g, "");
|
||||
if (/^value.+/.test(key)) {
|
||||
const value =
|
||||
skillInfo.value?.attribute.optionValue[key as `value${string}`] ?? "-";
|
||||
const value = skillInfo.value?.attribute.optionValue[key as `value${string}`] ?? "-";
|
||||
a = a.replace(
|
||||
match,
|
||||
`<span style="padding:0 2px;color:var(--color-skill-value,#4AA256)">${value}</span>`,
|
||||
@@ -70,7 +66,10 @@ const hasEvolution = computed(() => (skillInfo.value?.evolution?.length ?? 0) >
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!skillInfo" class="flex items-center justify-center h-16 text-xs text-muted-foreground">
|
||||
<div
|
||||
v-if="!skillInfo"
|
||||
class="flex items-center justify-center h-16 text-xs text-muted-foreground"
|
||||
>
|
||||
加载中…
|
||||
</div>
|
||||
|
||||
@@ -139,7 +138,9 @@ const hasEvolution = computed(() => (skillInfo.value?.evolution?.length ?? 0) >
|
||||
<div class="text-xs font-semibold text-purple-400 mb-1.5">{{ evo.name }}</div>
|
||||
<div
|
||||
class="text-xs leading-relaxed text-muted-foreground whitespace-pre-wrap max-h-36 overflow-y-auto"
|
||||
>{{ evo.descDetail }}</div>
|
||||
>
|
||||
{{ evo.descDetail }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -259,373 +259,389 @@ const resetAllSkills = () => {
|
||||
]"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center transition-colors border-b-2"
|
||||
:class="
|
||||
tab === 0
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
"
|
||||
@click="tab = 0"
|
||||
<!-- Tab bar -->
|
||||
<div
|
||||
class="h-10 shrink-0 flex border-b border-border/50 bg-muted/20 text-[15px] font-bold select-none"
|
||||
>
|
||||
技能加点
|
||||
</button>
|
||||
<div class="w-px bg-border/40 my-2" />
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center transition-colors border-b-2"
|
||||
:class="
|
||||
tab === 1
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
"
|
||||
@click="tab = 1"
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center transition-colors border-b-2"
|
||||
:class="
|
||||
tab === 0
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
"
|
||||
@click="tab = 0"
|
||||
>
|
||||
技能加点
|
||||
</button>
|
||||
<div class="w-px bg-border/40 my-2" />
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center transition-colors border-b-2"
|
||||
:class="
|
||||
tab === 1
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
"
|
||||
@click="tab = 1"
|
||||
>
|
||||
技能定制
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── TAB 0: Skill Tree ── -->
|
||||
<div
|
||||
v-show="tab === 0"
|
||||
class="flex-1 overflow-y-auto overflow-x-auto"
|
||||
@click.self="clearActive"
|
||||
>
|
||||
技能定制
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── TAB 0: Skill Tree ── -->
|
||||
<div
|
||||
v-show="tab === 0"
|
||||
class="flex-1 overflow-y-auto overflow-x-auto"
|
||||
@click.self="clearActive"
|
||||
>
|
||||
<div class="min-w-max py-1" @click.self="clearActive">
|
||||
<template v-for="row in treeRows" :key="row.lv">
|
||||
<div class="flex items-center px-1 gap-[2px] mb-px">
|
||||
<!-- Level label: 34×34 (matches original) -->
|
||||
<div
|
||||
class="w-[34px] h-[34px] shrink-0 flex items-center justify-center text-[11px] text-muted-foreground leading-[1.2] text-center"
|
||||
>
|
||||
<template v-if="row.isExtra">其<br />余</template>
|
||||
<template v-else>{{ row.label }}</template>
|
||||
</div>
|
||||
|
||||
<!-- "其余": sequential list -->
|
||||
<template v-if="row.isExtra">
|
||||
<div class="min-w-max py-1" @click.self="clearActive">
|
||||
<template v-for="row in treeRows" :key="row.lv">
|
||||
<div class="flex items-center px-1 gap-[2px] mb-px">
|
||||
<!-- Level label: 34×34 (matches original) -->
|
||||
<div
|
||||
v-for="skill in row.extraSkills"
|
||||
:key="skill.id"
|
||||
class="skill-cell"
|
||||
:title="skill.name"
|
||||
@click.stop="chooseSkill(skill.id.toString())"
|
||||
class="w-[34px] h-[34px] shrink-0 flex items-center justify-center text-[11px] text-muted-foreground leading-[1.2] text-center"
|
||||
>
|
||||
<div class="badge-row">
|
||||
<img
|
||||
v-if="skillCfg(skill.id.toString()).vp > 0"
|
||||
:src="vpImg(skillCfg(skill.id.toString()).vp)"
|
||||
class="badge-img"
|
||||
/>
|
||||
<img
|
||||
v-if="skillCfg(skill.id.toString()).up > 0"
|
||||
:src="upImg(skillCfg(skill.id.toString()).up)"
|
||||
class="badge-img"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
:src="getImageURL(skill.icon)"
|
||||
:alt="skill.name"
|
||||
class="skill-icon"
|
||||
:class="[
|
||||
skill.type === 'passive' ? 'opacity-60' : '',
|
||||
activeSkillId === skill.id.toString()
|
||||
? 'ring-2 ring-primary ring-offset-1 ring-offset-background'
|
||||
: '',
|
||||
]"
|
||||
/>
|
||||
<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="setMaxLv(skill.id.toString())">»</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="lv-num"
|
||||
:style="{ color: lvColor(skill.id.toString(), skill.maxLearnLv) }"
|
||||
>
|
||||
{{ skillCfg(skill.id.toString()).lv }}
|
||||
</div>
|
||||
<template v-if="row.isExtra">其<br />余</template>
|
||||
<template v-else>{{ row.label }}</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Normal row: 11 fixed columns -->
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(cell, colIdx) in row.cells"
|
||||
:key="colIdx"
|
||||
class="skill-cell"
|
||||
:class="cell ? 'cursor-pointer' : 'pointer-events-none'"
|
||||
:title="cell?.name"
|
||||
@click.stop="cell && chooseSkill(cell.id.toString())"
|
||||
>
|
||||
<template v-if="cell">
|
||||
<!-- "其余": sequential list -->
|
||||
<template v-if="row.isExtra">
|
||||
<div
|
||||
v-for="skill in row.extraSkills"
|
||||
:key="skill.id"
|
||||
class="skill-cell"
|
||||
:title="skill.name"
|
||||
@click.stop="chooseSkill(skill.id.toString())"
|
||||
>
|
||||
<div class="badge-row">
|
||||
<img
|
||||
v-if="skillCfg(cell.id.toString()).vp > 0"
|
||||
:src="vpImg(skillCfg(cell.id.toString()).vp)"
|
||||
v-if="skillCfg(skill.id.toString()).vp > 0"
|
||||
:src="vpImg(skillCfg(skill.id.toString()).vp)"
|
||||
class="badge-img"
|
||||
/>
|
||||
<img
|
||||
v-if="skillCfg(cell.id.toString()).up > 0"
|
||||
:src="upImg(skillCfg(cell.id.toString()).up)"
|
||||
v-if="skillCfg(skill.id.toString()).up > 0"
|
||||
:src="upImg(skillCfg(skill.id.toString()).up)"
|
||||
class="badge-img"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
:src="getImageURL(cell.icon)"
|
||||
:alt="cell.name"
|
||||
:src="getImageURL(skill.icon)"
|
||||
:alt="skill.name"
|
||||
class="skill-icon"
|
||||
:class="[
|
||||
cell.type === 'passive' ? 'opacity-60' : '',
|
||||
activeSkillId === cell.id.toString()
|
||||
skill.type === 'passive' ? 'opacity-60' : '',
|
||||
activeSkillId === skill.id.toString()
|
||||
? 'ring-2 ring-primary ring-offset-1 ring-offset-background'
|
||||
: '',
|
||||
]"
|
||||
/>
|
||||
<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)">
|
||||
<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(cell.id.toString(), 1)">
|
||||
<button class="skill-btn" @click.stop="stepLv(skill.id.toString(), 1)">
|
||||
›
|
||||
</button>
|
||||
<button class="skill-btn" @click.stop="setMaxLv(cell.id.toString())">
|
||||
<button class="skill-btn" @click.stop="setMaxLv(skill.id.toString())">
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="lv-num"
|
||||
:style="{ color: lvColor(cell.id.toString(), cell.maxLearnLv) }"
|
||||
:style="{ color: lvColor(skill.id.toString(), skill.maxLearnLv) }"
|
||||
>
|
||||
{{ skillCfg(cell.id.toString()).lv }}
|
||||
{{ skillCfg(skill.id.toString()).lv }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── TAB 1: Customization ── -->
|
||||
<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"
|
||||
>
|
||||
<span>技能突破 (UP)</span>
|
||||
<span>{{ usedUPCount }}/{{ UP_LIMIT }}</span>
|
||||
</div>
|
||||
<div class="px-3 pb-3 space-y-2">
|
||||
<div
|
||||
v-for="(slot, idx) in upSlots"
|
||||
: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>
|
||||
|
||||
<!-- Mode 1 icon -->
|
||||
<button
|
||||
class="relative w-8 h-8 rounded border flex items-center justify-center shrink-0 transition-colors"
|
||||
:class="
|
||||
slot.skillId && slot.mode === 1
|
||||
? 'border-purple-400 ring-1 ring-purple-400/50 bg-purple-500/10'
|
||||
: 'border-border/50 hover:border-border'
|
||||
"
|
||||
@click="
|
||||
slot.skillId
|
||||
? setUPSlotMode(idx, 1)
|
||||
: (openUpPickerSlot = openUpPickerSlot === idx ? null : idx)
|
||||
"
|
||||
>
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else class="text-[10px] text-muted-foreground">1</span>
|
||||
</button>
|
||||
|
||||
<!-- Exchange -->
|
||||
<button
|
||||
class="text-muted-foreground hover:text-foreground shrink-0 text-sm"
|
||||
:disabled="!slot.skillId"
|
||||
title="切换形态"
|
||||
@click="exchangeUPMode(idx)"
|
||||
>
|
||||
⇄
|
||||
</button>
|
||||
|
||||
<!-- Mode 2 icon -->
|
||||
<button
|
||||
class="relative w-8 h-8 rounded border flex items-center justify-center shrink-0 transition-colors"
|
||||
:class="
|
||||
slot.skillId && slot.mode === 2
|
||||
? 'border-purple-400 ring-1 ring-purple-400/50 bg-purple-500/10'
|
||||
: 'border-border/50 hover:border-border'
|
||||
"
|
||||
@click="
|
||||
slot.skillId
|
||||
? setUPSlotMode(idx, 2)
|
||||
: (openUpPickerSlot = openUpPickerSlot === idx ? null : idx)
|
||||
"
|
||||
>
|
||||
<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
|
||||
<!-- Normal row: 11 fixed columns -->
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(cell, colIdx) in row.cells"
|
||||
:key="colIdx"
|
||||
class="skill-cell"
|
||||
:class="cell ? 'cursor-pointer' : 'pointer-events-none'"
|
||||
:title="cell?.name"
|
||||
@click.stop="cell && chooseSkill(cell.id.toString())"
|
||||
>
|
||||
<template v-if="cell">
|
||||
<div class="badge-row">
|
||||
<img
|
||||
v-if="skillCfg(cell.id.toString()).vp > 0"
|
||||
:src="vpImg(skillCfg(cell.id.toString()).vp)"
|
||||
class="badge-img"
|
||||
/>
|
||||
<img
|
||||
v-if="skillCfg(cell.id.toString()).up > 0"
|
||||
:src="upImg(skillCfg(cell.id.toString()).up)"
|
||||
class="badge-img"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
:src="getImageURL(cell.icon)"
|
||||
:alt="cell.name"
|
||||
class="skill-icon"
|
||||
:class="[
|
||||
cell.type === 'passive' ? 'opacity-60' : '',
|
||||
activeSkillId === cell.id.toString()
|
||||
? 'ring-2 ring-primary ring-offset-1 ring-offset-background'
|
||||
: '',
|
||||
]"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="lv-num"
|
||||
:style="{ color: lvColor(cell.id.toString(), cell.maxLearnLv) }"
|
||||
>
|
||||
{{ skillCfg(cell.id.toString()).lv }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</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 ?? '') : ''"
|
||||
>
|
||||
{{ slot.skillId ? skillById(slot.skillId)?.name : "未选择技能" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pick button -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Icon picker -->
|
||||
<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
|
||||
v-for="item in upOptionsForSlot(openUpPickerSlot!)"
|
||||
:key="item.id"
|
||||
class="relative w-7 h-7 rounded border transition-all hover:ring-1 ring-primary"
|
||||
:class="
|
||||
upSlots[openUpPickerSlot!]?.skillId === item.id.toString()
|
||||
? 'border-primary ring-1'
|
||||
: 'border-border/60'
|
||||
"
|
||||
: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"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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"
|
||||
>
|
||||
<span>技能进化 (VP)</span>
|
||||
<span>{{ usedVPCount }}/{{ VP_LIMIT }}</span>
|
||||
</div>
|
||||
<div class="divide-y divide-border/30">
|
||||
<!-- ── TAB 1: Customization ── -->
|
||||
<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
|
||||
v-for="skill in vpSkills"
|
||||
:key="skill.id"
|
||||
class="px-3 py-2.5 flex items-start gap-2"
|
||||
class="px-3 pt-3 pb-1 text-xs font-semibold text-muted-foreground flex justify-between"
|
||||
>
|
||||
<!-- 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"
|
||||
/>
|
||||
</div>
|
||||
<!-- Name -->
|
||||
<span>技能突破 (UP)</span>
|
||||
<span>{{ usedUPCount }}/{{ UP_LIMIT }}</span>
|
||||
</div>
|
||||
<div class="px-3 pb-3 space-y-2">
|
||||
<div
|
||||
class="shrink-0 w-[4.5rem] text-[11px] text-muted-foreground truncate pt-1.5"
|
||||
:title="skill.name"
|
||||
v-for="(slot, idx) in upSlots"
|
||||
:key="idx"
|
||||
class="flex items-center gap-2 rounded-md border border-border/50 bg-muted/10 p-2"
|
||||
>
|
||||
{{ skill.name }}
|
||||
</div>
|
||||
<!-- Two VP choice cards -->
|
||||
<div class="flex flex-1 gap-1.5">
|
||||
<div class="text-[10px] text-muted-foreground w-6 shrink-0 text-center">
|
||||
{{ idx + 1 }}
|
||||
</div>
|
||||
|
||||
<!-- Mode 1 icon -->
|
||||
<button
|
||||
v-for="mode in [1, 2] as const"
|
||||
:key="mode"
|
||||
class="flex-1 rounded border p-1.5 text-left transition-colors hover:bg-muted/30"
|
||||
class="relative w-8 h-8 rounded border flex items-center justify-center shrink-0 transition-colors"
|
||||
:class="
|
||||
skillCfg(skill.id.toString()).vp === mode
|
||||
? 'border-purple-400 bg-purple-500/10'
|
||||
: 'border-border/50'
|
||||
slot.skillId && slot.mode === 1
|
||||
? 'border-purple-400 ring-1 ring-purple-400/50 bg-purple-500/10'
|
||||
: 'border-border/50 hover:border-border'
|
||||
"
|
||||
@click="
|
||||
setVPMode(
|
||||
skill.id.toString(),
|
||||
skillCfg(skill.id.toString()).vp === mode ? 0 : mode,
|
||||
)
|
||||
slot.skillId
|
||||
? setUPSlotMode(idx, 1)
|
||||
: (openUpPickerSlot = openUpPickerSlot === idx ? null : idx)
|
||||
"
|
||||
>
|
||||
<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
|
||||
>
|
||||
</template>
|
||||
<span v-else class="text-[10px] text-muted-foreground">1</span>
|
||||
</button>
|
||||
|
||||
<!-- Exchange -->
|
||||
<button
|
||||
class="text-muted-foreground hover:text-foreground shrink-0 text-sm"
|
||||
:disabled="!slot.skillId"
|
||||
title="切换形态"
|
||||
@click="exchangeUPMode(idx)"
|
||||
>
|
||||
⇄
|
||||
</button>
|
||||
|
||||
<!-- Mode 2 icon -->
|
||||
<button
|
||||
class="relative w-8 h-8 rounded border flex items-center justify-center shrink-0 transition-colors"
|
||||
:class="
|
||||
slot.skillId && slot.mode === 2
|
||||
? 'border-purple-400 ring-1 ring-purple-400/50 bg-purple-500/10'
|
||||
: 'border-border/50 hover:border-border'
|
||||
"
|
||||
@click="
|
||||
slot.skillId
|
||||
? setUPSlotMode(idx, 2)
|
||||
: (openUpPickerSlot = openUpPickerSlot === idx ? null : idx)
|
||||
"
|
||||
>
|
||||
<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
|
||||
>
|
||||
</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-[10px] font-semibold"
|
||||
class="text-xs truncate"
|
||||
:title="slot.skillId ? (skillById(slot.skillId)?.name ?? '') : ''"
|
||||
>
|
||||
{{ slot.skillId ? skillById(slot.skillId)?.name : "未选择技能" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pick button -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Icon picker -->
|
||||
<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
|
||||
v-for="item in upOptionsForSlot(openUpPickerSlot!)"
|
||||
:key="item.id"
|
||||
class="relative w-7 h-7 rounded border transition-all hover:ring-1 ring-primary"
|
||||
:class="
|
||||
upSlots[openUpPickerSlot!]?.skillId === item.id.toString()
|
||||
? 'border-primary ring-1'
|
||||
: 'border-border/60'
|
||||
"
|
||||
: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"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<!-- 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"
|
||||
/>
|
||||
</div>
|
||||
<!-- 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 -->
|
||||
<div class="flex flex-1 gap-1.5">
|
||||
<button
|
||||
v-for="mode in [1, 2] as const"
|
||||
:key="mode"
|
||||
class="flex-1 rounded border p-1.5 text-left transition-colors hover:bg-muted/30"
|
||||
:class="
|
||||
skillCfg(skill.id.toString()).vp === mode
|
||||
? 'text-purple-400'
|
||||
: 'text-muted-foreground'
|
||||
? 'border-purple-400 bg-purple-500/10'
|
||||
: 'border-border/50'
|
||||
"
|
||||
@click="
|
||||
setVPMode(
|
||||
skill.id.toString(),
|
||||
skillCfg(skill.id.toString()).vp === mode ? 0 : mode,
|
||||
)
|
||||
"
|
||||
>
|
||||
形态 {{ mode }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-[10px] text-muted-foreground leading-tight line-clamp-2">
|
||||
{{ skill.vps[mode - 1]?.name || "—" }}
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="text-[10px] font-semibold"
|
||||
: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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ RIGHT PANEL: Skill Detail ══ -->
|
||||
|
||||
Reference in New Issue
Block a user