mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-06 08:32:51 +08:00
✨ 祈愿添加UP抽数数据,采取动态高度计算 (#174)
* Initial plan * feat: Add UP average pull count for 5-star items in gacha records Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * refactor: Optimize isStar5Up function by extracting Number conversion Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * refactor: Add 4-star UP average and optimize hint calculation - Add star4UpAvg variable and getStar4UpAvg() for 4-star UP average - Add getItemHint() to calculate UP/歪 hint for both 4 and 5-star items - Calculate hints in gro-data-view and pass via hint prop to gro-data-line - Remove duplicate getEndHint() calculation from gro-data-line.vue - Add UP average display in 4-star section with dynamic grid layout Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * ♻️ 调整传递数据类型,样式适配调整 * 💄 微调高度 * fix: Use flexbox for dynamic height calculation in gro-data-view Replace hardcoded height calculations with flexbox layout: - Make gro-dv-container a flex column container - Use flex: 1 for gro-bottom and gro-bottom-window to take remaining space - Add min-height: 0 to enable proper flex shrinking for scrollable areas Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * fix: Resolve v-window height overflow issue - Add overflow: hidden to .gro-bottom and .gro-bottom-window containers - Move overflow-y: auto scrolling to .gro-b-window-item level - Add height: 100% to .gro-b-window-item for proper containment Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * fix: Use dynamic height calculation via template refs - Add template refs for container and header elements - Calculate bottom and window heights dynamically based on actual DOM element sizes - Remove flexbox approach that didn't work with Vuetify v-window - Apply calculated heights via style binding Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * 🎨 useTemplateRef & v-bind --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> Co-authored-by: BTMuli <bt-muli@outlook.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
<!-- 祈愿数据项展示行 -->
|
||||
<template>
|
||||
<div class="gro-dl-box">
|
||||
<div class="gro-dl-progress" />
|
||||
@@ -10,7 +11,7 @@
|
||||
</div>
|
||||
<div class="gro-dl-info">
|
||||
<div class="gro-dl-cnt">{{ props.count }}</div>
|
||||
<div class="gro-dl-hint" v-if="hint !== ''">{{ hint }}</div>
|
||||
<div v-if="props.isUp !== undefined" class="gro-dl-hint">{{ props.isUp ? "UP" : "歪" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -18,12 +19,19 @@
|
||||
import { getWikiBrief } from "@utils/toolFunc.js";
|
||||
import { computed } from "vue";
|
||||
|
||||
import { AppGachaData } from "@/data/index.js";
|
||||
|
||||
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGacha; count: number };
|
||||
/**
|
||||
* 祈愿数据项展示行组件参数
|
||||
*/
|
||||
export type GroDataLineProps = {
|
||||
/* 原始数据 */
|
||||
data: TGApp.Sqlite.GachaRecords.TableGacha;
|
||||
/* 抽数 */
|
||||
count: number;
|
||||
/* 是否是 Up */
|
||||
isUp: boolean | undefined;
|
||||
};
|
||||
|
||||
const props = defineProps<GroDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
function getIcon(): string {
|
||||
const find = getWikiBrief(props.data.itemId);
|
||||
@@ -32,32 +40,11 @@ function getIcon(): string {
|
||||
return `/WIKI/weapon/${props.data.itemId}.webp`;
|
||||
}
|
||||
|
||||
function getEndHint(): string {
|
||||
if (props.data.gachaType === "100" || props.data.gachaType === "200") return "";
|
||||
// if (props.data.rank !== "5") return "";
|
||||
const itemTime = new Date(props.data.time).getTime();
|
||||
const poolsFind = AppGachaData.filter((pool) => {
|
||||
if (pool.type.toLocaleString() !== props.data.gachaType) return false;
|
||||
const startTime = new Date(pool.from).getTime();
|
||||
const endTime = new Date(pool.to).getTime();
|
||||
return itemTime >= startTime && itemTime <= endTime;
|
||||
});
|
||||
if (poolsFind.length === 0) return "";
|
||||
if (props.data.rank === "5") {
|
||||
if (poolsFind.some((pool) => pool.up5List.includes(Number(props.data.itemId)))) return "UP";
|
||||
return "歪";
|
||||
}
|
||||
if (props.data.rank === "4") {
|
||||
if (poolsFind.some((pool) => pool.up4List.includes(Number(props.data.itemId)))) return "UP";
|
||||
return "歪";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const progressColor = computed<string>(() => {
|
||||
if (hint === "UP" && props.data.rank === "5") return "#d19a66";
|
||||
if (hint === "UP" && props.data.rank === "4") return "#c678dd";
|
||||
if (hint === "歪") return "#e06c75";
|
||||
if (props.isUp === undefined) return "#61afef";
|
||||
if (props.isUp && props.data.rank === "5") return "#d19a66";
|
||||
if (props.isUp && props.data.rank === "4") return "#c678dd";
|
||||
if (!props.isUp) return "#e06c75";
|
||||
return "#61afef";
|
||||
});
|
||||
const progressWidth = computed<string>(() => {
|
||||
|
||||
@@ -1,51 +1,61 @@
|
||||
<!-- 祈愿数据概览 -->
|
||||
<template>
|
||||
<div class="gro-dv-container">
|
||||
<div class="gro-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gro-dvt-subtitle">
|
||||
<span v-show="props.dataVal.length === 0">暂无数据</span>
|
||||
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
|
||||
</div>
|
||||
<!-- 4星相关数据 -->
|
||||
<div class="gro-mid-list">
|
||||
<div class="gro-ml-title s4">★★★★</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
<div ref="groDvBoxRef" class="gro-dv-container">
|
||||
<div ref="headerRef" class="gro-dv-header">
|
||||
<div class="gro-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star4avg }}</span>
|
||||
<div class="gro-dvt-subtitle">
|
||||
<span v-show="props.dataVal.length === 0">暂无数据</span>
|
||||
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star4List.length }}</span>
|
||||
<!-- 4星相关数据 -->
|
||||
<div :class="{ 'has-up': isUpPool }" class="gro-mid-list">
|
||||
<div class="gro-ml-title s4">★★★★</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>均</span>
|
||||
<span>{{ star4avg }}</span>
|
||||
</div>
|
||||
<div v-if="star4UpAvg !== ''" class="gro-ml-card">
|
||||
<span>UP</span>
|
||||
<span>{{ star4UpAvg }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>总</span>
|
||||
<span>{{ star4List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 5星相关数据 -->
|
||||
<div class="gro-mid-list">
|
||||
<div class="gro-ml-title s5">★★★★★</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
<!-- 5星相关数据 -->
|
||||
<div :class="{ 'has-up': star5UpAvg !== '' }" class="gro-mid-list">
|
||||
<div class="gro-ml-title s5">★★★★★</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
</div>
|
||||
<div v-if="star5UpAvg !== ''" class="gro-ml-card">
|
||||
<span>UP</span>
|
||||
<span>{{ star5UpAvg }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>总</span>
|
||||
<span>{{ star5List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
<!-- 进度条拼接 -->
|
||||
<div v-if="props.dataVal.length > 0" class="gro-mid-progress">
|
||||
<div v-if="pg3 !== '0'" :style="{ width: pg3 }" :title="`3星占比:${pg3}`" class="s3" />
|
||||
<div v-if="pg4 !== '0'" :style="{ width: pg4 }" :title="`4星占比:${pg4}`" class="s4" />
|
||||
<div v-if="pg5 !== '0'" :style="{ width: pg5 }" :title="`5星占比:${pg5}`" class="s5" />
|
||||
</div>
|
||||
<div class="gro-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star5List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条拼接 -->
|
||||
<div v-if="props.dataVal.length > 0" class="gro-mid-progress">
|
||||
<div v-if="pg3 !== '0'" :style="{ width: pg3 }" :title="`3星占比:${pg3}`" class="s3" />
|
||||
<div v-if="pg4 !== '0'" :style="{ width: pg4 }" :title="`4星占比:${pg4}`" class="s4" />
|
||||
<div v-if="pg5 !== '0'" :style="{ width: pg5 }" :title="`5星占比:${pg5}`" class="s5" />
|
||||
</div>
|
||||
<!-- 这边放具体物品的列表 -->
|
||||
<div class="gro-bottom">
|
||||
@@ -57,14 +67,24 @@
|
||||
<v-window-item class="gro-b-window-item" value="5">
|
||||
<v-virtual-scroll :item-height="48" :items="star5List">
|
||||
<template #default="{ item }">
|
||||
<GroDataLine :key="item.data.id" :count="item.count" :data="item.data" />
|
||||
<GroDataLine
|
||||
:key="item.data.id"
|
||||
:count="item.count"
|
||||
:data="item.data"
|
||||
:is-up="item.isUp"
|
||||
/>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
<v-window-item class="gro-b-window-item" value="4">
|
||||
<v-virtual-scroll :item-height="48" :items="star4List">
|
||||
<template #default="{ item }">
|
||||
<GroDataLine :key="item.data.id" :count="item.count" :data="item.data" />
|
||||
<GroDataLine
|
||||
:key="item.data.id"
|
||||
:count="item.count"
|
||||
:data="item.data"
|
||||
:is-up="item.isUp"
|
||||
/>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
@@ -73,10 +93,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { computed, nextTick, onMounted, ref, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
import GroDataLine, { type GroDataLineProps } from "./gro-data-line.vue";
|
||||
|
||||
import { AppGachaData } from "@/data/index.js";
|
||||
|
||||
type GachaDataViewProps = {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGacha>;
|
||||
@@ -84,6 +106,14 @@ type GachaDataViewProps = {
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
// Template refs for dynamic height calculation
|
||||
const groDvBoxEl = useTemplateRef<HTMLElement>("groDvBoxRef");
|
||||
const headerEl = useTemplateRef<HTMLElement>("headerRef");
|
||||
|
||||
// Dynamic heights
|
||||
const bottomHeight = ref<string>("auto");
|
||||
const windowHeight = ref<string>("auto");
|
||||
|
||||
// data
|
||||
const loading = ref<boolean>(true); // 是否加载完
|
||||
const title = ref<string>(""); // 卡片标题
|
||||
@@ -95,15 +125,34 @@ const reset5count = ref<number>(1); // 5星垫抽数量
|
||||
const reset4count = ref<number>(1); // 4星垫抽数量
|
||||
const star3count = ref<number>(0); // 3星物品数量
|
||||
const star5avg = ref<string>(""); // 5星平均抽数
|
||||
const star5UpAvg = ref<string>(""); // 5星UP平均抽数
|
||||
const star4avg = ref<string>(""); // 4星平均抽数
|
||||
const star4UpAvg = ref<string>(""); // 4星UP平均抽数
|
||||
const tab = ref<string>("5"); // tab
|
||||
const pg3 = computed<string>(() => getPg("3"));
|
||||
const pg4 = computed<string>(() => getPg("4"));
|
||||
const pg5 = computed<string>(() => getPg("5"));
|
||||
const isUpPool = computed<boolean>(() => props.dataType !== "new" && props.dataType !== "normal");
|
||||
|
||||
onMounted(() => {
|
||||
// Calculate dynamic heights
|
||||
function calculateHeights(): void {
|
||||
if (!groDvBoxEl.value || !headerEl.value) return;
|
||||
const containerHeight = groDvBoxEl.value.clientHeight;
|
||||
const headerHeight = headerEl.value.clientHeight;
|
||||
const padding = 20; // 8px padding top + 8px padding bottom + 4px magic
|
||||
const tabsHeight = 36; // v-tabs compact height
|
||||
const gap = 8; // gap between tabs and window
|
||||
const bottomHeightPx = containerHeight - headerHeight - padding;
|
||||
const windowHeightPx = bottomHeightPx - tabsHeight - gap;
|
||||
bottomHeight.value = `${bottomHeightPx}px`;
|
||||
windowHeight.value = `${windowHeightPx}px`;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadData();
|
||||
loading.value = false;
|
||||
await nextTick();
|
||||
calculateHeights();
|
||||
});
|
||||
|
||||
function loadData(): void {
|
||||
@@ -125,18 +174,20 @@ function loadData(): void {
|
||||
star3count.value++;
|
||||
} else if (item.rank === "4") {
|
||||
reset5count.value++;
|
||||
temp4Data.push({ data: item, count: reset4count.value });
|
||||
temp4Data.push({ data: item, count: reset4count.value, isUp: checkIsUp(item) });
|
||||
reset4count.value = 1;
|
||||
} else if (item.rank === "5") {
|
||||
reset4count.value++;
|
||||
temp5Data.push({ data: item, count: reset5count.value });
|
||||
temp5Data.push({ data: item, count: reset5count.value, isUp: checkIsUp(item) });
|
||||
reset5count.value = 1;
|
||||
}
|
||||
});
|
||||
star5List.value = temp5Data.reverse();
|
||||
star4List.value = temp4Data.reverse();
|
||||
star5avg.value = getStar5Avg();
|
||||
star5UpAvg.value = getStar5UpAvg();
|
||||
star4avg.value = getStar4Avg();
|
||||
star4UpAvg.value = getStar4UpAvg();
|
||||
}
|
||||
|
||||
// 获取标题
|
||||
@@ -157,6 +208,42 @@ function getStar5Avg(): string {
|
||||
return (total / star5List.value.length).toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是Up物品
|
||||
* @param {TGApp.Sqlite.GachaRecords.TableGacha} item 原始数据
|
||||
* @returns {boolean|undefined}
|
||||
*/
|
||||
function checkIsUp(item: TGApp.Sqlite.GachaRecords.TableGacha): boolean | undefined {
|
||||
// 新手池和常驻池不存在UP概念
|
||||
if (item.gachaType === "100" || item.gachaType === "200") return undefined;
|
||||
const itemTime = new Date(item.time).getTime();
|
||||
const itemIdNum = Number(item.itemId);
|
||||
const poolsFind = AppGachaData.filter((pool) => {
|
||||
if (pool.type.toLocaleString() !== item.gachaType) return false;
|
||||
const startTime = new Date(pool.from).getTime();
|
||||
const endTime = new Date(pool.to).getTime();
|
||||
return itemTime >= startTime && itemTime <= endTime;
|
||||
});
|
||||
if (poolsFind.length === 0) return undefined;
|
||||
if (item.rank === "5") {
|
||||
return poolsFind.some((pool) => pool.up5List.includes(itemIdNum));
|
||||
}
|
||||
if (item.rank === "4") {
|
||||
return poolsFind.some((pool) => pool.up4List.includes(itemIdNum));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 获取5星UP平均抽数
|
||||
function getStar5UpAvg(): string {
|
||||
// 新手池和常驻池不显示UP平均
|
||||
if (props.dataType === "new" || props.dataType === "normal") return "";
|
||||
const upList = star5List.value.filter((item) => item.isUp);
|
||||
if (upList.length === 0) return "0";
|
||||
const total = upList.reduce((a, b) => a + b.count, 0);
|
||||
return (total / upList.length).toFixed(2);
|
||||
}
|
||||
|
||||
// 获取4星平均抽数
|
||||
function getStar4Avg(): string {
|
||||
const resetList = star4List.value.map((item) => item.count);
|
||||
@@ -165,6 +252,16 @@ function getStar4Avg(): string {
|
||||
return (total / star4List.value.length).toFixed(2);
|
||||
}
|
||||
|
||||
// 获取4星UP平均抽数
|
||||
function getStar4UpAvg(): string {
|
||||
// 新手池和常驻池不显示UP平均
|
||||
if (props.dataType === "new" || props.dataType === "normal") return "";
|
||||
const upList = star4List.value.filter((item) => item.isUp);
|
||||
if (upList.length === 0) return "0";
|
||||
const total = upList.reduce((a, b) => a + b.count, 0);
|
||||
return (total / upList.length).toFixed(2);
|
||||
}
|
||||
|
||||
// 获取占比
|
||||
function getPg(star: "5" | "4" | "3"): string {
|
||||
let progress: number;
|
||||
@@ -182,7 +279,7 @@ function getPg(star: "5" | "4" | "3"): string {
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.dataVal,
|
||||
() => {
|
||||
async () => {
|
||||
star5List.value = [];
|
||||
star4List.value = [];
|
||||
reset5count.value = 1;
|
||||
@@ -191,14 +288,19 @@ watch(
|
||||
startDate.value = "";
|
||||
endDate.value = "";
|
||||
star5avg.value = "";
|
||||
star5UpAvg.value = "";
|
||||
star4avg.value = "";
|
||||
star4UpAvg.value = "";
|
||||
tab.value = "5";
|
||||
loadData();
|
||||
await nextTick();
|
||||
calculateHeights();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-dv-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
@@ -206,6 +308,10 @@ watch(
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.gro-dv-header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gro-dvt-title {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@@ -225,12 +331,16 @@ watch(
|
||||
|
||||
.gro-mid-list {
|
||||
display: grid;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--box-text-7);
|
||||
column-gap: 12px;
|
||||
column-gap: 4px;
|
||||
font-size: 14px;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
|
||||
&.has-up {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.gro-ml-title {
|
||||
@@ -294,7 +404,7 @@ watch(
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100% - 120px);
|
||||
height: v-bind(bottomHeight); /* stylelint-disable-line value-keyword-case */
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -302,13 +412,14 @@ watch(
|
||||
|
||||
.gro-bottom-window {
|
||||
position: relative;
|
||||
height: calc(100vh - 380px);
|
||||
height: v-bind(windowHeight); /* stylelint-disable-line value-keyword-case */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gro-b-window-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user