添加简略模式切换功能,优化战斗和卡片展示

This commit is contained in:
BTMuli
2026-04-01 22:40:22 +08:00
parent 34dde3b3b9
commit 234c13ee11
4 changed files with 337 additions and 46 deletions

View File

@@ -1,30 +1,58 @@
<!-- 剧诗辉彩祝福 -->
<template>
<div class="tuc-buff-box">
<div class="tuc-buff-item">
<div class="tuc-buff-summary">
<div class="tuc-buff-icon">
<img alt="total" class="summary" src="/UI/combat/combatCrown.webp" />
</div>
<div class="tuc-buff-desc">
<span>辉彩祝福</span>
</div>
</div>
<div class="tuc-buff-total" v-html="getBuffDesc(props.modelValue.summary.desc)" />
<div ref="containerRef" :class="['tuc-buff-box', { 'simple-mode': props.simpleMode }]">
<div class="tuc-buff-title">
<span>辉彩祝福</span>
<span v-if="props.simpleMode">Lv.{{ props.modelValue.summary.total_level }}</span>
</div>
<template v-for="(buff, idx) in props.modelValue.buffs" :key="idx">
<div v-if="buff.level > 1" class="tuc-buff-item">
<div :class="{ 'simple-mode': props.simpleMode }" class="tuc-buff-list">
<div
:data-key="'summary'"
class="tuc-buff-item"
:class="{ 'is-summary': true }"
>
<div class="tuc-buff-summary">
<div class="tuc-buff-icon">
<img :alt="buff.name" :src="buff.icon" />
<div
class="tuc-buff-icon"
:title="props.simpleMode ? `辉彩祝福 Lv.${props.modelValue.summary.total_level}` : undefined"
>
<img alt="total" class="summary" src="/UI/combat/combatCrown.webp" />
<span v-if="props.simpleMode" class="tuc-buff-level-badge">
{{ props.modelValue.summary.total_level }}
</span>
</div>
<div class="tuc-buff-desc">
<div v-show="!props.simpleMode" class="tuc-buff-desc">
<span>辉彩祝福</span>
<span>Lv.{{ props.modelValue.summary.total_level }}</span>
</div>
<span v-if="props.simpleMode" class="tuc-buff-name">辉彩祝福</span>
</div>
<div v-show="!props.simpleMode" class="tuc-buff-total" v-html="getBuffDesc(props.modelValue.summary.desc)" />
</div>
<div
v-for="buff in props.modelValue.buffs"
:key="buff.icon"
:data-key="buff.icon"
class="tuc-buff-item"
>
<div class="tuc-buff-summary">
<div
class="tuc-buff-icon"
:title="props.simpleMode ? `${buff.name} Lv.${buff.level}` : undefined"
>
<img :alt="buff.name" :src="buff.icon" />
<span v-if="props.simpleMode" class="tuc-buff-level-badge">
{{ buff.level }}
</span>
</div>
<div v-show="!props.simpleMode" class="tuc-buff-desc">
<span>{{ buff.name }}</span>
<span>Lv.{{ buff.level }}</span>
</div>
<span v-if="props.simpleMode" class="tuc-buff-name">{{ buff.name }}</span>
</div>
<div class="tuc-buff-detail" @click="console.log(buff)">
<div v-for="(effect, idx) in buff.level_effect" :key="idx" class="tuc-effect-item">
<div v-show="!props.simpleMode" class="tuc-buff-detail">
<div v-for="(effect, eIdx) in buff.level_effect" :key="eIdx" class="tuc-effect-item">
<div class="tuc-effect-title">
<img :src="effect.icon" alt="icon" />
<span v-html="parseHtmlText(effect.name)" />
@@ -33,19 +61,24 @@
</div>
</div>
</div>
</template>
</div>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onUnmounted, ref, watch } from "vue";
import { parseHtmlText } from "@utils/toolFunc.js";
type TucBuffBoxProps = {
/* 辉彩祝福数据 */
modelValue: TGApp.Game.Combat.SplendourBuff;
simpleMode?: boolean;
};
const props = defineProps<TucBuffBoxProps>();
const containerRef = ref<HTMLDivElement>();
let animationFrameId: number | null = null;
function getBuffDesc(desc: string): string {
return parseHtmlText(desc.replaceAll("点,", "点,\n"));
}
@@ -53,12 +86,74 @@ function getBuffDesc(desc: string): string {
function getEffectDesc(desc: string): string {
return parseHtmlText(desc.replaceAll("", "\n")).replaceAll("\n<br />", "<br />");
}
watch(
() => props.simpleMode,
async (newVal, oldVal) => {
if (oldVal === undefined || !containerRef.value) return;
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
const items = containerRef.value.querySelectorAll<HTMLDivElement>(".tuc-buff-item");
const firstRects = new Map<string, DOMRect>();
items.forEach((el) => {
const key = el.dataset.key;
if (key) {
firstRects.set(key, el.getBoundingClientRect());
}
});
await nextTick();
const animations: Array<{ el: HTMLDivElement }> = [];
items.forEach((el) => {
const key = el.dataset.key;
if (!key) return;
const firstRect = firstRects.get(key);
if (!firstRect) return;
const lastRect = el.getBoundingClientRect();
const deltaX = firstRect.left - lastRect.left;
const deltaY = firstRect.top - lastRect.top;
if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
el.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
el.style.transition = "none";
animations.push({ el });
}
});
if (animations.length > 0) {
containerRef.value.offsetHeight;
animationFrameId = requestAnimationFrame(() => {
animations.forEach(({ el }) => {
el.style.transition = "transform 0.3s ease";
el.style.transform = "";
});
animationFrameId = null;
});
}
},
);
onUnmounted(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
});
</script>
<style lang="css" scoped>
.tuc-buff-box {
position: relative;
display: flex;
width: fit-content;
width: 100%;
box-sizing: border-box;
flex: 2;
flex-direction: column;
@@ -69,6 +164,38 @@ function getEffectDesc(desc: string): string {
border-radius: 4px;
background: var(--box-bg-2);
row-gap: 8px;
overflow: hidden;
}
.tuc-buff-box.simple-mode {
flex: 1;
}
.tuc-buff-title {
color: var(--box-text-2);
font-family: var(--font-title);
}
.tuc-buff-title span:last-child {
margin-left: 4px;
color: var(--tgc-od-orange);
}
.tuc-buff-list {
display: flex;
width: 100%;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
row-gap: 8px;
}
.tuc-buff-list.simple-mode {
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
gap: 8px;
}
.tuc-buff-item {
@@ -81,6 +208,12 @@ function getEffectDesc(desc: string): string {
column-gap: 8px;
}
.tuc-buff-list.simple-mode .tuc-buff-item {
width: auto;
flex-direction: column;
align-items: center;
}
.tuc-buff-summary {
position: relative;
display: flex;
@@ -98,12 +231,32 @@ function getEffectDesc(desc: string): string {
padding: 4px;
border-radius: 4px;
background-color: var(--box-bg-3);
cursor: default;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.tuc-buff-icon img {
width: 100%;
height: 100%;
object-fit: cover;
}
.tuc-buff-level-badge {
position: absolute;
right: 0;
bottom: 0;
padding: 1px 4px;
border-radius: 4px;
background-color: var(--tgc-od-orange);
color: white;
font-size: 10px;
font-weight: bold;
}
.tuc-buff-name {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 12px;
text-align: center;
}
.tuc-buff-desc {
@@ -132,6 +285,11 @@ function getEffectDesc(desc: string): string {
white-space: pre-wrap;
}
.tuc-effect-item {
display: flex;
flex-direction: column;
}
.tuc-effect-title {
position: relative;
display: flex;
@@ -140,12 +298,12 @@ function getEffectDesc(desc: string): string {
column-gap: 4px;
font-family: var(--font-title);
font-size: 12px;
}
img {
width: 16px;
height: 16px;
filter: invert(0.6);
}
.tuc-effect-title img {
width: 16px;
height: 16px;
filter: invert(0.6);
}
.dark .tuc-effect-title img {

View File

@@ -1,13 +1,28 @@
<!-- 剧诗神秘收获 -->
<template>
<div v-if="props.modelValue.length > 0" class="tuc-card-box">
<div
v-if="props.modelValue.length > 0"
ref="containerRef"
:class="['tuc-card-box', { 'simple-mode': props.simpleMode }]"
>
<div class="tuc-card-title">神秘收获 {{ props.modelValue.length }}</div>
<div class="tuc-card-list">
<div v-for="(card, idx) in props.modelValue" :key="idx" class="tuc-card-item">
<div class="tuc-ci-icon">
<img :src="card.icon" alt="icon" />
<div :class="{ 'simple-mode': props.simpleMode }" class="tuc-card-list">
<div
v-for="card in props.modelValue"
:key="card.id"
:data-key="card.id"
class="tuc-card-item"
>
<div class="tuc-card-summary">
<div
class="tuc-ci-icon"
:title="props.simpleMode ? card.name : undefined"
>
<img :src="card.icon" alt="icon" />
</div>
<span v-if="props.simpleMode" class="tuc-card-name">{{ card.name }}</span>
</div>
<div class="tuc-ci-info">
<div v-show="!props.simpleMode" class="tuc-ci-info">
<div class="tuc-ci-title">{{ card.name }}</div>
<div class="tuc-ci-desc" v-html="parseHtmlText(card.desc)" />
</div>
@@ -16,14 +31,81 @@
</div>
</template>
<script lang="ts" setup>
import { nextTick, onUnmounted, ref, watch } from "vue";
import { parseHtmlText } from "@utils/toolFunc.js";
type TucCardBoxProps = {
/* 神秘收获数据 */
modelValue: Array<TGApp.Game.Combat.Card>;
simpleMode?: boolean;
};
const props = defineProps<TucCardBoxProps>();
const containerRef = ref<HTMLDivElement>();
let animationFrameId: number | null = null;
watch(
() => props.simpleMode,
async (newVal, oldVal) => {
if (oldVal === undefined || !containerRef.value) return;
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
const items = containerRef.value.querySelectorAll<HTMLDivElement>(".tuc-card-item");
const firstRects = new Map<number, DOMRect>();
items.forEach((el) => {
const key = el.dataset.key;
if (key) {
firstRects.set(Number(key), el.getBoundingClientRect());
}
});
await nextTick();
const animations: Array<{ el: HTMLDivElement }> = [];
items.forEach((el) => {
const key = el.dataset.key;
if (!key) return;
const firstRect = firstRects.get(Number(key));
if (!firstRect) return;
const lastRect = el.getBoundingClientRect();
const deltaX = firstRect.left - lastRect.left;
const deltaY = firstRect.top - lastRect.top;
if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
el.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
el.style.transition = "none";
animations.push({ el });
}
});
if (animations.length > 0) {
containerRef.value.offsetHeight;
animationFrameId = requestAnimationFrame(() => {
animations.forEach(({ el }) => {
el.style.transition = "transform 0.3s ease";
el.style.transform = "";
});
animationFrameId = null;
});
}
},
);
onUnmounted(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
});
</script>
<style lang="scss" scoped>
.tuc-card-box {
@@ -40,6 +122,7 @@ const props = defineProps<TucCardBoxProps>();
border-radius: 4px;
background: var(--box-bg-2);
row-gap: 8px;
overflow: hidden;
}
.tuc-card-title {
@@ -57,6 +140,14 @@ const props = defineProps<TucCardBoxProps>();
row-gap: 8px;
}
.tuc-card-list.simple-mode {
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
gap: 8px;
}
.tuc-card-item {
position: relative;
display: flex;
@@ -66,6 +157,20 @@ const props = defineProps<TucCardBoxProps>();
column-gap: 8px;
}
.tuc-card-list.simple-mode .tuc-card-item {
flex-direction: column;
align-items: center;
}
.tuc-card-summary {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
row-gap: 4px;
}
.tuc-ci-icon {
position: relative;
width: 60px;
@@ -74,19 +179,27 @@ const props = defineProps<TucCardBoxProps>();
padding: 4px;
border-radius: 4px;
background-color: var(--box-bg-3);
cursor: default;
}
img {
width: 100%;
height: 100%;
filter: invert(1);
object-fit: cover;
}
.tuc-ci-icon img {
width: 100%;
height: 100%;
filter: invert(1);
object-fit: cover;
}
.dark .tuc-ci-icon img {
filter: unset;
}
.tuc-card-name {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 12px;
text-align: center;
}
.tuc-ci-title {
position: relative;
font-family: var(--font-title);

View File

@@ -19,8 +19,9 @@
<TucBuffBox
:class="props.round.choice_cards.length === 0 ? 'fill' : ''"
:model-value="props.round.splendour_buff"
:simple-mode="props.simpleMode"
/>
<TucCardBox :model-value="props.round.choice_cards" />
<TucCardBox :model-value="props.round.choice_cards" :simple-mode="props.simpleMode" />
</div>
</div>
</template>
@@ -34,7 +35,12 @@ import TucAeBox from "./tuc-ae-box.vue";
import TucBuffBox from "./tuc-buff-box.vue";
import TucCardBox from "./tuc-card-box.vue";
type TucRoundProps = { round: TGApp.Game.Combat.RoundData; uid: string; id: number };
type TucRoundProps = {
round: TGApp.Game.Combat.RoundData;
uid: string;
id: number;
simpleMode?: boolean;
};
const props = defineProps<TucRoundProps>();
const showInfo = ref<boolean>(false);
const tucrRef = useTemplateRef<HTMLDivElement>("tucrEl");

View File

@@ -72,6 +72,13 @@
<img alt="char" src="/UI/combat/tarotDefault.webp" />
<span>月谕圣牌</span>
</v-btn>
<v-switch
v-model="simpleMode"
class="uc-switch"
color="var(--tgc-od-orange)"
hide-details
label="简略模式"
/>
</div>
<div class="uct-extension-right">
<span @click="tryLoginHutao()">{{ userName ?? "登录胡桃云" }}</span>
@@ -129,6 +136,7 @@
:id="item.id"
:key="idx"
:round="round"
:simpleMode
:uid="item.uid"
/>
</div>
@@ -190,6 +198,7 @@ const showChar = ref<boolean>(false);
const charMasters = shallowRef<Array<TGApp.Game.Combat.CharMaster>>([]);
const showTarot = ref<boolean>(false);
const tarotStat = shallowRef<TGApp.Game.Combat.TarotState>();
const simpleMode = ref<boolean>(false);
onMounted(async () => {
await showLoading.start("正在加载剧诗数据");
@@ -622,6 +631,7 @@ function isFinTarot(data: TGApp.Sqlite.Combat.TableTrans): boolean {
height: 100%;
padding-right: 8px;
overflow-y: auto;
overflow-x: hidden;
}
.ucw-i-ref {
@@ -696,4 +706,8 @@ function isFinTarot(data: TGApp.Sqlite.Combat.TableTrans): boolean {
font-size: 1.5rem;
row-gap: 12px;
}
.uc-switch {
margin-left: 8px;
}
</style>