This commit is contained in:
2026-05-11 14:42:22 +08:00
parent 5344e6660e
commit 4a198101f1
14 changed files with 1687 additions and 80 deletions
@@ -1,13 +1,55 @@
<script setup lang="ts">
import { computed } from "vue";
import { computed, ref } from "vue";
import { Hammer, ShieldCheck, Sparkles } from "lucide-vue-next";
import { useConfigStore, useInfoStore } from "@/stores";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
const configStore = useConfigStore();
const infoStore = useInfoStore();
const moduleMode = ref<"equip" | "oath" | "avatar" | "global">("equip");
const notice = ref("");
const noticeType = ref<"error" | "info">("info");
const avatarPart = ref("头部");
const emblemPart = ref("彩色");
const avatarOptionKeyword = ref("");
const emblemOptionKeyword = ref("");
const onlyConfiguredAvatarParts = ref(false);
const onlyConfiguredEmblemSlots = ref(false);
const noEnchantAvatarParts = ["头部", "帽子", "脸部", "胸部", "上衣", "腰带", "下装", "鞋"];
const emblemLevels = [
"稀有Lv.1",
"稀有Lv.2",
"稀有Lv.3",
"稀有Lv.4",
"稀有Lv.5",
"神器Lv.1",
"神器Lv.2",
"神器Lv.3",
"神器Lv.4",
"神器Lv.5",
"传说Lv.1",
"传说Lv.2",
"传说Lv.3",
"传说Lv.4",
"传说Lv.5",
"史诗Lv.1",
];
const setNotice = (message: string, type: "error" | "info" = "info") => {
notice.value = message;
noticeType.value = type;
};
const trackSource = (detail: string) => {
configStore.pushChangeSource("Forge", detail);
};
const summary = computed(() => {
const equipCount = Object.values(configStore.config.equips || {}).filter(
(item) => !!item.id,
@@ -18,59 +60,672 @@ const summary = computed(() => {
const oathCount = Object.values(configStore.config.oaths || {}).filter(
(item) => !!item.id,
).length;
const emblemCount = Object.values(configStore.config.emblems || {}).reduce((acc, item) => {
return acc + (item.items?.filter(Boolean).length ?? 0);
}, 0);
return {
equipCount,
avatarCount,
oathCount,
emblemCount,
emblemSlots: infoStore.emblemParts.length,
};
});
const parts = computed(() => infoStore.parts.filter((part) => configStore.config.equips[part]));
const oathParts = computed(() =>
infoStore.oathParts.filter((part) => configStore.config.oaths[part]),
);
const avatarParts = computed(() => {
const all = infoStore.avatarParts.filter((part) => !!configStore.config.avatar[part]);
if (!onlyConfiguredAvatarParts.value) return all;
return all.filter((part) => {
const cfg = configStore.config.avatar[part];
if (!cfg) return false;
return !!cfg.id || !!cfg.enchant || !!cfg.emblem_0 || !!cfg.emblem_1 || cfg.option !== "0";
});
});
const oathNameById = computed(() => {
const map = new Map<string, string>();
infoStore.oaths.forEach((item) => {
if (!map.has(item.id)) {
map.set(item.id, item.name);
}
});
return map;
});
const equipNameById = computed(() => {
const map = new Map<string, string>();
infoStore.equips.forEach((item) => map.set(item.id, item.name));
return map;
});
const avatarEnchantOptions = computed(() => {
const keyword = avatarOptionKeyword.value.trim().toLowerCase();
return infoStore.enchants
.filter((item) => item.position.includes(avatarPart.value))
.filter((item) => !keyword || item.detail.toLowerCase().includes(keyword))
.sort((a, b) => (b.fame ?? 0) - (a.fame ?? 0));
});
const avatarSuitOptions = computed(() => {
return infoStore.avatars[avatarPart.value] ?? [];
});
const avatarEmblemOptions = computed(() => {
const keyword = avatarOptionKeyword.value.trim().toLowerCase();
return infoStore.emblems
.filter((item) => item.type !== "白金")
.filter((item) => {
if (!keyword) return true;
return (
item.value.toLowerCase().includes(keyword) ||
item.rarity.toLowerCase().includes(keyword) ||
item.type.toLowerCase().includes(keyword)
);
})
.sort((a, b) => b.fame - a.fame);
});
const hasAvatarEnchant = computed(() => !noEnchantAvatarParts.includes(avatarPart.value));
const emblemOptions = computed(() => {
const keyword = emblemOptionKeyword.value.trim().toLowerCase();
const selected = new Set((emblemCfg.value.items || []).filter(Boolean));
if (emblemPart.value === "彩色") {
return infoStore.emblems
.filter((a) => a.type !== "白金")
.filter((a) => {
const keywordOk =
!keyword ||
a.value.toLowerCase().includes(keyword) ||
a.rarity.toLowerCase().includes(keyword) ||
a.type.toLowerCase().includes(keyword);
const configuredOk = !onlyConfiguredEmblemSlots.value || selected.has(String(a.id));
return keywordOk && configuredOk;
})
.sort((a, b) => b.fame - a.fame);
}
return infoStore.emblems
.filter((a) => {
const color = emblemPart.value.charAt(0);
return a.type === emblemPart.value || a.type === "彩色" || a.type.includes(color);
})
.filter((a) => {
const keywordOk =
!keyword ||
a.value.toLowerCase().includes(keyword) ||
a.rarity.toLowerCase().includes(keyword) ||
a.type.toLowerCase().includes(keyword);
const configuredOk = !onlyConfiguredEmblemSlots.value || selected.has(String(a.id));
return keywordOk && configuredOk;
})
.sort((a, b) => b.fame - a.fame);
});
const emblemMaxCount = computed(() => {
if (emblemPart.value === "彩色") return 6;
if (emblemPart.value === "白金") return 2;
return 4;
});
const setNumberField = (
part: string,
field: "reinforce" | "refine" | "adaptation",
value: string,
) => {
const target = configStore.config.equips[part];
if (!target) return;
const parsed = Number.parseInt(value || "0", 10);
if (Number.isNaN(parsed)) {
setNotice(`${part} 的输入无效,请输入数字。`, "error");
return;
}
target[field] = Math.max(0, parsed);
setNotice(`已更新 ${part} 的打造参数。`);
};
const setOathAdaptation = (part: string, value: string) => {
const target = configStore.config.oaths[part];
if (!target) return;
const parsed = Number.parseInt(value || "0", 10);
if (Number.isNaN(parsed)) {
setNotice(`誓约槽位 ${part} 的输入无效,请输入数字。`, "error");
return;
}
target.adaptation = Math.max(0, parsed);
setNotice(`已更新誓约槽位 ${part} 的适配值。`);
};
const equipCfg = (part: string) => configStore.config.equips[part] ?? configStore.defaultEqusConfig;
const oathCfg = (part: string) => configStore.config.oaths[part] ?? configStore.defaultOathConfig;
const avatarCfg = (part: string) =>
configStore.config.avatar[part] ?? configStore.defaultAvatarConfig;
const ensureAvatarPart = (part: string) => {
if (!configStore.config.avatar[part]) {
configStore.config.avatar[part] = { ...configStore.defaultAvatarConfig };
}
return configStore.config.avatar[part];
};
const setAvatarField = (
field: "id" | "enchant" | "emblem_0" | "emblem_1" | "option",
value: string,
) => {
const target = ensureAvatarPart(avatarPart.value);
target[field] = value;
if (field === "id") {
const options =
avatarSuitOptions.value.find((item) => item.id.toString() === value)?.options ?? [];
if (options.length === 0) {
target.option = "0";
} else if (!options.includes(target.option)) {
target.option = options[0] ?? "0";
}
}
setNotice(`已更新 ${avatarPart.value} 的配置。`);
trackSource(`时装 ${avatarPart.value} ${field} 已更新`);
};
const resetAvatarPart = () => {
configStore.config.avatar[avatarPart.value] = { ...configStore.defaultAvatarConfig };
setNotice(`已重置 ${avatarPart.value} 配置。`);
trackSource(`时装 ${avatarPart.value} 已重置`);
};
const ensureEmblemPart = (part: string) => {
if (!configStore.config.emblems[part]) {
configStore.config.emblems[part] = { ...configStore.defaultEmblemConfig };
}
return configStore.config.emblems[part];
};
const emblemCfg = computed(() => ensureEmblemPart(emblemPart.value));
const setEmblemLv = (value: string) => {
const parsed = Number.parseInt(value || "0", 10);
if (Number.isNaN(parsed)) {
setNotice("徽章等级输入无效。", "error");
return;
}
const target = ensureEmblemPart(emblemPart.value);
target.lv = Math.max(0, Math.min(parsed, emblemLevels.length - 1));
if (emblemPart.value === "彩色") {
["红色", "蓝色", "黄色", "绿色"].forEach((part) => {
const partCfg = ensureEmblemPart(part);
partCfg.lv = Math.max(partCfg.lv || 0, target.lv);
});
} else {
const colorCfg = ensureEmblemPart("彩色");
colorCfg.lv = Math.min(colorCfg.lv || 0, target.lv);
}
setNotice(`已更新 ${emblemPart.value} 徽章等级。`);
trackSource(`徽章 ${emblemPart.value} 等级已更新`);
};
const setEmblemItem = (index: number, value: string) => {
const target = ensureEmblemPart(emblemPart.value);
const items = [...(target.items ?? configStore.defaultEmblemConfig.items)];
while (items.length < 6) items.push("");
items[index] = value;
target.items = items;
setNotice(`已更新 ${emblemPart.value} 徽章位 ${index + 1}。`);
trackSource(`徽章 ${emblemPart.value} 槽位 ${index + 1} 已更新`);
};
const resetEmblemPart = () => {
configStore.config.emblems[emblemPart.value] = { ...configStore.defaultEmblemConfig };
setNotice(`已重置 ${emblemPart.value} 徽章配置。`);
trackSource(`徽章 ${emblemPart.value} 已重置`);
};
const resetEquipPart = (part: string) => {
configStore.config.equips[part] = {
...configStore.defaultEqusConfig,
id: configStore.config.equips[part]?.id ?? "",
};
setNotice(`已重置 ${part} 的打造参数。`);
trackSource(`装备 ${part} 打造参数已重置`);
};
const resetOathPart = (part: string) => {
configStore.config.oaths[part] = {
...configStore.defaultOathConfig,
id: configStore.config.oaths[part]?.id ?? "",
};
setNotice(`已重置誓约槽位 ${part}。`);
trackSource(`誓约槽位 ${part} 已重置`);
};
const resetAllForge = () => {
parts.value.forEach((part) => {
configStore.config.equips[part] = {
...configStore.defaultEqusConfig,
id: configStore.config.equips[part]?.id ?? "",
};
});
oathParts.value.forEach((part) => {
configStore.config.oaths[part] = {
...configStore.defaultOathConfig,
id: configStore.config.oaths[part]?.id ?? "",
};
});
avatarParts.value.forEach((part) => {
configStore.config.avatar[part] = { ...configStore.defaultAvatarConfig };
});
infoStore.emblemParts.forEach((part) => {
configStore.config.emblems[part] = { ...configStore.defaultEmblemConfig };
});
setNotice("已重置当前角色的打造参数。", "info");
trackSource("当前角色 Forge 配置已快速重置");
};
</script>
<template>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-3">
<Card>
<div class="space-y-4">
<Card class="border-border/60 bg-gradient-to-b from-background to-muted/30">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Hammer class="size-4" />
装备打造
<CardTitle class="flex flex-wrap items-center justify-between gap-2">
<span>Forge 面板</span>
<span class="text-xs font-normal text-muted-foreground">
装备 {{ summary.equipCount }} · 时装 {{ summary.avatarCount }} · 誓约
{{ summary.oathCount }} · 徽章 {{ summary.emblemCount }}
</span>
</CardTitle>
<CardDescription>已迁移基础统计,后续补齐增幅/强化/锻造编辑器。</CardDescription>
<CardDescription>Forge 进阶:子模块可编辑,统一浅色/深色模式。</CardDescription>
</CardHeader>
<CardContent class="space-y-2 text-sm text-muted-foreground">
<div>已配置装备: {{ summary.equipCount }}</div>
<div>誓约槽位: {{ summary.oathCount }}</div>
<CardContent class="space-y-4">
<div class="inline-flex flex-wrap rounded-md border p-1">
<Button
size="sm"
:variant="moduleMode === 'equip' ? 'default' : 'ghost'"
@click="moduleMode = 'equip'"
>
装备打造
</Button>
<Button
size="sm"
:variant="moduleMode === 'oath' ? 'default' : 'ghost'"
@click="moduleMode = 'oath'"
>
誓约
</Button>
<Button
size="sm"
:variant="moduleMode === 'avatar' ? 'default' : 'ghost'"
@click="moduleMode = 'avatar'"
>
时装徽章
</Button>
<Button
size="sm"
:variant="moduleMode === 'global' ? 'default' : 'ghost'"
@click="moduleMode = 'global'"
>
全局
</Button>
</div>
<div class="flex flex-wrap gap-2">
<Button size="sm" variant="outline" @click="resetAllForge">快速重置打造</Button>
<router-link to="/dcalc/dnfhelper">
<Button size="sm" variant="outline">前往助手导入</Button>
</router-link>
</div>
<div
v-if="notice"
class="rounded-md border px-3 py-2 text-xs"
:class="
noticeType === 'error'
? 'border-destructive/40 bg-destructive/10 text-destructive'
: 'border-border/60 bg-muted/40 text-muted-foreground'
"
>
{{ notice }}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Sparkles class="size-4" />
时装与徽章
</CardTitle>
<CardDescription>保留旧版数据结构,逐步替换为 shadcn-vue 控件。</CardDescription>
</CardHeader>
<CardContent class="space-y-2 text-sm text-muted-foreground">
<div>已配置时装: {{ summary.avatarCount }}</div>
<div>徽章位类型: {{ summary.emblemSlots }}</div>
</CardContent>
</Card>
<div v-if="moduleMode === 'equip'" class="grid grid-cols-1 gap-4">
<Card class="border-border/60 bg-card/90">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Hammer class="size-4" />
装备打造编辑器
</CardTitle>
<CardDescription>强化、锻造、品级编辑并即时回写配置。</CardDescription>
</CardHeader>
<CardContent class="space-y-3">
<div
v-for="part in parts"
:key="part"
class="grid grid-cols-1 gap-2 rounded-md border p-3 text-sm md:grid-cols-[7rem_1fr_6rem_6rem_6rem_auto] md:items-center"
>
<div class="font-medium">{{ part }}</div>
<div class="truncate text-muted-foreground">
{{ equipNameById.get(equipCfg(part).id) || "未选择装备(可在装备页选择)" }}
</div>
<Input
:model-value="String(equipCfg(part).reinforce)"
type="number"
min="0"
class="h-8"
placeholder="强化"
@update:model-value="(value) => setNumberField(part, 'reinforce', String(value))"
/>
<Input
:model-value="String(equipCfg(part).refine)"
type="number"
min="0"
class="h-8"
placeholder="锻造"
@update:model-value="(value) => setNumberField(part, 'refine', String(value))"
/>
<Input
:model-value="String(equipCfg(part).adaptation)"
type="number"
min="0"
class="h-8"
placeholder="品级"
@update:model-value="(value) => setNumberField(part, 'adaptation', String(value))"
/>
<Button size="sm" variant="ghost" @click="resetEquipPart(part)">重置</Button>
</div>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<ShieldCheck class="size-4" />
助手导入
</CardTitle>
<CardDescription>沿用助手识别能力快速回填打造数据。</CardDescription>
</CardHeader>
<CardContent>
<router-link to="/dcalc/dnfhelper">
<Button variant="outline">前往助手导入</Button>
</router-link>
</CardContent>
</Card>
<div v-if="moduleMode === 'oath'" class="grid grid-cols-1 gap-4">
<Card class="border-border/60 bg-card/90">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<ShieldCheck class="size-4" />
誓约子模块
</CardTitle>
<CardDescription>编辑誓约槽位适配值。</CardDescription>
</CardHeader>
<CardContent class="space-y-3">
<div
v-for="part in oathParts"
:key="part"
class="grid grid-cols-1 gap-2 rounded-md border p-3 text-sm md:grid-cols-[6rem_1fr_7rem_auto] md:items-center"
>
<div class="font-medium">槽位 {{ part }}</div>
<div class="truncate text-muted-foreground">
{{ oathNameById.get(oathCfg(part).id) || "未选择誓约(可在装备页选择)" }}
</div>
<Input
:model-value="String(oathCfg(part).adaptation)"
type="number"
min="0"
class="h-8"
placeholder="适配"
@update:model-value="(value) => setOathAdaptation(part, String(value))"
/>
<Button size="sm" variant="ghost" @click="resetOathPart(part)">重置</Button>
</div>
</CardContent>
</Card>
</div>
<div v-if="moduleMode === 'avatar'" class="grid grid-cols-1 gap-4 xl:grid-cols-2">
<Card class="border-border/60 bg-card/90">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Sparkles class="size-4" />
时装编辑
</CardTitle>
<CardDescription>支持附魔、徽章项、品级和属性值编辑。</CardDescription>
</CardHeader>
<CardContent class="space-y-3 text-sm">
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">筛选</div>
<div class="flex items-center gap-2">
<Input v-model="avatarOptionKeyword" class="h-8" placeholder="搜索附魔/徽章可选项" />
<label class="inline-flex items-center gap-1 text-xs whitespace-nowrap">
<input v-model="onlyConfiguredAvatarParts" type="checkbox" class="size-3.5" />
仅已配置部位
</label>
</div>
</div>
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">部位</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
v-model="avatarPart"
>
<option v-for="part in avatarParts" :key="part" :value="part">{{ part }}</option>
</select>
</div>
<div
v-if="hasAvatarEnchant"
class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center"
>
<div class="text-muted-foreground">附魔</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
:value="avatarCfg(avatarPart).enchant"
@change="
(event) => setAvatarField('enchant', (event.target as HTMLSelectElement).value)
"
>
<option value="">无</option>
<option v-for="item in avatarEnchantOptions" :key="item.id" :value="String(item.id)">
{{ item.detail }}
</option>
</select>
</div>
<template v-else>
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">品级</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
:value="avatarCfg(avatarPart).id"
@change="(event) => setAvatarField('id', (event.target as HTMLSelectElement).value)"
>
<option value="">未选择</option>
<option v-for="item in avatarSuitOptions" :key="item.id" :value="String(item.id)">
{{ item.suit || item.rarity || item.name }}
</option>
</select>
</div>
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">属性</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
:value="avatarCfg(avatarPart).option"
@change="
(event) => setAvatarField('option', (event.target as HTMLSelectElement).value)
"
>
<option value="0">无</option>
<option
v-for="opt in avatarSuitOptions.find(
(x) => String(x.id) === avatarCfg(avatarPart).id,
)?.options || []"
:key="opt"
:value="opt"
>
{{ opt }}
</option>
</select>
</div>
</template>
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">徽章一</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
:value="avatarCfg(avatarPart).emblem_0"
@change="
(event) => setAvatarField('emblem_0', (event.target as HTMLSelectElement).value)
"
>
<option value="">无</option>
<option v-for="item in avatarEmblemOptions" :key="item.id" :value="String(item.id)">
[{{ item.rarity }}] {{ item.value }}
</option>
</select>
</div>
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">徽章二</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
:value="avatarCfg(avatarPart).emblem_1"
@change="
(event) => setAvatarField('emblem_1', (event.target as HTMLSelectElement).value)
"
>
<option value="">无</option>
<option v-for="item in avatarEmblemOptions" :key="item.id" :value="String(item.id)">
[{{ item.rarity }}] {{ item.value }}
</option>
</select>
</div>
<div class="flex justify-end">
<Button size="sm" variant="outline" @click="resetAvatarPart">重置当前部位</Button>
</div>
</CardContent>
</Card>
<Card class="border-border/60 bg-card/90">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Sparkles class="size-4" />
徽章编辑
</CardTitle>
<CardDescription>支持等级、徽章条目与槽位数量规则。</CardDescription>
</CardHeader>
<CardContent class="space-y-3 text-sm">
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">筛选</div>
<div class="flex items-center gap-2">
<Input v-model="emblemOptionKeyword" class="h-8" placeholder="搜索徽章可选项" />
<label class="inline-flex items-center gap-1 text-xs whitespace-nowrap">
<input v-model="onlyConfiguredEmblemSlots" type="checkbox" class="size-3.5" />
仅已配置项
</label>
</div>
</div>
<div class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center">
<div class="text-muted-foreground">槽位类型</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
v-model="emblemPart"
>
<option v-for="part in infoStore.emblemParts" :key="part" :value="part">
{{ part }}
</option>
</select>
</div>
<div
v-if="emblemPart !== '白金'"
class="grid grid-cols-1 gap-2 md:grid-cols-[7rem_1fr] md:items-center"
>
<div class="text-muted-foreground">等级</div>
<select
class="h-8 rounded-md border border-input bg-background px-2"
:value="String(emblemCfg.lv ?? 0)"
@change="(event) => setEmblemLv((event.target as HTMLSelectElement).value)"
>
<option v-for="(label, index) in emblemLevels" :key="label" :value="String(index)">
{{ label }}
</option>
</select>
</div>
<div class="space-y-2">
<div class="text-muted-foreground">徽章条目</div>
<div class="grid grid-cols-1 gap-2 md:grid-cols-2">
<div
v-for="index in emblemMaxCount"
:key="index"
v-show="!onlyConfiguredEmblemSlots || !!(emblemCfg.items[index - 1] || '')"
class="space-y-1"
>
<div class="text-xs text-muted-foreground">槽位 {{ index }}</div>
<select
class="h-8 w-full rounded-md border border-input bg-background px-2"
:value="emblemCfg.items[index - 1] || ''"
@change="
(event) => setEmblemItem(index - 1, (event.target as HTMLSelectElement).value)
"
>
<option value="">无</option>
<option v-for="item in emblemOptions" :key="item.id" :value="String(item.id)">
[{{ item.rarity }}] {{ item.value }}
</option>
</select>
</div>
</div>
</div>
<div class="flex justify-end">
<Button size="sm" variant="outline" @click="resetEmblemPart">重置当前徽章槽</Button>
</div>
</CardContent>
</Card>
</div>
<div v-if="moduleMode === 'global'" class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<Card class="border-border/60 bg-card/90">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<ShieldCheck class="size-4" />
计算开关
</CardTitle>
<CardDescription>从旧版 Character 页底部迁移的全局参数。</CardDescription>
</CardHeader>
<CardContent class="space-y-2 text-sm text-muted-foreground">
<label class="flex items-center justify-between rounded-md border p-2">
<span>系统奶 DSB</span>
<input v-model="configStore.config.DSB" type="checkbox" class="size-4" />
</label>
<label class="flex items-center justify-between rounded-md border p-2">
<span>组队 BUFF</span>
<input v-model="configStore.config.BUFF" type="checkbox" class="size-4" />
</label>
</CardContent>
</Card>
<Card class="border-border/60 bg-card/90">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<ShieldCheck class="size-4" />
当前统计
</CardTitle>
</CardHeader>
<CardContent class="space-y-2 text-sm text-muted-foreground">
<div>已配置装备: {{ summary.equipCount }}</div>
<div>誓约槽位: {{ summary.oathCount }}</div>
<div>已配置时装: {{ summary.avatarCount }}</div>
<div>已配置徽章条目: {{ summary.emblemCount }}</div>
</CardContent>
</Card>
</div>
</div>
</template>