This commit is contained in:
2026-08-18 12:18:28 +08:00
parent 5fd7b17bec
commit 5ccf31aa20
3 changed files with 82 additions and 80 deletions
@@ -118,7 +118,7 @@
</div> </div>
<footer class="damage-panel__total" :class="toneClass(totalTone)"> <footer class="damage-panel__total" :class="toneClass(totalTone)">
<span>{{ totalStandard ? '相对基准' : '总伤害' }}</span> <span>{{ totalStandard ? "相对基准" : "总伤害" }}</span>
<strong>{{ showTotalText }}</strong> <strong>{{ showTotalText }}</strong>
</footer> </footer>
</div> </div>
@@ -126,166 +126,166 @@
</template> </template>
<script setup lang="ts" name="Skill"> <script setup lang="ts" name="Skill">
import type { IResultSkill, IResultSkillCount } from '@/api/calc/type' import type { IResultSkill, IResultSkillCount } from "@/api/calc/type";
import { UiNumberField } from '@/components/ui/number-field' import { UiNumberField } from "@/components/ui/number-field";
import { useConfigStore } from '@/stores' import { useConfigStore } from "@/stores";
import { getImageURL } from '@/utils/images' import { getImageURL } from "@/utils/images";
type SortKey = 'learnLv' | 'cd' | 'dps' | 'total' type SortKey = "learnLv" | "cd" | "dps" | "total";
type Tone = 'positive' | 'negative' | undefined type Tone = "positive" | "negative" | undefined;
const props = defineProps<{ const props = defineProps<{
data: IResultSkill[] data: IResultSkill[];
standard?: { standard?: {
skills: IResultSkill[] skills: IResultSkill[];
skillCount: IResultSkillCount[] skillCount: IResultSkillCount[];
} };
}>() }>();
const expanded = ref(false) const expanded = ref(false);
const sort = ref<SortKey>('learnLv') const sort = ref<SortKey>("learnLv");
const order = ref<'asc' | 'desc'>('desc') const order = ref<"asc" | "desc">("desc");
const config = useConfigStore() const config = useConfigStore();
watchEffect(() => { watchEffect(() => {
props.data.forEach((skill) => { props.data.forEach((skill) => {
const exists = config.skillCountConfig.some( const exists = config.skillCountConfig.some(
(item) => item.name === skill.name && item.mode === skill.mode, (item) => item.name === skill.name && item.mode === skill.mode,
) );
if (!exists) config.skillCountConfig.push({ name: skill.name, mode: skill.mode, count: 1 }) if (!exists) config.skillCountConfig.push({ name: skill.name, mode: skill.mode, count: 1 });
}) });
}) });
function skillCountValue(skill: IResultSkill) { function skillCountValue(skill: IResultSkill) {
return ( return (
config.skillCountConfig.find((item) => item.name === skill.name && item.mode === skill.mode) config.skillCountConfig.find((item) => item.name === skill.name && item.mode === skill.mode)
?.count ?? 1 ?.count ?? 1
) );
} }
function setSkillCount(skill: IResultSkill, value: number | undefined) { function setSkillCount(skill: IResultSkill, value: number | undefined) {
const count = typeof value === 'number' && Number.isFinite(value) ? value : 0 const count = typeof value === "number" && Number.isFinite(value) ? value : 0;
const target = config.skillCountConfig.find( const target = config.skillCountConfig.find(
(item) => item.name === skill.name && item.mode === skill.mode, (item) => item.name === skill.name && item.mode === skill.mode,
) );
if (target) target.count = count if (target) target.count = count;
else config.skillCountConfig.push({ name: skill.name, mode: skill.mode, count }) else config.skillCountConfig.push({ name: skill.name, mode: skill.mode, count });
} }
const totalDamage = computed(() => const totalDamage = computed(() =>
props.data.reduce((sum, skill) => sum + skill.damage * skillCountValue(skill), 0), props.data.reduce((sum, skill) => sum + skill.damage * skillCountValue(skill), 0),
) );
const totalStandard = computed(() => { const totalStandard = computed(() => {
if (!props.standard) return undefined if (!props.standard) return undefined;
return props.standard.skills.reduce((sum, skill) => { return props.standard.skills.reduce((sum, skill) => {
const count = props.standard?.skillCount.find( const count = props.standard?.skillCount.find(
(item) => item.name === skill.name && item.mode === skill.mode, (item) => item.name === skill.name && item.mode === skill.mode,
)?.count )?.count;
return sum + skill.damage * (count ?? 0) return sum + skill.damage * (count ?? 0);
}, 0) }, 0);
}) });
function standardValues(skill: IResultSkill) { function standardValues(skill: IResultSkill) {
const standardSkill = props.standard?.skills.find( const standardSkill = props.standard?.skills.find(
(item) => item.name === skill.name && item.mode === skill.mode, (item) => item.name === skill.name && item.mode === skill.mode,
) );
const standardCount = props.standard?.skillCount.find( const standardCount = props.standard?.skillCount.find(
(item) => item.name === skill.name && item.mode === skill.mode, (item) => item.name === skill.name && item.mode === skill.mode,
)?.count )?.count;
if (!standardSkill || standardSkill.damage <= 0 || standardSkill.cd <= 0 || !standardCount) { if (!standardSkill || standardSkill.damage <= 0 || standardSkill.cd <= 0 || !standardCount) {
return undefined return undefined;
} }
return { skill: standardSkill, count: standardCount } return { skill: standardSkill, count: standardCount };
} }
function comparisonTone(value: number): Tone { function comparisonTone(value: number): Tone {
return value >= 0 ? 'positive' : 'negative' return value >= 0 ? "positive" : "negative";
} }
function formatPercent(value: number) { function formatPercent(value: number) {
return `${(value * 100).toFixed(2)}%` return `${(value * 100).toFixed(2)}%`;
} }
function sortMetric(skill: IResultSkill, key: SortKey) { function sortMetric(skill: IResultSkill, key: SortKey) {
if (key === 'total') return skill.damage * skillCountValue(skill) if (key === "total") return skill.damage * skillCountValue(skill);
if (key === 'dps') return skill.cd > 0 ? skill.damage / skill.cd : 0 if (key === "dps") return skill.cd > 0 ? skill.damage / skill.cd : 0;
return Number(skill[key] ?? 0) return Number(skill[key] ?? 0);
} }
const sortedSkills = computed(() => const sortedSkills = computed(() =>
[...props.data].sort((left, right) => { [...props.data].sort((left, right) => {
const leftValue = sortMetric(left, sort.value) const leftValue = sortMetric(left, sort.value);
const rightValue = sortMetric(right, sort.value) const rightValue = sortMetric(right, sort.value);
return order.value === 'asc' ? leftValue - rightValue : rightValue - leftValue return order.value === "asc" ? leftValue - rightValue : rightValue - leftValue;
}), }),
) );
const rows = computed(() => const rows = computed(() =>
sortedSkills.value.map((skill) => { sortedSkills.value.map((skill) => {
const count = skillCountValue(skill) const count = skillCountValue(skill);
const currentTotal = skill.damage * count const currentTotal = skill.damage * count;
const currentDps = skill.cd > 0 ? skill.damage / skill.cd : 0 const currentDps = skill.cd > 0 ? skill.damage / skill.cd : 0;
const standard = standardValues(skill) const standard = standardValues(skill);
let dps = Math.round(currentDps).toLocaleString() let dps = Math.round(currentDps).toLocaleString();
let total = Math.round(currentTotal).toLocaleString() let total = Math.round(currentTotal).toLocaleString();
let dpsTone: Tone let dpsTone: Tone;
let totalTone: Tone let totalTone: Tone;
if (standard) { if (standard) {
const standardDps = standard.skill.damage / standard.skill.cd const standardDps = standard.skill.damage / standard.skill.cd;
const standardTotal = standard.skill.damage * standard.count const standardTotal = standard.skill.damage * standard.count;
const dpsDelta = standardDps === 0 ? 0 : (currentDps - standardDps) / standardDps const dpsDelta = standardDps === 0 ? 0 : (currentDps - standardDps) / standardDps;
const totalDelta = standardTotal === 0 ? 0 : (currentTotal - standardTotal) / standardTotal const totalDelta = standardTotal === 0 ? 0 : (currentTotal - standardTotal) / standardTotal;
dps = formatPercent(dpsDelta) dps = formatPercent(dpsDelta);
total = formatPercent(totalDelta) total = formatPercent(totalDelta);
dpsTone = comparisonTone(dpsDelta) dpsTone = comparisonTone(dpsDelta);
totalTone = comparisonTone(totalDelta) totalTone = comparisonTone(totalDelta);
} }
const share = totalDamage.value === 0 ? 0 : currentTotal / totalDamage.value const share = totalDamage.value === 0 ? 0 : currentTotal / totalDamage.value;
return { return {
key: `${skill.name}-${skill.mode ?? ''}`, key: `${skill.name}-${skill.mode ?? ""}`,
skill, skill,
displayName: `${skill.name}${skill.mode ? ` [${skill.mode}]` : ''}`, displayName: `${skill.name}${skill.mode ? ` [${skill.mode}]` : ""}`,
count, count,
cd: `${skill.cd.toFixed(1)}s`, cd: typeof skill.cd === "number" ? `${skill.cd.toFixed(1)}s` : "-",
dps, dps,
total, total,
dpsTone, dpsTone,
totalTone, totalTone,
share: formatPercent(share), share: formatPercent(share),
} };
}), }),
) );
const showTotal = computed(() => { const showTotal = computed(() => {
if (!totalStandard.value) return totalDamage.value if (!totalStandard.value) return totalDamage.value;
return ((totalDamage.value - totalStandard.value) / totalStandard.value) * 100 return ((totalDamage.value - totalStandard.value) / totalStandard.value) * 100;
}) });
const showTotalText = computed(() => const showTotalText = computed(() =>
totalStandard.value totalStandard.value
? `${showTotal.value.toFixed(2)}%` ? `${showTotal.value.toFixed(2)}%`
: Math.round(showTotal.value).toLocaleString(), : Math.round(showTotal.value).toLocaleString(),
) );
const totalTone = computed<Tone>(() => const totalTone = computed<Tone>(() =>
totalStandard.value ? comparisonTone(showTotal.value) : undefined, totalStandard.value ? comparisonTone(showTotal.value) : undefined,
) );
function toneClass(tone: Tone) { function toneClass(tone: Tone) {
return { return {
'is-positive': tone === 'positive', "is-positive": tone === "positive",
'is-negative': tone === 'negative', "is-negative": tone === "negative",
} };
} }
function sortValue(key: SortKey) { function sortValue(key: SortKey) {
if (sort.value === key) order.value = order.value === 'asc' ? 'desc' : 'asc' if (sort.value === key) order.value = order.value === "asc" ? "desc" : "asc";
else { else {
sort.value = key sort.value = key;
order.value = 'desc' order.value = "desc";
} }
} }
</script> </script>
@@ -39,7 +39,9 @@
</div> </div>
<div>{{ skill.name }} {{ !skill.mode ? '' : `[${skill.mode}]` }}</div> <div>{{ skill.name }} {{ !skill.mode ? '' : `[${skill.mode}]` }}</div>
</div> </div>
<div class="w-50px flex items-center justify-center">{{ skill.cd.toFixed(1) }}s</div> <div class="w-50px flex items-center justify-center">
{{ typeof skill.cd === 'number' ? `${skill.cd.toFixed(1)}s` : '-' }}
</div>
<div class="w-50px flex items-center justify-center"> <div class="w-50px flex items-center justify-center">
<calc-input <calc-input
type="number" type="number"
@@ -582,7 +582,7 @@ const leftPanelOpenSections = ["equipped", "stats", "suit", "equipped-list"];
</div> </div>
</td> </td>
<td class="hidden px-1.5 py-1.5 text-right sm:table-cell"> <td class="hidden px-1.5 py-1.5 text-right sm:table-cell">
{{ skill.cd.toFixed(1) }} {{ typeof skill.cd === "number" ? `${skill.cd.toFixed(1)}s` : "-" }}
</td> </td>
<template v-if="!isBufferClass"> <template v-if="!isBufferClass">
<td class="px-1.5 py-1.5 text-right"> <td class="px-1.5 py-1.5 text-right">
@@ -630,7 +630,7 @@ const leftPanelOpenSections = ["equipped", "stats", "suit", "equipped-list"];
</template> </template>
<template v-else> <template v-else>
<td class="hidden px-1.5 py-1.5 text-right sm:table-cell"> <td class="hidden px-1.5 py-1.5 text-right sm:table-cell">
{{ skill.cd.toFixed(1) }} {{ typeof skill.cd === "number" ? `${skill.cd.toFixed(1)}s` : "-" }}
</td> </td>
<td <td
class="px-1.5 py-1.5 text-right" class="px-1.5 py-1.5 text-right"