千星奇域页面

This commit is contained in:
BTMuli
2025-10-27 19:42:02 +08:00
parent 6eab6c81f1
commit a368223805
13 changed files with 1222 additions and 176 deletions

View File

@@ -0,0 +1,144 @@
<!-- 千星奇域概览单项组件 -->
<template>
<div class="gbr-dl-box">
<div class="gbr-dl-progress" />
<div class="gbr-dl-icon">
<img :alt="props.data.name" :src="getIcon()" />
</div>
<div class="gbr-dl-base">
<div class="gbr-dl-name">{{ props.data.name }}</div>
<div class="gbr-dl-time">{{ props.data.time }}</div>
</div>
<div class="gbr-dl-info">
<div class="gbr-dl-cnt">{{ props.count }}</div>
<div class="gbr-dl-hint" v-if="hint !== ''">{{ hint }}</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
export type GbrDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGachaB; count: number };
const props = defineProps<GbrDataLineProps>();
const hint = getEndHint();
function getIcon(): string {
console.log(props.data);
// const find = AppGachaBData.find((i) => i.id.toString() === props.data.itemId);
// if (!find) return `/source/UI/paimon.webp`;
// return `https://api.hakush.in/gi/UI/${find.icon}.webp`;
// TODO: 缺失元数据
return `/source/UI/paimon.webp`;
}
function getEndHint(): string {
if (props.data.gachaType === "1000") return "";
if (!props.data.isUp) 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";
return "#61afef";
});
const progressWidth = computed<string>(() => {
let final = 10;
if (props.data.rank === "5") {
if (props.data.gachaType === "302") final = 80;
else final = 90;
} else if (props.data.rank === "4") final = 10;
else return "0%";
return ((props.count / final) * 100).toFixed(2) + "%";
});
</script>
<style lang="scss" scoped>
.gbr-dl-box {
position: relative;
display: flex;
width: 100%;
height: 48px;
box-sizing: border-box;
align-items: center;
justify-content: flex-start;
padding: 8px;
border: 1px solid var(--common-shadow-1);
border-radius: 4px;
background: var(--box-bg-2);
column-gap: 4px;
}
.gbr-dl-progress {
position: absolute;
bottom: 0;
left: 0;
width: v-bind(progressWidth); /* stylelint-disable-line value-keyword-case */
max-width: 100%;
height: 4px;
border-radius: 4px;
background: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
}
.gbr-dl-icon {
display: flex;
width: 32px;
height: 32px;
flex-shrink: 0;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
}
}
.gbr-dl-base {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.gbr-dl-name {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 14px;
line-height: 18px;
}
.gbr-dl-time {
color: var(--box-text-7);
font-size: 12px;
line-height: 14px;
}
.gbr-dl-info {
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
column-gap: 4px;
}
.gbr-dl-cnt {
color: var(--common-text-title);
font-family: var(--font-title);
}
.gbr-dl-hint {
display: flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
padding: 4px;
border-radius: 50%;
background: var(--box-bg-3);
color: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
font-family: var(--font-title);
transform: rotate(25deg);
}
</style>

View File

@@ -0,0 +1,263 @@
<!-- 千星奇域概览数据视图组件 -->
<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>
<div class="gbr-mid-list">
<div class="gbr-ml-item">
<span>4已垫</span>
<span>{{ reset4count - 1 }}</span>
</div>
<div class="gbr-ml-item">
<span>5已垫</span>
<span>{{ reset5count - 1 }}</span>
</div>
<div class="gbr-ml-item">
<span>5平均</span>
<span>{{ star5avg }}</span>
</div>
</div>
<div class="gbr-mid-list">
<div class="gbr-ml-item">
<span>5统计</span>
<span>{{ getTitle("5") }}</span>
</div>
<div class="gbr-ml-item">
<span>4统计</span>
<span>{{ getTitle("4") }}</span>
</div>
</div>
<!-- 这边放具体物品的列表 -->
<div class="gbr-bottom">
<v-tabs v-model="tab" density="compact">
<v-tab value="5">5</v-tab>
<v-tab value="4">4</v-tab>
<v-tab value="3">3</v-tab>
</v-tabs>
<v-window v-model="tab" class="gbr-bottom-window">
<v-window-item value="5" class="gbr-b-window-item">
<v-virtual-scroll :items="star5List" :item-height="48">
<template #default="{ item }">
<GbrDataLine :data="item.data" :count="item.count" />
</template>
</v-virtual-scroll>
</v-window-item>
<v-window-item value="4" class="gbr-b-window-item">
<v-virtual-scroll :items="star4List" :item-height="48">
<template #default="{ item }">
<GbrDataLine :data="item.data" :count="item.count" />
</template>
</v-virtual-scroll>
</v-window-item>
<v-window-item value="3" class="gbr-b-window-item">
<v-virtual-scroll :items="star3List" :item-height="48">
<template #default="{ item }">
<GbrDataLine :data="item.data" :count="item.count" />
</template>
</v-virtual-scroll>
</v-window-item>
</v-window>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, shallowRef, watch } from "vue";
import GbrDataLine, { type GbrDataLineProps } from "./gbr-data-line.vue";
type GachaDataViewProps = {
dataType: "normal" | "boy" | "girl";
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGachaB>;
};
const props = defineProps<GachaDataViewProps>();
// data
const loading = ref<boolean>(true); // 是否加载完
const title = ref<string>(""); // 卡片标题
const startDate = ref<string>(""); // 最早的时间
const endDate = ref<string>(""); // 最晚的时间
const star5List = shallowRef<Array<GbrDataLineProps>>([]); // 5星物品数据
const star4List = shallowRef<Array<GbrDataLineProps>>([]); // 4星物品数据
const star3List = shallowRef<Array<GbrDataLineProps>>([]);
const reset5count = ref<number>(1); // 5星垫抽数量
const reset4count = ref<number>(1); // 4星垫抽数量
const reset3count = ref<number>(1); // 3星垫抽数量
const star3count = ref<number>(0); // 3星物品数量
const star5avg = ref<string>(""); // 5星平均抽数
const tab = ref<string>("5"); // tab
onMounted(() => {
loadData();
loading.value = false;
});
function loadData(): void {
title.value = getTitle("top");
const tempData = props.dataVal;
const temp5Data: Array<GbrDataLineProps> = [];
const temp4Data: Array<GbrDataLineProps> = [];
const temp3Data: Array<GbrDataLineProps> = [];
// 按照 id 升序
tempData
.sort((a, b) => a.id.localeCompare(b.id))
.forEach((item) => {
// 处理时间
if (startDate.value === "" || item.time < startDate.value) startDate.value = item.time;
if (endDate.value === "" || item.time > endDate.value) endDate.value = item.time;
if (item.rank === "2") {
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;
}
});
star5List.value = temp5Data.reverse();
star4List.value = temp4Data.reverse();
star3List.value = temp3Data.reverse();
star5avg.value = getStar5Avg();
}
// 获取标题
function getTitle(type: "top" | "5" | "4" | "3"): string {
if (type === "top") {
if (props.dataType === "normal") return "常驻颂愿";
if (props.dataType === "boy") return "活动颂愿(男)";
if (props.dataType === "girl") return "活动颂愿(女)";
return "";
}
if (props.dataVal.length === 0) return "暂无数据";
if (type === "5") {
// 5星物品统计 00.00%
return `${star5List.value.length} [${((star5List.value.length * 100) / props.dataVal.length)
.toFixed(2)
.padStart(5, "0")}%]`;
}
if (type === "4") {
// 4星物品统计
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
.toFixed(2)
.padStart(5, "0")}%]`;
}
// 3星物品统计
return `${star3count.value} [${((star3count.value * 100) / props.dataVal.length)
.toFixed(2)
.padStart(5, "0")}%]`;
}
// 获取5星平均抽数
function getStar5Avg(): string {
const resetList = star5List.value.map((item) => item.count);
if (resetList.length === 0) return "0";
const total = resetList.reduce((a, b) => a + b);
return (total / star5List.value.length).toFixed(2);
}
// 监听数据变化
watch(
() => props.dataVal,
() => {
star5List.value = [];
star4List.value = [];
reset5count.value = 1;
reset4count.value = 1;
star3count.value = 1;
startDate.value = "";
endDate.value = "";
star5avg.value = "";
tab.value = "5";
loadData();
},
);
</script>
<style lang="css" scoped>
.gbr-dv-container {
height: 100%;
box-sizing: border-box;
padding: 8px;
border-radius: 4px;
background: var(--box-bg-1);
}
.gbr-dvt-title {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 18px;
}
.gbr-dvt-subtitle {
width: 100%;
font-family: var(--font-text);
font-size: 12px;
opacity: 0.6;
}
.gbr-mid-list {
padding-top: 4px;
padding-bottom: 4px;
border-top: 1px solid var(--common-shadow-4);
color: var(--box-text-7);
}
.gbr-ml-item {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
font-family: var(--font-title);
font-size: 14px;
}
.gbr-bottom {
position: relative;
display: flex;
width: 100%;
height: calc(100% - 150px);
box-sizing: border-box;
flex-direction: column;
gap: 8px;
}
.gbr-bottom-window {
position: relative;
height: calc(100vh - 428px);
overflow-y: auto;
}
.gbr-b-window-item {
position: relative;
width: 100%;
box-sizing: border-box;
padding-right: 4px;
}
/* stylelint-disable selector-class-pattern */
:deep(.v-virtual-scroll__item + .v-virtual-scroll__item) {
margin-top: 8px;
}
/* stylelint-enable selector-class-pattern */
</style>

View File

@@ -0,0 +1,34 @@
<!-- 千星奇域祈愿概览组件 -->
<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" />
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import GbrDataView from "./gbr-data-view.vue";
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
const props = defineProps<GachaOverviewProps>();
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
props.modelValue.filter((item) => item.opGachaType === "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"),
);
</script>
<style lang="css" scoped>
.gro-o-container {
display: grid;
height: 100%;
column-gap: 8px;
grid-template-columns: repeat(3, 1fr);
}
</style>

View File

@@ -0,0 +1,62 @@
<!-- 千星奇域数据表格 -->
<template>
<v-data-table
:headers="headers"
:items="props.modelValue"
fixed-header
fixed-footer
class="gbr-t-box"
>
<template v-slot:item="{ item }">
<tr class="gbr-t-tr">
<td>{{ item.time }}</td>
<td>{{ getPool(item.opGachaType) }}</td>
<td>{{ item.type }}</td>
<td>{{ item.name }}</td>
<td>{{ item.rank }}</td>
</tr>
</template>
</v-data-table>
</template>
<script lang="ts" setup>
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
const props = defineProps<GroTableProps>();
const headers = <const>[
{ title: "时间", align: "center", key: "time" },
{ title: "卡池", align: "center", key: "opGachaType" },
{ title: "类型", align: "center", key: "type" },
{ title: "名称", align: "center", key: "name" },
{ title: "星级", align: "center", key: "rank" },
];
function getPool(type: string) {
switch (type) {
case "1000":
return "常驻颂愿";
case "2000":
return "活动颂愿";
case "20011":
case "20012":
return "活动颂愿-男";
case "20021":
case "20022":
return "活动颂愿-女";
default:
return "未知";
}
}
</script>
<style lang="css" scoped>
.gbr-t-box {
height: calc(100vh - 200px);
padding-right: 5px;
border-radius: 5px;
overflow-y: auto;
}
.gbr-t-tr {
text-align: center;
}
</style>

View File

@@ -35,22 +35,28 @@ const GachaIdMap: Record<string, string> = {
"20021": "57016dec6b768231ba1342c01935417a799b", // 千星奇域角色活动-女
};
const tabNormal: ReadonlyArray<GroTab> = [
{ label: "常驻祈愿", value: "200" },
{ label: "角色活动祈愿", value: "301" },
{ label: "武器活动祈愿", value: "302" },
{ label: "角色活动祈愿-2", value: "400" },
];
const tabBeyond: ReadonlyArray<GroTab> = [
{ label: "常驻颂愿", value: "1000", beyond: true },
{ label: "活动颂愿-男", value: "20011", beyond: true },
{ label: "活动颂愿-女", value: "20021", beyond: true },
];
type GroTabKey = keyof typeof GachaIdMap;
type GroTab = { label: string; value: string; beyond?: boolean };
type GroIframeProps = { mode: "normal" | "beyond" };
const props = defineProps<GroIframeProps>();
const { cookie, account } = storeToRefs(useUserStore());
const authkey = ref<string>("");
const link = ref<string>("");
const poolTab = ref<GroTabKey>("200");
const tabList = shallowRef<ReadonlyArray<GroTab>>([
{ label: "常驻祈愿", value: "200" },
{ label: "角色活动祈愿", value: "301" },
{ label: "武器活动祈愿", value: "302" },
{ label: "角色活动祈愿-2", value: "400" },
{ label: "常驻颂愿", value: "1000", beyond: true },
{ label: "活动颂愿-男", value: "20011", beyond: true },
{ label: "活动颂愿-女", value: "20021", beyond: true },
]);
const tabList = shallowRef<ReadonlyArray<GroTab>>(props.mode === "beyond" ? tabBeyond : tabNormal);
onMounted(async () => {
link.value = await getUrl();
@@ -107,7 +113,7 @@ async function refreshAuthkey(): Promise<void> {
display: flex;
width: 100%;
height: 100%;
align-items: center;
align-items: flex-start;
justify-content: space-between;
}