mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-20 04:39:45 +08:00
🚸重构千星页面,高度动态计算&活动次数计算共享 (#186)
* Initial plan * Fix event pool pity count sharing for male/female gacha pools Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * Use gachaType for pool filtering and simplify data passing Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * Add dynamic height calculation and window resize listener to GbrDataView Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com> * 🎨 Code style * 🚸 没有数据更新时不刷新 --------- 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,69 +1,71 @@
|
||||
<!-- 颂愿数据概览 TODO: 页面高度动态计算,参考GroDataView -->
|
||||
<!-- 颂愿数据概览 -->
|
||||
<template>
|
||||
<div class="gbr-dv-container">
|
||||
<div class="gbr-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gbr-dvt-subtitle">
|
||||
<span v-show="props.dataVal.length === 0">暂无数据</span>
|
||||
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
|
||||
</div>
|
||||
<!-- 3星相关数据 -->
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-title s3">★★★</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset3count - 1 }}</span>
|
||||
<div ref="gbrDvBoxRef" class="gbr-dv-container">
|
||||
<div ref="headerRef" class="gbr-dv-header">
|
||||
<div class="gbr-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star3avg }}</span>
|
||||
<div class="gbr-dvt-subtitle">
|
||||
<span v-show="props.dataVal.length === 0">暂无数据</span>
|
||||
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star3List.length }}</span>
|
||||
<!-- 3星相关数据 -->
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-title s3">★★★</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset3count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star3avg }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star3List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 4星相关数据 -->
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-title s4">★★★★</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
<!-- 4星相关数据 -->
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-title s4">★★★★</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star4avg }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star4List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star4avg }}</span>
|
||||
<!-- 5星相关数据 -->
|
||||
<div v-if="!isNormalPool" class="gbr-mid-list">
|
||||
<div class="gbr-ml-title s5">★★★★★</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star5List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star4List.length }}</span>
|
||||
<!-- 进度条拼接 -->
|
||||
<div v-if="props.dataVal.length > 0" class="gbr-mid-progress">
|
||||
<div v-if="pg2 !== '0'" :style="{ width: pg2 }" :title="`2星占比:${pg2}`" class="s2" />
|
||||
<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>
|
||||
<!-- 5星相关数据 -->
|
||||
<div v-if="!isNormalPool" class="gbr-mid-list">
|
||||
<div class="gbr-ml-title s5">★★★★★</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>已垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>平均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-card">
|
||||
<span>统计</span>
|
||||
<span>{{ star5List.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 进度条拼接 -->
|
||||
<div v-if="props.dataVal.length > 0" class="gbr-mid-progress">
|
||||
<div v-if="pg2 !== '0'" :style="{ width: pg2 }" :title="`2星占比:${pg2}`" class="s2" />
|
||||
<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="gbr-bottom">
|
||||
<v-tabs v-model="tab" density="compact">
|
||||
@@ -98,7 +100,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { computed, nextTick, onMounted, ref, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
import GbrDataLine, { type GbrDataLineProps } from "./gbr-data-line.vue";
|
||||
|
||||
@@ -108,8 +111,17 @@ type GachaDataViewProps = {
|
||||
};
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
const curWin = getCurrentWindow();
|
||||
const isNormalPool = computed<boolean>(() => props.dataType === "normal");
|
||||
|
||||
// Template refs for dynamic height calculation
|
||||
const gbrDvBoxEl = useTemplateRef<HTMLElement>("gbrDvBoxRef");
|
||||
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>(""); // 卡片标题
|
||||
@@ -131,9 +143,38 @@ const pg3 = computed<string>(() => getPg("3"));
|
||||
const pg4 = computed<string>(() => getPg("4"));
|
||||
const pg5 = computed<string>(() => getPg("5"));
|
||||
|
||||
onMounted(() => {
|
||||
// Calculate dynamic heights
|
||||
function calculateHeights(): void {
|
||||
if (!gbrDvBoxEl.value || !headerEl.value) return;
|
||||
const containerHeight = gbrDvBoxEl.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 - 8;
|
||||
const windowHeightPx = bottomHeightPx - tabsHeight - gap;
|
||||
bottomHeight.value = `${bottomHeightPx}px`;
|
||||
windowHeight.value = `${windowHeightPx}px`;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadData();
|
||||
loading.value = false;
|
||||
await nextTick();
|
||||
calculateHeights();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [props.dataVal, props.dataType],
|
||||
async () => {
|
||||
await nextTick();
|
||||
calculateHeights();
|
||||
},
|
||||
);
|
||||
|
||||
curWin.onResized(async () => {
|
||||
await nextTick();
|
||||
calculateHeights();
|
||||
});
|
||||
|
||||
function loadData(): void {
|
||||
@@ -142,31 +183,86 @@ function loadData(): void {
|
||||
const temp5Data: Array<GbrDataLineProps> = [];
|
||||
const temp4Data: Array<GbrDataLineProps> = [];
|
||||
const temp3Data: Array<GbrDataLineProps> = [];
|
||||
// 按照 id 升序
|
||||
|
||||
// Create a map to store pity counts for each item by ID
|
||||
const pityMap = new Map<string, { count5: number; count4: number; count3: number }>();
|
||||
let currentReset5 = 1;
|
||||
let currentReset4 = 1;
|
||||
let currentReset3 = 1;
|
||||
|
||||
// First pass: calculate pity counts using all pool data
|
||||
tempData
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
.forEach((item) => {
|
||||
if (item.rank === "2") {
|
||||
currentReset3++;
|
||||
currentReset4++;
|
||||
currentReset5++;
|
||||
} else if (item.rank === "3") {
|
||||
currentReset4++;
|
||||
currentReset5++;
|
||||
pityMap.set(item.id, {
|
||||
count3: currentReset3,
|
||||
count4: currentReset4,
|
||||
count5: currentReset5,
|
||||
});
|
||||
currentReset3 = 1;
|
||||
} else if (item.rank === "4") {
|
||||
currentReset5++;
|
||||
pityMap.set(item.id, {
|
||||
count3: currentReset3,
|
||||
count4: currentReset4,
|
||||
count5: currentReset5,
|
||||
});
|
||||
currentReset4 = 1;
|
||||
} else if (item.rank === "5") {
|
||||
currentReset4++;
|
||||
pityMap.set(item.id, {
|
||||
count3: currentReset3,
|
||||
count4: currentReset4,
|
||||
count5: currentReset5,
|
||||
});
|
||||
currentReset5 = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Store current reset counts for display
|
||||
reset5count.value = currentReset5;
|
||||
reset4count.value = currentReset4;
|
||||
reset3count.value = currentReset3;
|
||||
|
||||
// Helper function to check if item should be displayed based on dataType
|
||||
function shouldDisplay(item: TGApp.Sqlite.GachaRecords.TableGachaB): boolean {
|
||||
if (props.dataType === "normal") return true;
|
||||
if (props.dataType === "boy") {
|
||||
return item.opGachaType === "20011" || item.opGachaType === "20012";
|
||||
}
|
||||
if (props.dataType === "girl") {
|
||||
return item.opGachaType === "20021" || item.opGachaType === "20022";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Second pass: build display data, filtering by dataType for event pools
|
||||
tempData
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
.forEach((item) => {
|
||||
// Only process items that should be displayed for this view
|
||||
if (!shouldDisplay(item)) return;
|
||||
|
||||
// 处理时间
|
||||
if (startDate.value === "" || item.time < startDate.value) startDate.value = item.time;
|
||||
if (endDate.value === "" || item.time > endDate.value) endDate.value = item.time;
|
||||
|
||||
const pityCounts = pityMap.get(item.id);
|
||||
if (item.rank === "2") {
|
||||
star2count.value++;
|
||||
reset3count.value++;
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
} else if (item.rank === "3") {
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
temp3Data.push({ data: item, count: reset3count.value });
|
||||
reset3count.value = 1;
|
||||
} else if (item.rank === "4") {
|
||||
reset5count.value++;
|
||||
temp4Data.push({ data: item, count: reset4count.value });
|
||||
reset4count.value = 1;
|
||||
} else if (item.rank === "5") {
|
||||
reset4count.value++;
|
||||
temp5Data.push({ data: item, count: reset5count.value });
|
||||
reset5count.value = 1;
|
||||
} else if (item.rank === "3" && pityCounts) {
|
||||
temp3Data.push({ data: item, count: pityCounts.count3 });
|
||||
} else if (item.rank === "4" && pityCounts) {
|
||||
temp4Data.push({ data: item, count: pityCounts.count4 });
|
||||
} else if (item.rank === "5" && pityCounts) {
|
||||
temp5Data.push({ data: item, count: pityCounts.count5 });
|
||||
}
|
||||
});
|
||||
star5List.value = temp5Data.reverse();
|
||||
@@ -228,7 +324,7 @@ function getStar3Avg(): string {
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.dataVal,
|
||||
() => {
|
||||
async () => {
|
||||
star5List.value = [];
|
||||
star4List.value = [];
|
||||
reset5count.value = 1;
|
||||
@@ -241,11 +337,14 @@ watch(
|
||||
star3avg.value = "";
|
||||
tab.value = isNormalPool.value ? "4" : "5";
|
||||
loadData();
|
||||
await nextTick();
|
||||
calculateHeights();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gbr-dv-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
@@ -253,6 +352,10 @@ watch(
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.gbr-dv-header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gbr-dvt-title {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@@ -349,7 +452,7 @@ watch(
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100% - 150px);
|
||||
height: v-bind(bottomHeight); /* stylelint-disable-line value-keyword-case */
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -357,7 +460,7 @@ watch(
|
||||
|
||||
.gbr-bottom-window {
|
||||
position: relative;
|
||||
height: calc(100vh - 428px);
|
||||
height: v-bind(windowHeight); /* stylelint-disable-line value-keyword-case */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<!-- 千星奇域祈愿概览组件 -->
|
||||
<template>
|
||||
<div class="gro-o-container">
|
||||
<GbrDataView :data-val="normalData" data-type="normal" />
|
||||
<GbrDataView :data-val="boyData" data-type="boy" />
|
||||
<GbrDataView :data-val="girlData" data-type="girl" />
|
||||
<GbrDataView :data-val="eventData" data-type="boy" />
|
||||
<GbrDataView :data-val="eventData" data-type="girl" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -15,18 +14,17 @@ type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGac
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "1000"),
|
||||
props.modelValue.filter((item) => item.gachaType === "1000"),
|
||||
);
|
||||
const girlData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "20011" || item.opGachaType === "20012"),
|
||||
);
|
||||
const boyData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "20021" || item.opGachaType === "20022"),
|
||||
const eventData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.gachaType === "2000"),
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-o-container {
|
||||
position: relative;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
column-gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@@ -425,9 +425,13 @@ async function checkData(): Promise<void> {
|
||||
}
|
||||
}
|
||||
await showLoading.end();
|
||||
showSnackbar.success(`成功补充遗漏数据${cnt}条,失败${fail}条,即将刷新`);
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
||||
window.location.reload();
|
||||
if (cnt > 0 || fail > 0) {
|
||||
showSnackbar.success(`成功补充遗漏数据${cnt}条,失败${fail}条,即将刷新`);
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
||||
window.location.reload();
|
||||
} else {
|
||||
showSnackbar.success(`成功检测${gachaListCur.value.length}条数据,无需更新`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<!-- 千星奇域祈愿记录页面 -->
|
||||
<!-- 千星奇域祈愿记录页面 TODO:处理活动卡池次数共享 -->
|
||||
<template>
|
||||
<v-app-bar>
|
||||
<template #prepend>
|
||||
<div class="gb-top-title">
|
||||
<img class="gb-top-byd" src="/icon/nation/千星奇域.webp" alt="byd" />
|
||||
<img alt="byd" class="gb-top-byd" src="/icon/nation/千星奇域.webp" />
|
||||
<span>祈愿记录</span>
|
||||
<v-select
|
||||
:hide-details="true"
|
||||
density="compact"
|
||||
v-model="uidCur"
|
||||
:hide-details="true"
|
||||
:items="selectItem"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
label="游戏UID"
|
||||
variant="outlined"
|
||||
/>
|
||||
<img src="/source/UI/userGacha.webp" alt="gacha" @click="toGacha()" title="祈愿" />
|
||||
<img alt="gacha" src="/source/UI/userGacha.webp" title="祈愿" @click="toGacha()" />
|
||||
</div>
|
||||
</template>
|
||||
<template #extension>
|
||||
<div class="gb-top-btns">
|
||||
<v-btn prepend-icon="mdi-refresh" class="gb-top-btn" @click="confirmRefresh(false)">
|
||||
<v-btn class="gb-top-btn" prepend-icon="mdi-refresh" @click="confirmRefresh(false)">
|
||||
增量刷新
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-refresh" class="gb-top-btn" @click="confirmRefresh(true)">
|
||||
<v-btn class="gb-top-btn" prepend-icon="mdi-refresh" @click="confirmRefresh(true)">
|
||||
全量刷新
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-delete" class="gb-top-btn" @click="deleteGacha()">删除</v-btn>
|
||||
<v-btn class="gb-top-btn" prepend-icon="mdi-delete" @click="deleteGacha()">删除</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
@@ -33,16 +33,16 @@
|
||||
<v-tab value="overview">数据概览</v-tab>
|
||||
<v-tab value="table">数据表格</v-tab>
|
||||
<!-- TODO: 暂时隐藏内置祈愿链接 -->
|
||||
<v-tab value="iframe" v-if="false">祈愿详情</v-tab>
|
||||
<v-tab v-if="false" value="iframe">祈愿详情</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="gb-window">
|
||||
<v-window-item value="overview" class="gb-window-item">
|
||||
<v-window-item class="gb-window-item" value="overview">
|
||||
<gbr-overview v-model="gachaListCur" />
|
||||
</v-window-item>
|
||||
<v-window-item value="table" class="gb-window-item">
|
||||
<v-window-item class="gb-window-item" value="table">
|
||||
<gbr-table v-model="gachaListCur" />
|
||||
</v-window-item>
|
||||
<v-window-item value="iframe" class="gb-window-item">
|
||||
<v-window-item class="gb-window-item" value="iframe">
|
||||
<gro-iframe mode="beyond" />
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
|
||||
Reference in New Issue
Block a user