dev
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import type { IEquipment } from "@/api/info/type";
|
||||||
|
import { getImageURL } from "@/utils";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
equipment?: IEquipment;
|
||||||
|
inactive?: boolean;
|
||||||
|
useDisplayUrl?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const rarity = computed(() => {
|
||||||
|
switch (props.equipment?.rarity) {
|
||||||
|
case "稀有":
|
||||||
|
return "rare";
|
||||||
|
case "神器":
|
||||||
|
return "artifact";
|
||||||
|
case "传说":
|
||||||
|
return "legend";
|
||||||
|
case "史诗":
|
||||||
|
return "epic";
|
||||||
|
case "太初":
|
||||||
|
return "primeval";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imagePath = computed(() => {
|
||||||
|
if (!props.equipment) return "";
|
||||||
|
const path = props.useDisplayUrl ? props.equipment.displayUrl : props.equipment.imageUrl;
|
||||||
|
return path ? getImageURL(path) : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRarityFrame = (value: string) => {
|
||||||
|
if (!value) return "";
|
||||||
|
return new URL(`./rarity/${value}.png`, import.meta.url).href;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-if="equipment">
|
||||||
|
<div class="legacy-icon" :class="{ inactive }">
|
||||||
|
<div class="legacy-icon-core" :style="{ backgroundImage: `url(${imagePath})` }">
|
||||||
|
<img v-if="rarity" :src="getRarityFrame(rarity)" :alt="rarity" class="legacy-icon-frame" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="legacy-icon-empty" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.legacy-icon {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legacy-icon-core {
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
margin-left: -1px;
|
||||||
|
margin-top: -1px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legacy-icon-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legacy-icon-empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid rgba(122, 122, 122, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.56);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 300 B |
Binary file not shown.
|
After Width: | Height: | Size: 304 B |
Binary file not shown.
|
After Width: | Height: | Size: 293 B |
Binary file not shown.
|
After Width: | Height: | Size: 274 B |
Binary file not shown.
|
After Width: | Height: | Size: 316 B |
@@ -0,0 +1,187 @@
|
|||||||
|
<script setup lang="ts" name="EquList">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useInfoStore, type IConfigEquip } from "@/stores";
|
||||||
|
import { getImageURL } from "@/utils";
|
||||||
|
import LegacyEquipmentIcon from "@/components/dnf/Equipment/Icon/index.vue";
|
||||||
|
|
||||||
|
const baseWidth = 264;
|
||||||
|
const baseHeight = 172;
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
withBg?: boolean;
|
||||||
|
withPet?: boolean;
|
||||||
|
detail?: Record<string, IConfigEquip>;
|
||||||
|
withSubWeapon?: boolean;
|
||||||
|
avatar?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
withBg: true,
|
||||||
|
withPet: true,
|
||||||
|
withSubWeapon: false,
|
||||||
|
avatar: "",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const partModelValue = defineModel<string>("part", { default: "" });
|
||||||
|
const infoStore = useInfoStore();
|
||||||
|
|
||||||
|
const parts = computed(() => {
|
||||||
|
return infoStore.parts.filter((part) => {
|
||||||
|
if (part === "宠物" && !props.withPet) return false;
|
||||||
|
return !!props.detail?.[part];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const equ = (id: string) => infoStore.equips.find((item) => item.id === id);
|
||||||
|
|
||||||
|
const infoStyle = (part: string) => {
|
||||||
|
let x = 10;
|
||||||
|
let y = 10;
|
||||||
|
let index = infoStore.parts.findIndex((current) => current === part);
|
||||||
|
|
||||||
|
if (index === 13) {
|
||||||
|
index -= 7;
|
||||||
|
} else if (index >= 5 && index <= 12) {
|
||||||
|
x += 179;
|
||||||
|
index -= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
x += (index % 2) * 39;
|
||||||
|
y += Math.floor(index / 2) * 34;
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: `${(x / baseWidth) * 100}%`,
|
||||||
|
top: `${(y / baseHeight) * 100}%`,
|
||||||
|
zIndex: 6,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const choosePart = (part: string) => {
|
||||||
|
partModelValue.value = part;
|
||||||
|
};
|
||||||
|
|
||||||
|
const partName = (part: string) => {
|
||||||
|
if (part === "辅助装备") return "左槽";
|
||||||
|
if (part === "魔法石") return "右槽";
|
||||||
|
return part;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="equ-list"
|
||||||
|
:class="{ 'equ-list--plain': !withBg }"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: withBg ? `url(${getImageURL('/common/head.png')})` : 'none',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template v-for="part in parts" :key="part">
|
||||||
|
<button class="equ-slot" :style="infoStyle(part)" type="button" @click="choosePart(part)">
|
||||||
|
<template v-if="detail?.[part]?.id">
|
||||||
|
<LegacyEquipmentIcon :equipment="equ(detail[part].id)" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="equ-slot-text">{{ partName(part) }}</span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="withSubWeapon"
|
||||||
|
class="equ-slot"
|
||||||
|
:style="{ left: `${(150 / baseWidth) * 100}%`, top: `${(5 / baseHeight) * 100}%`, zIndex: 8 }"
|
||||||
|
type="button"
|
||||||
|
@click="choosePart('副武器')"
|
||||||
|
>
|
||||||
|
<template v-if="detail?.['副武器']?.id">
|
||||||
|
<LegacyEquipmentIcon :equipment="equ(detail['副武器'].id)" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="equ-slot-text">副武</span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<img v-if="avatar" :src="avatar" alt="Avatar" class="equ-avatar" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.equ-list {
|
||||||
|
position: relative;
|
||||||
|
width: min(100%, 264px);
|
||||||
|
aspect-ratio: 264 / 172;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 1px solid rgba(85, 72, 46, 0.55);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(24, 20, 14, 0.8),
|
||||||
|
0 8px 20px rgba(0, 0, 0, 0.32);
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-list::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 8%, rgba(88, 161, 255, 0.24), transparent 35%),
|
||||||
|
linear-gradient(180deg, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.28));
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-list--plain {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(19, 21, 24, 0.96), rgba(10, 10, 12, 0.98)),
|
||||||
|
radial-gradient(circle at 50% 0, rgba(55, 95, 138, 0.24), transparent 45%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-slot {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 9.848%;
|
||||||
|
height: 15.116%;
|
||||||
|
border: 1px solid rgba(122, 122, 122, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgb(255 255 255 / 0.5);
|
||||||
|
background: rgba(0, 0, 0, 0.16);
|
||||||
|
padding: 0;
|
||||||
|
transition:
|
||||||
|
border-color 0.18s ease,
|
||||||
|
box-shadow 0.18s ease,
|
||||||
|
background-color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-slot:hover {
|
||||||
|
border-color: rgba(229, 196, 113, 0.72);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(229, 196, 113, 0.38),
|
||||||
|
inset 0 0 8px rgba(255, 210, 122, 0.22);
|
||||||
|
background: rgba(47, 35, 14, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-slot:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(235, 211, 141, 0.88);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(235, 211, 141, 0.35),
|
||||||
|
inset 0 0 8px rgba(255, 210, 122, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-slot-text {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equ-avatar {
|
||||||
|
position: absolute;
|
||||||
|
width: 70%;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -5.814%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
<script setup lang="ts" name="OathList">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useInfoStore, type IConfigOath } from "@/stores";
|
||||||
|
import { getImageURL } from "@/utils";
|
||||||
|
import LegacyEquipmentIcon from "@/components/dnf/Equipment/Icon/index.vue";
|
||||||
|
|
||||||
|
const baseWidth = 264;
|
||||||
|
const baseHeight = 172;
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
withBg?: boolean;
|
||||||
|
detail?: Record<string, IConfigOath>;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
withBg: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const partModelValue = defineModel<string>("part", { default: "0" });
|
||||||
|
const infoStore = useInfoStore();
|
||||||
|
|
||||||
|
const parts = computed(() => infoStore.oathParts);
|
||||||
|
|
||||||
|
const equ = (id: string, part: string) =>
|
||||||
|
infoStore.oaths.find((item) => item.id === id && item.oathPos === part);
|
||||||
|
|
||||||
|
const position = [
|
||||||
|
{ x: 31, y: 13 },
|
||||||
|
{ x: 31, y: 46 },
|
||||||
|
{ x: 31, y: 79 },
|
||||||
|
{ x: 31, y: 112 },
|
||||||
|
{ x: 207, y: 13 },
|
||||||
|
{ x: 207, y: 46 },
|
||||||
|
{ x: 207, y: 79 },
|
||||||
|
{ x: 207, y: 112 },
|
||||||
|
{ x: 75, y: 128.5 },
|
||||||
|
{ x: 119, y: 141.5 },
|
||||||
|
{ x: 163, y: 128.5 },
|
||||||
|
{ x: 119, y: 109 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const positionSimple = [
|
||||||
|
{ x: 30, y: 12 },
|
||||||
|
{ x: 30, y: 46 },
|
||||||
|
{ x: 30, y: 80.5 },
|
||||||
|
{ x: 30, y: 114 },
|
||||||
|
{ x: 206, y: 12 },
|
||||||
|
{ x: 206, y: 46 },
|
||||||
|
{ x: 206, y: 80.5 },
|
||||||
|
{ x: 206, y: 114 },
|
||||||
|
{ x: 74, y: 114 },
|
||||||
|
{ x: 118, y: 114 },
|
||||||
|
{ x: 162, y: 114 },
|
||||||
|
{ x: 118, y: 80.5 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const infoStyle = (part: string) => {
|
||||||
|
const index = Number.parseInt(part, 10);
|
||||||
|
const target = props.withBg ? position[index] : positionSimple[index];
|
||||||
|
return {
|
||||||
|
left: `${((target?.x ?? 0) / baseWidth) * 100}%`,
|
||||||
|
top: `${((target?.y ?? 0) / baseHeight) * 100}%`,
|
||||||
|
zIndex: 6,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const choosePart = (part: string) => {
|
||||||
|
partModelValue.value = part;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="oath-list"
|
||||||
|
:class="{ 'oath-list--plain': !withBg }"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: withBg ? `url(${getImageURL('/common/oath/head.png')})` : 'none',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template v-for="part in parts" :key="part">
|
||||||
|
<button class="oath-slot" :style="infoStyle(part)" type="button" @click="choosePart(part)">
|
||||||
|
<template v-if="detail?.[part]?.id">
|
||||||
|
<LegacyEquipmentIcon :equipment="equ(detail[part].id, part)" use-display-url />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="!withBg">
|
||||||
|
<span class="oath-slot-text">{{ part !== "11" ? "星蕴" : "誓约" }}</span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.oath-list {
|
||||||
|
position: relative;
|
||||||
|
width: min(100%, 264px);
|
||||||
|
aspect-ratio: 264 / 172;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 1px solid rgba(85, 72, 46, 0.55);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(24, 20, 14, 0.8),
|
||||||
|
0 8px 20px rgba(0, 0, 0, 0.32);
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-list::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 8%, rgba(88, 161, 255, 0.24), transparent 35%),
|
||||||
|
linear-gradient(180deg, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.28));
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-list--plain {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(19, 21, 24, 0.96), rgba(10, 10, 12, 0.98)),
|
||||||
|
radial-gradient(circle at 50% 0, rgba(55, 95, 138, 0.24), transparent 45%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-slot {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 9.848%;
|
||||||
|
height: 15.116%;
|
||||||
|
border: 1px solid rgba(122, 122, 122, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgb(255 255 255 / 0.5);
|
||||||
|
background: rgba(0, 0, 0, 0.16);
|
||||||
|
padding: 0;
|
||||||
|
transition:
|
||||||
|
border-color 0.18s ease,
|
||||||
|
box-shadow 0.18s ease,
|
||||||
|
background-color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-slot:hover {
|
||||||
|
border-color: rgba(229, 196, 113, 0.72);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(229, 196, 113, 0.38),
|
||||||
|
inset 0 0 8px rgba(255, 210, 122, 0.22);
|
||||||
|
background: rgba(47, 35, 14, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-slot:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(235, 211, 141, 0.88);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(235, 211, 141, 0.35),
|
||||||
|
inset 0 0 8px rgba(255, 210, 122, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-slot-text {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Sparkles } from "lucide-vue-next";
|
import {
|
||||||
|
Check,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronsLeft,
|
||||||
|
ChevronsRight,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-vue-next";
|
||||||
import { useConfigStore, useInfoStore } from "@/stores";
|
import { useConfigStore, useInfoStore } from "@/stores";
|
||||||
import type { IEquipment, ISuit } from "@/api/info/type";
|
import type { IEquipment, ISuit } from "@/api/info/type";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -8,6 +15,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import Tabs from "@/components/ui/tabs/Tabs.vue";
|
import Tabs from "@/components/ui/tabs/Tabs.vue";
|
||||||
import TabsList from "@/components/ui/tabs/TabsList.vue";
|
import TabsList from "@/components/ui/tabs/TabsList.vue";
|
||||||
import TabsTrigger from "@/components/ui/tabs/TabsTrigger.vue";
|
import TabsTrigger from "@/components/ui/tabs/TabsTrigger.vue";
|
||||||
|
import {
|
||||||
|
ColorSwatchPickerItem,
|
||||||
|
ColorSwatchPickerItemIndicator,
|
||||||
|
ColorSwatchPickerItemSwatch,
|
||||||
|
ColorSwatchPickerRoot,
|
||||||
|
} from "reka-ui";
|
||||||
|
import EquList from "@/components/dnf/Equipment/List/index.vue";
|
||||||
|
import OathList from "@/components/dnf/Equipment/Oath/index.vue";
|
||||||
import EquipmentCell from "./components/EquipmentCell.vue";
|
import EquipmentCell from "./components/EquipmentCell.vue";
|
||||||
import { getImageURL } from "@/utils";
|
import { getImageURL } from "@/utils";
|
||||||
|
|
||||||
@@ -41,6 +56,8 @@ const oathSuitId = ref("");
|
|||||||
const suitRarity = ref<string>("");
|
const suitRarity = ref<string>("");
|
||||||
const oathSuitRarity = ref<string>("");
|
const oathSuitRarity = ref<string>("");
|
||||||
const sidePanelTab = ref<SidePanelTab>("detail");
|
const sidePanelTab = ref<SidePanelTab>("detail");
|
||||||
|
const equippedPart = ref("");
|
||||||
|
const equippedOathPart = ref("0");
|
||||||
const previewEquipKey = ref("");
|
const previewEquipKey = ref("");
|
||||||
const previewSuitLevel = ref<LevelState>({ rarity: "太初", lv: 1 });
|
const previewSuitLevel = ref<LevelState>({ rarity: "太初", lv: 1 });
|
||||||
const previewOathLevel = ref<LevelState>({ rarity: "史诗", lv: 1 });
|
const previewOathLevel = ref<LevelState>({ rarity: "史诗", lv: 1 });
|
||||||
@@ -50,10 +67,16 @@ const previewOathSuitInfo = ref<ISuit>();
|
|||||||
const canUseSubWeapon = computed(() => infoStore.infos?.alter?.includes("vegabond") ?? false);
|
const canUseSubWeapon = computed(() => infoStore.infos?.alter?.includes("vegabond") ?? false);
|
||||||
const equips = computed(() => infoStore.equips ?? []);
|
const equips = computed(() => infoStore.equips ?? []);
|
||||||
const oathEquips = computed(() => infoStore.oaths ?? []);
|
const oathEquips = computed(() => infoStore.oaths ?? []);
|
||||||
const iconWrapStyle = computed(() => ({
|
const panelLayoutVars = computed(() => ({
|
||||||
"--equip-cell-size": "clamp(1.5rem, 2.2vw, 2rem)",
|
"--equip-cell-size": "clamp(1.5rem, 2.2vw, 2rem)",
|
||||||
"--suit-switch-size": "clamp(2rem, 3.8vw, 2.25rem)",
|
"--suit-switch-size": "clamp(1.2rem, 2vw, 1.8rem)",
|
||||||
width: `calc(var(--equip-cell-size) * ${rowCount} + 0.25rem * ${rowCount - 1})`,
|
"--equip-grid-width": `calc(var(--equip-cell-size) * ${rowCount} + 0.25rem * ${rowCount - 1})`,
|
||||||
|
"--panel-card-width": "calc(var(--equip-grid-width) + 2rem)",
|
||||||
|
}));
|
||||||
|
|
||||||
|
const iconWrapStyle = computed(() => ({
|
||||||
|
...panelLayoutVars.value,
|
||||||
|
width: "var(--equip-grid-width)",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const isOathEquipment = (equip?: IEquipment) =>
|
const isOathEquipment = (equip?: IEquipment) =>
|
||||||
@@ -130,28 +153,6 @@ const previewOathMeta = computed(() => {
|
|||||||
return infoStore.oathSkills?.[previewSuitId.value];
|
return infoStore.oathSkills?.[previewSuitId.value];
|
||||||
});
|
});
|
||||||
|
|
||||||
const equippedParts = computed(() => {
|
|
||||||
const parts = [...infoStore.parts, ...(canUseSubWeapon.value ? ["副武器"] : [])];
|
|
||||||
return parts.map((part) => {
|
|
||||||
const current = configStore.config.equips[part];
|
|
||||||
return {
|
|
||||||
part,
|
|
||||||
equip: current?.id ? equips.value.find((item) => item.id === current.id) : undefined,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const equippedOaths = computed(() => {
|
|
||||||
return infoStore.oathParts
|
|
||||||
.map((slot) => ({
|
|
||||||
slot,
|
|
||||||
equip: oathEquips.value.find(
|
|
||||||
(item) => item.oathPos === slot && item.id === configStore.config.oaths?.[slot]?.id,
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
.filter((item) => !!item.equip);
|
|
||||||
});
|
|
||||||
|
|
||||||
const padToRow = <T,>(items: (T | undefined)[]) => {
|
const padToRow = <T,>(items: (T | undefined)[]) => {
|
||||||
const remainder = items.length % rowCount;
|
const remainder = items.length % rowCount;
|
||||||
if (remainder === 0) return items;
|
if (remainder === 0) return items;
|
||||||
@@ -577,6 +578,10 @@ const chooseEquip = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
configStore.chooseEqu(equip, false, options?.subweapon ?? false);
|
configStore.chooseEqu(equip, false, options?.subweapon ?? false);
|
||||||
|
|
||||||
|
if (isSelected(equip, options)) {
|
||||||
|
sidePanelTab.value = "equipped";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPreview = (equip: IEquipment | undefined) => {
|
const showPreview = (equip: IEquipment | undefined) => {
|
||||||
@@ -584,6 +589,32 @@ const showPreview = (equip: IEquipment | undefined) => {
|
|||||||
previewEquipKey.value = getEquipKey(equip);
|
previewEquipKey.value = getEquipKey(equip);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showPreviewByContext = (equip: IEquipment | undefined) => {
|
||||||
|
if (!equip) return;
|
||||||
|
previewEquipKey.value = getEquipKey(equip);
|
||||||
|
sidePanelTab.value = "detail";
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(equippedPart, (part) => {
|
||||||
|
if (!part) return;
|
||||||
|
const equipId = configStore.config.equips[part]?.id;
|
||||||
|
if (!equipId) return;
|
||||||
|
const equip = equips.value.find((item) => item.id === equipId);
|
||||||
|
if (!equip) return;
|
||||||
|
showPreview(equip);
|
||||||
|
sidePanelTab.value = "detail";
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(equippedOathPart, (part) => {
|
||||||
|
if (!part) return;
|
||||||
|
const equipId = configStore.config.oaths[part]?.id;
|
||||||
|
if (!equipId) return;
|
||||||
|
const equip = oathEquips.value.find((item) => item.id === equipId && item.oathPos === part);
|
||||||
|
if (!equip) return;
|
||||||
|
showPreview(equip);
|
||||||
|
sidePanelTab.value = "detail";
|
||||||
|
});
|
||||||
|
|
||||||
const wearCurrentSuitByRarity = (isOath = false, targetSuitId?: string) => {
|
const wearCurrentSuitByRarity = (isOath = false, targetSuitId?: string) => {
|
||||||
const currentRarity = isOath ? oathSuitRarity.value : suitRarity.value;
|
const currentRarity = isOath ? oathSuitRarity.value : suitRarity.value;
|
||||||
const currentSuitId = targetSuitId ?? (isOath ? oathSuitId.value : suitId.value);
|
const currentSuitId = targetSuitId ?? (isOath ? oathSuitId.value : suitId.value);
|
||||||
@@ -750,23 +781,51 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const rarityChoices = [
|
const rarityChoices = [
|
||||||
{ value: "稀有", label: "稀" },
|
{ value: "稀有", label: "稀", color: "#9879da" },
|
||||||
{ value: "神器", label: "神" },
|
{ value: "神器", label: "神", color: "#ef28c1" },
|
||||||
{ value: "传说", label: "传" },
|
{ value: "传说", label: "传", color: "#ff7700" },
|
||||||
{ value: "史诗", label: "史" },
|
{ value: "史诗", label: "史", color: "#fcb600" },
|
||||||
{ value: "太初", label: "太" },
|
{
|
||||||
|
value: "太初",
|
||||||
|
label: "太",
|
||||||
|
color: "linear-gradient(to bottom, #5aff62, #58c6ff)",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const normalizeRarityValue = (value: unknown): string => {
|
||||||
|
const raw = Array.isArray(value) ? value[0] : value;
|
||||||
|
return typeof raw === "string" ? raw : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSuitRarity = (value: unknown) => {
|
||||||
|
const next = normalizeRarityValue(value);
|
||||||
|
suitRarity.value = !next || suitRarity.value === next ? "" : next;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleOathRarity = (value: unknown) => {
|
||||||
|
const next = normalizeRarityValue(value);
|
||||||
|
oathSuitRarity.value = !next || oathSuitRarity.value === next ? "" : next;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid min-h-full min-w-0 gap-4 pb-6 xl:grid-cols-[minmax(0,1fr)_320px]">
|
<div
|
||||||
<div class="min-w-0 space-y-4">
|
class="grid min-h-full min-w-0 gap-4 pb-6 md:h-full md:pb-0 md:grid-cols-[minmax(0,1fr)_320px]"
|
||||||
<Card>
|
>
|
||||||
<CardHeader class="pb-3">
|
<div
|
||||||
|
class="min-h-0 min-w-0 grid items-stretch gap-4 md:h-full [grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr))]"
|
||||||
|
:style="panelLayoutVars"
|
||||||
|
>
|
||||||
|
<div class="min-h-0 min-w-0 md:h-full">
|
||||||
|
<Card
|
||||||
|
class="w-full md:flex md:h-full md:flex-col md:overflow-hidden"
|
||||||
|
:style="panelLayoutVars"
|
||||||
|
>
|
||||||
|
<!-- <CardHeader class="pb-3">
|
||||||
<CardTitle>武器/称号/宠物/秘宝</CardTitle>
|
<CardTitle>武器/称号/宠物/秘宝</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader> -->
|
||||||
<CardContent class="space-y-4">
|
<CardContent class="space-y-4 md:min-h-0 md:flex-1 md:overflow-y-auto">
|
||||||
<Tabs v-model="weaponTab" class="gap-0">
|
<Tabs v-model="weaponTab" class="gap-0" size="sm">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="weapon">武器</TabsTrigger>
|
<TabsTrigger value="weapon">武器</TabsTrigger>
|
||||||
<TabsTrigger value="else">称号/宠物/秘宝</TabsTrigger>
|
<TabsTrigger value="else">称号/宠物/秘宝</TabsTrigger>
|
||||||
@@ -775,7 +834,7 @@ const rarityChoices = [
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
<div class="mx-auto flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
||||||
<template v-if="weaponTab === 'weapon'">
|
<template v-if="weaponTab === 'weapon'">
|
||||||
<EquipmentCell
|
<EquipmentCell
|
||||||
v-for="(equip, index) in weaponGrid"
|
v-for="(equip, index) in weaponGrid"
|
||||||
@@ -784,7 +843,7 @@ const rarityChoices = [
|
|||||||
:selected="equip ? isSelected(equip) : false"
|
:selected="equip ? isSelected(equip) : false"
|
||||||
:inactive="equip ? !isSelected(equip) : false"
|
:inactive="equip ? !isSelected(equip) : false"
|
||||||
@click="equip ? chooseEquip(equip) : undefined"
|
@click="equip ? chooseEquip(equip) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="weaponTab === 'else'">
|
<template v-else-if="weaponTab === 'else'">
|
||||||
@@ -795,7 +854,7 @@ const rarityChoices = [
|
|||||||
:selected="equip ? isSelected(equip) : false"
|
:selected="equip ? isSelected(equip) : false"
|
||||||
:inactive="equip ? !isSelected(equip) : false"
|
:inactive="equip ? !isSelected(equip) : false"
|
||||||
@click="equip ? chooseEquip(equip) : undefined"
|
@click="equip ? chooseEquip(equip) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -806,29 +865,51 @@ const rarityChoices = [
|
|||||||
:selected="equip ? isSelected(equip, { subweapon: true }) : false"
|
:selected="equip ? isSelected(equip, { subweapon: true }) : false"
|
||||||
:inactive="equip ? !isSelected(equip, { subweapon: true }) : false"
|
:inactive="equip ? !isSelected(equip, { subweapon: true }) : false"
|
||||||
@click="equip ? chooseEquip(equip, { subweapon: true }) : undefined"
|
@click="equip ? chooseEquip(equip, { subweapon: true }) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div class="flex items-center gap-2">
|
||||||
<CardHeader class="pb-3">
|
|
||||||
<CardTitle>套装/通宝</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent class="space-y-4">
|
|
||||||
<Tabs v-model="equipTab" class="gap-0">
|
<Tabs v-model="equipTab" class="gap-0">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="suit">套装</TabsTrigger>
|
<TabsTrigger value="suit">装备套装</TabsTrigger>
|
||||||
<TabsTrigger value="universal">通宝</TabsTrigger>
|
<TabsTrigger value="universal">装备通宝</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
<ColorSwatchPickerRoot
|
||||||
|
v-if="equipTab === 'suit'"
|
||||||
|
class="ml-auto flex items-center gap-2"
|
||||||
|
orientation="horizontal"
|
||||||
|
:model-value="suitRarity || undefined"
|
||||||
|
@update:model-value="toggleSuitRarity"
|
||||||
|
>
|
||||||
|
<ColorSwatchPickerItem
|
||||||
|
v-for="item in rarityChoices"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
class="group relative h-6 w-6 rounded-md border border-border/70 outline-none ring-offset-background transition hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
:title="item.value"
|
||||||
|
>
|
||||||
|
<ColorSwatchPickerItemSwatch
|
||||||
|
:label="item.value"
|
||||||
|
class="h-full w-full rounded-[5px]"
|
||||||
|
:style="{ background: item.color }"
|
||||||
|
/>
|
||||||
|
<ColorSwatchPickerItemIndicator
|
||||||
|
class="absolute inset-0 grid place-items-center rounded-[5px] bg-black/35 text-white"
|
||||||
|
>
|
||||||
|
<Check class="size-3" />
|
||||||
|
</ColorSwatchPickerItemIndicator>
|
||||||
|
</ColorSwatchPickerItem>
|
||||||
|
</ColorSwatchPickerRoot>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-if="equipTab === 'suit'">
|
<template v-if="equipTab === 'suit'">
|
||||||
<div
|
<div
|
||||||
class="flex max-w-full flex-nowrap gap-1.5 overflow-x-auto pb-1"
|
class="mx-auto flex w-max max-w-full flex-nowrap overflow-x-auto pb-1"
|
||||||
:style="iconWrapStyle"
|
:style="iconWrapStyle"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -861,20 +942,8 @@ const rarityChoices = [
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="item in rarityChoices"
|
|
||||||
:key="item.value"
|
|
||||||
size="sm"
|
|
||||||
:variant="suitRarity === item.value ? 'default' : 'outline'"
|
|
||||||
@click="suitRarity = suitRarity === item.value ? '' : item.value"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
<div class="mx-auto flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
||||||
<EquipmentCell
|
<EquipmentCell
|
||||||
v-for="(equip, index) in suitGrid"
|
v-for="(equip, index) in suitGrid"
|
||||||
:key="`suit-${index}-${equip?.id ?? 'empty'}`"
|
:key="`suit-${index}-${equip?.id ?? 'empty'}`"
|
||||||
@@ -882,7 +951,7 @@ const rarityChoices = [
|
|||||||
:selected="equip ? isSelected(equip) : false"
|
:selected="equip ? isSelected(equip) : false"
|
||||||
:inactive="equip ? !isSelected(equip) : false"
|
:inactive="equip ? !isSelected(equip) : false"
|
||||||
@click="equip ? chooseEquip(equip) : undefined"
|
@click="equip ? chooseEquip(equip) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -899,7 +968,7 @@ const rarityChoices = [
|
|||||||
{{ section.title }}
|
{{ section.title }}
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
<div class="mx-auto flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
||||||
<EquipmentCell
|
<EquipmentCell
|
||||||
v-for="(equip, index) in section.items"
|
v-for="(equip, index) in section.items"
|
||||||
:key="`universal-${sectionIndex}-${index}-${equip?.id ?? 'empty'}`"
|
:key="`universal-${sectionIndex}-${index}-${equip?.id ?? 'empty'}`"
|
||||||
@@ -907,7 +976,7 @@ const rarityChoices = [
|
|||||||
:selected="equip ? isSelected(equip) : false"
|
:selected="equip ? isSelected(equip) : false"
|
||||||
:inactive="equip ? !isSelected(equip) : false"
|
:inactive="equip ? !isSelected(equip) : false"
|
||||||
@click="equip ? chooseEquip(equip) : undefined"
|
@click="equip ? chooseEquip(equip) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -921,23 +990,57 @@ const rarityChoices = [
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card>
|
<div class="min-h-0 min-w-0 md:h-full">
|
||||||
<CardHeader class="pb-3">
|
<Card
|
||||||
|
class="w-full md:flex md:h-full md:flex-col md:overflow-hidden"
|
||||||
|
:style="panelLayoutVars"
|
||||||
|
>
|
||||||
|
<!-- <CardHeader>
|
||||||
<CardTitle>誓约</CardTitle>
|
<CardTitle>誓约</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader> -->
|
||||||
<CardContent class="space-y-4">
|
<CardContent class="space-y-4 md:min-h-0 md:flex-1 md:overflow-y-auto">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
<Tabs v-model="oathTab" class="gap-0">
|
<Tabs v-model="oathTab" class="gap-0">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="suit">套装</TabsTrigger>
|
<TabsTrigger value="suit">誓约</TabsTrigger>
|
||||||
<TabsTrigger value="universal">通宝</TabsTrigger>
|
<!-- <TabsTrigger value="universal">通宝</TabsTrigger>
|
||||||
<TabsTrigger value="other">其他</TabsTrigger>
|
<TabsTrigger value="other">其他</TabsTrigger> -->
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
<ColorSwatchPickerRoot
|
||||||
|
v-if="oathTab === 'suit'"
|
||||||
|
class="ml-auto flex items-center gap-2"
|
||||||
|
orientation="horizontal"
|
||||||
|
:model-value="oathSuitRarity || undefined"
|
||||||
|
@update:model-value="toggleOathRarity"
|
||||||
|
>
|
||||||
|
<ColorSwatchPickerItem
|
||||||
|
v-for="item in rarityChoices"
|
||||||
|
:key="`oath-rarity-${item.value}`"
|
||||||
|
:value="item.value"
|
||||||
|
class="group relative h-6 w-6 rounded-md border border-border/70 outline-none ring-offset-background transition hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
:title="item.value"
|
||||||
|
>
|
||||||
|
<ColorSwatchPickerItemSwatch
|
||||||
|
:label="item.value"
|
||||||
|
class="h-full w-full rounded-[5px]"
|
||||||
|
:style="{ background: item.color }"
|
||||||
|
/>
|
||||||
|
<ColorSwatchPickerItemIndicator
|
||||||
|
class="absolute inset-0 grid place-items-center rounded-[5px] bg-black/35 text-white"
|
||||||
|
>
|
||||||
|
<Check class="size-3" />
|
||||||
|
</ColorSwatchPickerItemIndicator>
|
||||||
|
</ColorSwatchPickerItem>
|
||||||
|
</ColorSwatchPickerRoot>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-if="oathTab === 'suit'">
|
<template v-if="oathTab === 'suit'">
|
||||||
<div
|
<div
|
||||||
class="flex max-w-full flex-nowrap gap-1.5 overflow-x-auto pb-1"
|
class="mx-auto flex w-max max-w-full flex-nowrap overflow-x-auto pb-1"
|
||||||
:style="iconWrapStyle"
|
:style="iconWrapStyle"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -972,18 +1075,6 @@ const rarityChoices = [
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<Button
|
|
||||||
v-for="item in rarityChoices"
|
|
||||||
:key="`oath-rarity-${item.value}`"
|
|
||||||
size="sm"
|
|
||||||
:variant="oathSuitRarity === item.value ? 'default' : 'outline'"
|
|
||||||
@click="oathSuitRarity = oathSuitRarity === item.value ? '' : item.value"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<template
|
<template
|
||||||
v-for="(section, sectionIndex) in oathSuitSections"
|
v-for="(section, sectionIndex) in oathSuitSections"
|
||||||
@@ -995,7 +1086,7 @@ const rarityChoices = [
|
|||||||
{{ section.title }}
|
{{ section.title }}
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
<div class="mx-auto flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
||||||
<EquipmentCell
|
<EquipmentCell
|
||||||
v-for="(equip, index) in section.items"
|
v-for="(equip, index) in section.items"
|
||||||
:key="`oath-suit-${sectionIndex}-${index}-${equip?.id ?? 'empty'}`"
|
:key="`oath-suit-${sectionIndex}-${index}-${equip?.id ?? 'empty'}`"
|
||||||
@@ -1004,7 +1095,7 @@ const rarityChoices = [
|
|||||||
:inactive="equip ? !isSelected(equip, { oath: true }) : false"
|
:inactive="equip ? !isSelected(equip, { oath: true }) : false"
|
||||||
:use-display-url="true"
|
:use-display-url="true"
|
||||||
@click="equip ? chooseEquip(equip, { oath: true }) : undefined"
|
@click="equip ? chooseEquip(equip, { oath: true }) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1020,7 +1111,7 @@ const rarityChoices = [
|
|||||||
|
|
||||||
<template v-else-if="oathTab === 'universal'">
|
<template v-else-if="oathTab === 'universal'">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
<div class="mx-auto flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
||||||
<EquipmentCell
|
<EquipmentCell
|
||||||
v-for="(equip, index) in oathUniversalGrid"
|
v-for="(equip, index) in oathUniversalGrid"
|
||||||
:key="`oath-universal-${index}-${equip?.id ?? 'empty'}`"
|
:key="`oath-universal-${index}-${equip?.id ?? 'empty'}`"
|
||||||
@@ -1029,7 +1120,7 @@ const rarityChoices = [
|
|||||||
:inactive="equip ? !isSelected(equip, { oath: true }) : false"
|
:inactive="equip ? !isSelected(equip, { oath: true }) : false"
|
||||||
:use-display-url="true"
|
:use-display-url="true"
|
||||||
@click="equip ? chooseEquip(equip, { oath: true }) : undefined"
|
@click="equip ? chooseEquip(equip, { oath: true }) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1037,7 +1128,7 @@ const rarityChoices = [
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
<div class="mx-auto flex flex-wrap content-start gap-1" :style="iconWrapStyle">
|
||||||
<EquipmentCell
|
<EquipmentCell
|
||||||
v-for="(equip, index) in oathOtherGrid"
|
v-for="(equip, index) in oathOtherGrid"
|
||||||
:key="`oath-other-${index}-${equip?.id ?? 'empty'}`"
|
:key="`oath-other-${index}-${equip?.id ?? 'empty'}`"
|
||||||
@@ -1046,7 +1137,7 @@ const rarityChoices = [
|
|||||||
:inactive="equip ? !isSelected(equip, { oath: true }) : false"
|
:inactive="equip ? !isSelected(equip, { oath: true }) : false"
|
||||||
:use-display-url="true"
|
:use-display-url="true"
|
||||||
@click="equip ? chooseEquip(equip, { oath: true }) : undefined"
|
@click="equip ? chooseEquip(equip, { oath: true }) : undefined"
|
||||||
@preview="equip ? showPreview(equip) : undefined"
|
@right-click="equip ? showPreviewByContext(equip) : undefined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1054,63 +1145,49 @@ const rarityChoices = [
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4 xl:sticky xl:top-4 xl:self-start">
|
<div class="min-h-0 space-y-4 md:h-full md:self-stretch">
|
||||||
<Card>
|
<Card class="md:flex md:h-full md:flex-col md:overflow-hidden">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="text-base">角色信息</CardTitle>
|
<CardTitle class="text-base">
|
||||||
<Tabs v-model="sidePanelTab" class="gap-0">
|
<Tabs v-model="sidePanelTab" class="gap-0">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="equipped">当前已穿戴</TabsTrigger>
|
<TabsTrigger value="equipped">当前已穿戴</TabsTrigger>
|
||||||
<TabsTrigger value="detail">装备属性</TabsTrigger>
|
<TabsTrigger value="detail">装备属性</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs></CardTitle
|
||||||
|
>
|
||||||
|
|
||||||
<CardDescription v-if="sidePanelTab === 'equipped'"
|
<CardDescription v-if="sidePanelTab === 'equipped'"
|
||||||
>按部位查看当前选择,点击可切到属性预览。</CardDescription
|
>按部位查看当前选择,点击可切到属性预览。</CardDescription
|
||||||
>
|
>
|
||||||
<CardDescription v-else>当前聚焦的装备会展示单件与套装属性。</CardDescription>
|
<CardDescription v-else>当前聚焦的装备会展示单件与套装属性。</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent v-if="sidePanelTab === 'equipped'" class="space-y-3">
|
<CardContent v-if="sidePanelTab === 'equipped'" class="min-h-0 flex-1 overflow-hidden">
|
||||||
<button
|
<div class="h-full space-y-3 overflow-y-auto pr-1">
|
||||||
v-for="item in equippedParts"
|
<div class="space-y-1">
|
||||||
:key="item.part"
|
<div class="text-xs text-muted-foreground">装备</div>
|
||||||
type="button"
|
<EquList
|
||||||
class="flex w-full flex-col rounded-md border px-3 py-2 text-left transition hover:border-primary/40 disabled:opacity-50"
|
v-model:part="equippedPart"
|
||||||
:disabled="!item.equip"
|
:detail="configStore.config.equips"
|
||||||
@click="
|
:with-sub-weapon="canUseSubWeapon"
|
||||||
if (item.equip) {
|
:with-pet="true"
|
||||||
showPreview(item.equip);
|
/>
|
||||||
sidePanelTab = 'detail';
|
</div>
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="text-xs text-muted-foreground">{{ item.part }}</span>
|
|
||||||
<span class="truncate text-sm font-medium">{{ item.equip?.name ?? "未选择" }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="equippedOaths.length" class="space-y-2">
|
<div class="space-y-1">
|
||||||
<div class="text-xs text-muted-foreground">誓约槽位</div>
|
<div class="text-xs text-muted-foreground">誓约</div>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<OathList v-model:part="equippedOathPart" :detail="configStore.config.oaths" />
|
||||||
<button
|
|
||||||
v-for="item in equippedOaths"
|
|
||||||
:key="item.slot"
|
|
||||||
type="button"
|
|
||||||
class="flex flex-col rounded-md border px-3 py-2 text-left transition hover:border-primary/40"
|
|
||||||
@click="
|
|
||||||
if (item.equip) {
|
|
||||||
showPreview(item.equip);
|
|
||||||
sidePanelTab = 'detail';
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="text-xs text-muted-foreground">槽位 {{ Number(item.slot) + 1 }}</span>
|
|
||||||
<span class="truncate text-sm font-medium">{{ item.equip?.name }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardContent v-else-if="previewEquip" class="space-y-4">
|
<CardContent
|
||||||
|
v-else-if="previewEquip"
|
||||||
|
class="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div class="space-y-4">
|
||||||
<div class="flex items-center gap-2 text-sm font-medium">
|
<div class="flex items-center gap-2 text-sm font-medium">
|
||||||
<Sparkles class="size-4" /> 装备详情
|
<Sparkles class="size-4" /> 装备详情
|
||||||
</div>
|
</div>
|
||||||
@@ -1136,7 +1213,9 @@ const rarityChoices = [
|
|||||||
<div>套装数: {{ previewEquip.suit.length }}</div>
|
<div>套装数: {{ previewEquip.suit.length }}</div>
|
||||||
<div>适应值上限: {{ previewEquip.max_adaptation }}</div>
|
<div>适应值上限: {{ previewEquip.max_adaptation }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-h-0 flex-1 space-y-4 overflow-y-auto pr-1">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="text-xs font-medium">装备词条</div>
|
<div class="text-xs font-medium">装备词条</div>
|
||||||
<div
|
<div
|
||||||
@@ -1153,7 +1232,9 @@ const rarityChoices = [
|
|||||||
size="icon"
|
size="icon"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="size-7"
|
class="size-7"
|
||||||
:disabled="rarityOrder.findIndex((item) => item === previewSuitLevel.rarity) <= 0"
|
:disabled="
|
||||||
|
rarityOrder.findIndex((item) => item === previewSuitLevel.rarity) <= 0
|
||||||
|
"
|
||||||
@click="changePreviewSuitLevel('preRarity')"
|
@click="changePreviewSuitLevel('preRarity')"
|
||||||
>
|
>
|
||||||
<ChevronsLeft class="size-3.5" />
|
<ChevronsLeft class="size-3.5" />
|
||||||
@@ -1231,7 +1312,9 @@ const rarityChoices = [
|
|||||||
size="icon"
|
size="icon"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="size-7"
|
class="size-7"
|
||||||
:disabled="rarityOrder.findIndex((item) => item === previewOathLevel.rarity) <= 0"
|
:disabled="
|
||||||
|
rarityOrder.findIndex((item) => item === previewOathLevel.rarity) <= 0
|
||||||
|
"
|
||||||
@click="changePreviewOathLevel('preRarity')"
|
@click="changePreviewOathLevel('preRarity')"
|
||||||
>
|
>
|
||||||
<ChevronsLeft class="size-3.5" />
|
<ChevronsLeft class="size-3.5" />
|
||||||
@@ -1330,6 +1413,7 @@ const rarityChoices = [
|
|||||||
v-html="highlightDetail(previewEquip.bufferDetail)"
|
v-html="highlightDetail(previewEquip.bufferDetail)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardContent v-else class="py-10 text-center text-sm text-muted-foreground">
|
<CardContent v-else class="py-10 text-center text-sm text-muted-foreground">
|
||||||
选择一件装备后可在这里查看详细数据。
|
选择一件装备后可在这里查看详细数据。
|
||||||
|
|||||||
+4
-5
@@ -14,6 +14,7 @@ const props = defineProps<{
|
|||||||
defineEmits<{
|
defineEmits<{
|
||||||
click: [];
|
click: [];
|
||||||
preview: [];
|
preview: [];
|
||||||
|
rightClick: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const rarityClass = computed(() => {
|
const rarityClass = computed(() => {
|
||||||
@@ -35,11 +36,8 @@ const rarityClass = computed(() => {
|
|||||||
|
|
||||||
const imageUrl = computed(() => {
|
const imageUrl = computed(() => {
|
||||||
if (!props.equipment) return "";
|
if (!props.equipment) return "";
|
||||||
return getImageURL(
|
const path = props.useDisplayUrl ? props.equipment.displayUrl : props.equipment.imageUrl;
|
||||||
props.useDisplayUrl
|
return path ? getImageURL(path) : "";
|
||||||
? props.equipment.displayUrl || props.equipment.imageUrl
|
|
||||||
: props.equipment.imageUrl,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cellStyle = {
|
const cellStyle = {
|
||||||
@@ -65,6 +63,7 @@ const cellStyle = {
|
|||||||
:style="cellStyle"
|
:style="cellStyle"
|
||||||
@click="$emit('click')"
|
@click="$emit('click')"
|
||||||
@mouseenter="$emit('preview')"
|
@mouseenter="$emit('preview')"
|
||||||
|
@contextmenu.prevent="$emit('rightClick')"
|
||||||
>
|
>
|
||||||
<img :src="imageUrl" :alt="equipment.name" class="size-full object-contain" />
|
<img :src="imageUrl" :alt="equipment.name" class="size-full object-contain" />
|
||||||
<div v-if="inactive" class="absolute inset-0 bg-black/45" />
|
<div v-if="inactive" class="absolute inset-0 bg-black/45" />
|
||||||
|
|||||||
Reference in New Issue
Block a user