mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-16 09:58:13 +08:00
♻️ 调整目录结构
This commit is contained in:
1
src/components/userGacha/gro-cloud.vue
Normal file
1
src/components/userGacha/gro-cloud.vue
Normal file
@@ -0,0 +1 @@
|
||||
<!-- todo 角色云图 -->
|
||||
176
src/components/userGacha/gro-data-line.vue
Normal file
176
src/components/userGacha/gro-data-line.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="gro-dl-box">
|
||||
<div class="gro-dl-progress" />
|
||||
<div class="gro-dl-icon">
|
||||
<img :alt="props.data.name" :src="getIcon()" />
|
||||
</div>
|
||||
<div class="gro-dl-line">
|
||||
<div class="gro-dl-base">
|
||||
<div class="gro-dl-name">{{ props.data.name }}</div>
|
||||
<div class="gro-dl-time">{{ props.data.time }}</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import { AppGachaData } from "../../data/index.js";
|
||||
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js";
|
||||
|
||||
export interface GroDataLineProps {
|
||||
data: TGApp.Sqlite.GachaRecords.SingleTable;
|
||||
count: number;
|
||||
}
|
||||
|
||||
const props = defineProps<GroDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
function getIcon(): string {
|
||||
const itemType = TSUserGacha.getGachaItemType(props.data.itemId);
|
||||
if (itemType[0] === "角色") {
|
||||
return `/WIKI/character/${props.data.itemId}.webp`;
|
||||
} else if (itemType[0] === "武器") {
|
||||
return `/WIKI/weapon/${props.data.itemId}.webp`;
|
||||
} else {
|
||||
return `/source/UI/paimon.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 "歪";
|
||||
} else 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 "var(--tgc-od-orange)";
|
||||
if (hint === "UP" && props.data.rank === "4") return "var(--tgc-od-purple)";
|
||||
if (hint === "歪") return "var(--tgc-od-red)";
|
||||
return "var(--tgc-od-blue)";
|
||||
});
|
||||
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="css" scoped>
|
||||
.gro-dl-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: calc(100% - 10px);
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
margin: 0 5px;
|
||||
background: var(--box-bg-3);
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.gro-dl-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: v-bind(progressWidth);
|
||||
max-width: 100%;
|
||||
height: 5px;
|
||||
border-radius: 5px;
|
||||
background: v-bind(progressColor);
|
||||
transition: width 0.5s;
|
||||
}
|
||||
|
||||
.gro-dl-icon {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.gro-dl-line {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gro-dl-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gro-dl-name {
|
||||
height: 16px;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gro-dl-time {
|
||||
height: 14px;
|
||||
color: var(--box-text-7);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.gro-dl-info {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.gro-dl-cnt {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.gro-dl-hint {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--box-bg-1);
|
||||
color: v-bind(progressColor);
|
||||
font-family: var(--font-title);
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
</style>
|
||||
239
src/components/userGacha/gro-dataview.vue
Normal file
239
src/components/userGacha/gro-dataview.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<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>
|
||||
<div class="gro-mid-list">
|
||||
<div class="gro-ml-item">
|
||||
<span>4☆已垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-item">
|
||||
<span>5☆已垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-item">
|
||||
<span>5☆平均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gro-mid-list">
|
||||
<div class="gro-ml-item">
|
||||
<span>5☆统计</span>
|
||||
<span>{{ getTitle("5") }}</span>
|
||||
</div>
|
||||
<div class="gro-ml-item">
|
||||
<span>4☆统计</span>
|
||||
<span>{{ getTitle("4") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 这边放具体物品的列表 -->
|
||||
<div class="gro-bottom">
|
||||
<v-tabs v-model="tab">
|
||||
<v-tab value="5">5☆</v-tab>
|
||||
<v-tab value="4">4☆</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="gro-bottom-window">
|
||||
<v-window-item value="5" class="gro-b-window-item">
|
||||
<GroDataLine
|
||||
v-for="(item, index) in star5List"
|
||||
:key="index"
|
||||
:data="item.data"
|
||||
:count="item.count"
|
||||
/>
|
||||
</v-window-item>
|
||||
<v-window-item value="4" class="gro-b-window-item">
|
||||
<GroDataLine
|
||||
v-for="(item, index) in star4List"
|
||||
:key="index"
|
||||
:data="item.data"
|
||||
:count="item.count"
|
||||
/>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
import GroDataLine, { GroDataLineProps } from "./gro-data-line.vue";
|
||||
|
||||
interface GachaDataViewProps {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
|
||||
dataVal: TGApp.Sqlite.GachaRecords.SingleTable[];
|
||||
}
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
// data
|
||||
const loading = ref<boolean>(true); // 是否加载完
|
||||
const title = ref<string>(""); // 卡片标题
|
||||
const startDate = ref<string>(""); // 最早的时间
|
||||
const endDate = ref<string>(""); // 最晚的时间
|
||||
const star5List = ref<GroDataLineProps[]>([]); // 5星物品数据
|
||||
const star4List = ref<GroDataLineProps[]>([]); // 4星物品数据
|
||||
const reset5count = ref<number>(1); // 5星垫抽数量
|
||||
const reset4count = ref<number>(1); // 4星垫抽数量
|
||||
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;
|
||||
// 按照 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 === "3") {
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
star3count.value++;
|
||||
} else if (item.rank === "4") {
|
||||
reset5count.value++;
|
||||
star4List.value.push({
|
||||
data: item,
|
||||
count: reset4count.value,
|
||||
});
|
||||
reset4count.value = 1;
|
||||
} else if (item.rank === "5") {
|
||||
reset4count.value++;
|
||||
star5List.value.push({
|
||||
data: item,
|
||||
count: reset5count.value,
|
||||
});
|
||||
reset5count.value = 1;
|
||||
}
|
||||
});
|
||||
star5avg.value = getStar5Avg();
|
||||
// 两个列表反序
|
||||
star5List.value.reverse();
|
||||
star4List.value.reverse();
|
||||
}
|
||||
|
||||
// 获取标题
|
||||
function getTitle(type: "top" | "5" | "4" | "3"): string {
|
||||
if (type === "top") {
|
||||
if (props.dataType === "new") return "新手祈愿";
|
||||
if (props.dataType === "avatar") return "角色祈愿";
|
||||
if (props.dataType === "weapon") return "武器祈愿";
|
||||
if (props.dataType === "normal") return "常驻祈愿";
|
||||
if (props.dataType === "mix") return "集录祈愿";
|
||||
return "";
|
||||
} else if (props.dataVal.length === 0) {
|
||||
return "暂无数据";
|
||||
} else if (type === "5") {
|
||||
// 5星物品统计 00.00%
|
||||
return `${star5List.value.length} [${((star5List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
} else if (type === "4") {
|
||||
// 4星物品统计
|
||||
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
} else {
|
||||
// 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>
|
||||
.gro-dv-container {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background: var(--box-bg-2);
|
||||
}
|
||||
|
||||
.gro-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: 20px;
|
||||
}
|
||||
|
||||
.gro-dvt-subtitle {
|
||||
width: 100%;
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.gro-mid-list {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
border-top: 1px solid var(--common-shadow-4);
|
||||
color: var(--box-text-7);
|
||||
}
|
||||
|
||||
.gro-ml-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gro-bottom {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gro-bottom-window {
|
||||
height: calc(100vh - 440px);
|
||||
}
|
||||
|
||||
.gro-b-window-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
gap: 5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
236
src/components/userGacha/gro-echarts.vue
Normal file
236
src/components/userGacha/gro-echarts.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="gro-echart">
|
||||
<v-chart :option="getPoolData()" autoresize />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// about import err,see:https://github.com/apache/echarts/issues/19992
|
||||
import { PieChart } from "echarts/charts.js";
|
||||
import {
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
ToolboxComponent,
|
||||
} from "echarts/components.js";
|
||||
import { use } from "echarts/core.js";
|
||||
import { LabelLayout } from "echarts/features.js";
|
||||
import { CanvasRenderer } from "echarts/renderers.js";
|
||||
import type { EChartsOption } from "echarts/types/dist/shared.js";
|
||||
import { provide } from "vue";
|
||||
import VChart, { THEME_KEY } from "vue-echarts";
|
||||
|
||||
// echarts
|
||||
use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
ToolboxComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
// 获取当前主题
|
||||
provide(THEME_KEY, "dark");
|
||||
|
||||
interface GachaOverviewEchartsProps {
|
||||
modelValue: TGApp.Sqlite.GachaRecords.SingleTable[];
|
||||
}
|
||||
|
||||
const props = defineProps<GachaOverviewEchartsProps>();
|
||||
|
||||
// data
|
||||
const defaultOptions = <EChartsOption>{
|
||||
title: [
|
||||
{
|
||||
text: ">> 祈愿系统大数据分析 <<",
|
||||
left: "center",
|
||||
top: "5%",
|
||||
},
|
||||
{
|
||||
text: "卡池分布",
|
||||
left: "17%",
|
||||
top: "45%",
|
||||
},
|
||||
{
|
||||
text: "星级分布",
|
||||
left: "17%",
|
||||
top: "90%",
|
||||
},
|
||||
{
|
||||
text: "角色池分布",
|
||||
left: "45%",
|
||||
bottom: "10%",
|
||||
},
|
||||
{
|
||||
text: "武器池分布",
|
||||
right: "5%",
|
||||
bottom: "10%",
|
||||
},
|
||||
],
|
||||
tooltip: { trigger: "item" },
|
||||
legend: {
|
||||
type: "scroll",
|
||||
orient: "vertical",
|
||||
left: 10,
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
restore: {},
|
||||
saveAsImage: {},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "卡池分布",
|
||||
type: "pie",
|
||||
radius: "50%",
|
||||
data: [],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: "rgba(0, 0, 0, 0.5)",
|
||||
},
|
||||
},
|
||||
right: "60%",
|
||||
top: 0,
|
||||
bottom: "50%",
|
||||
},
|
||||
{
|
||||
name: "星级分布",
|
||||
type: "pie",
|
||||
radius: "50%",
|
||||
data: [],
|
||||
right: "60%",
|
||||
top: "50%",
|
||||
bottom: "0",
|
||||
},
|
||||
{
|
||||
name: "角色池分布",
|
||||
type: "pie",
|
||||
radius: [30, 150],
|
||||
center: ["50%", "50%"],
|
||||
roseType: "area",
|
||||
itemStyle: {
|
||||
borderRadius: [2, 10],
|
||||
},
|
||||
data: [],
|
||||
datasetIndex: 3,
|
||||
},
|
||||
{
|
||||
name: "武器池分布",
|
||||
type: "pie",
|
||||
radius: [30, 100],
|
||||
itemStyle: {
|
||||
borderRadius: [3, 10],
|
||||
},
|
||||
data: [],
|
||||
left: "70%",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 获取卡池分布的数据
|
||||
// todo 重构以完善类型
|
||||
function getPoolData(): EChartsOption {
|
||||
const data: EChartsOption = JSON.parse(JSON.stringify(defaultOptions));
|
||||
if (data.title !== undefined && Array.isArray(data.title)) {
|
||||
data.title[0].subtext = `共 ${props.modelValue.length} 条数据`;
|
||||
}
|
||||
if (data.series !== undefined && Array.isArray(data.series)) {
|
||||
data.series[0].data = [
|
||||
{
|
||||
value: props.modelValue.filter((item) => item.uigfType === "100").length,
|
||||
name: "新手祈愿",
|
||||
},
|
||||
{
|
||||
value: props.modelValue.filter((item) => item.uigfType === "200").length,
|
||||
name: "常驻祈愿",
|
||||
},
|
||||
{
|
||||
value: props.modelValue.filter((item) => item.uigfType === "301").length,
|
||||
name: "角色活动祈愿",
|
||||
},
|
||||
{
|
||||
value: props.modelValue.filter((item) => item.uigfType === "302").length,
|
||||
name: "武器活动祈愿",
|
||||
},
|
||||
{
|
||||
value: props.modelValue.filter((item) => item.uigfType === "500").length,
|
||||
name: "集录祈愿",
|
||||
},
|
||||
];
|
||||
data.series[1].data = [
|
||||
{ value: props.modelValue.filter((item) => item.rank === "3").length, name: "3星" },
|
||||
{ value: props.modelValue.filter((item) => item.rank === "4").length, name: "4星" },
|
||||
{ value: props.modelValue.filter((item) => item.rank === "5").length, name: "5星" },
|
||||
];
|
||||
}
|
||||
|
||||
const tempSet = new Set<string>();
|
||||
const tempRecord = new Map<string, number>();
|
||||
// 角色池分析
|
||||
let tempList = props.modelValue.filter((item) => item.uigfType === "301");
|
||||
let star3 = tempList.filter((item) => item.rank === "3").length;
|
||||
if (data.title !== undefined && Array.isArray(data.title)) {
|
||||
data.title[3].subtext = `共 ${tempList.length} 条数据, 其中三星武器 ${star3} 抽`;
|
||||
}
|
||||
tempList
|
||||
.filter((item) => item.rank !== "3")
|
||||
.forEach((item) => {
|
||||
if (tempSet.has(item.name)) {
|
||||
tempRecord.set(item.name, (tempRecord.get(item.name) ?? 0) + 1);
|
||||
} else {
|
||||
tempSet.add(item.name);
|
||||
tempRecord.set(item.name, 1);
|
||||
}
|
||||
});
|
||||
if (data.series !== undefined && Array.isArray(data.series)) {
|
||||
data.series[2].data = Array.from(tempRecord).map((item) => {
|
||||
return {
|
||||
value: item[1],
|
||||
name: item[0],
|
||||
};
|
||||
});
|
||||
}
|
||||
tempSet.clear();
|
||||
tempRecord.clear();
|
||||
// 武器池分析
|
||||
tempList = props.modelValue.filter((item) => item.uigfType === "302");
|
||||
star3 = tempList.filter((item) => item.rank === "3").length;
|
||||
if (data.title !== undefined && Array.isArray(data.title)) {
|
||||
data.title[4].subtext = `共 ${tempList.length} 条数据,其中三星武器 ${star3} 抽`;
|
||||
}
|
||||
tempList
|
||||
.filter((item) => item.rank !== "3")
|
||||
.forEach((item) => {
|
||||
if (tempSet.has(item.name)) {
|
||||
tempRecord.set(item.name, (tempRecord.get(item.name) ?? 0) + 1);
|
||||
} else {
|
||||
tempSet.add(item.name);
|
||||
tempRecord.set(item.name, 1);
|
||||
}
|
||||
});
|
||||
if (data.series !== undefined && Array.isArray(data.series)) {
|
||||
data.series[3].data = Array.from(tempRecord).map((item) => {
|
||||
return {
|
||||
value: item[1],
|
||||
name: item[0],
|
||||
};
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-echart {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
299
src/components/userGacha/gro-history.vue
Normal file
299
src/components/userGacha/gro-history.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="gro-container">
|
||||
<v-tabs class="gro-tabs" v-model="historyTab" align-tabs="start" direction="vertical">
|
||||
<v-tab v-for="(item, index) in tabList" :key="index" :value="item.tab">
|
||||
v{{ item.tab }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="historyTab" class="gro-window">
|
||||
<v-window-item
|
||||
v-for="(item, index) in tabList"
|
||||
:key="index"
|
||||
:value="item.tab"
|
||||
class="gro-pools"
|
||||
>
|
||||
<div v-for="pool in item.value" :key="pool.order" class="gro-pool">
|
||||
<img
|
||||
:src="pool.banner"
|
||||
class="gro-banner"
|
||||
alt="banner"
|
||||
@click="createPost(pool.postId)"
|
||||
/>
|
||||
<div class="gro-pool-info">
|
||||
<div class="gro-pi-title">
|
||||
<span>{{ pool.name }}</span>
|
||||
<span class="gro-pi-tag">{{ pool.order === 1 ? "上半" : "下半" }}</span>
|
||||
<span class="gro-pi-tag">{{ getType(pool.type) }}</span>
|
||||
</div>
|
||||
<div class="gro-pi-time">{{ getTimeStr(pool) }}</div>
|
||||
<div class="gro-pi-sub">Up 五星</div>
|
||||
<div class="gro-pool-up lv5">
|
||||
<TItembox
|
||||
v-for="i in pool.up5List"
|
||||
:key="i"
|
||||
:model-value="getBox(i)"
|
||||
@click="toWiki(i)"
|
||||
/>
|
||||
</div>
|
||||
<div class="gro-pi-sub">Up 四星</div>
|
||||
<div class="gro-pool-up lv4">
|
||||
<TItembox
|
||||
v-for="i in pool.up4List"
|
||||
:key="i"
|
||||
:model-value="getBox(i)"
|
||||
@click="toWiki(i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { AppGachaData, AppCharacterData, AppWeaponData } from "../../data/index.js";
|
||||
import { createPost } from "../../utils/TGWindow.js";
|
||||
import { timestampToDate } from "../../utils/toolFunc.js";
|
||||
import TItembox, { TItemBoxData } from "../app/t-item-box.vue";
|
||||
import showDialog from "../func/dialog.js";
|
||||
import showSnackbar from "../func/snackbar.js";
|
||||
|
||||
interface GroHistoryMap {
|
||||
tab: string;
|
||||
value: TGApp.App.Gacha.PoolItem[];
|
||||
}
|
||||
|
||||
const historyTab = ref<string>("");
|
||||
const tabList = ref<GroHistoryMap[]>([]);
|
||||
const router = useRouter();
|
||||
|
||||
onMounted(() => {
|
||||
const res: GroHistoryMap[] = [];
|
||||
AppGachaData.forEach((pool) => {
|
||||
const index = res.findIndex((item) => item.tab === pool.version);
|
||||
if (index === -1) {
|
||||
res.push({
|
||||
tab: pool.version,
|
||||
value: [pool],
|
||||
});
|
||||
} else {
|
||||
res[index].value.push(pool);
|
||||
}
|
||||
});
|
||||
tabList.value = res.reverse();
|
||||
historyTab.value = res[0].tab;
|
||||
});
|
||||
|
||||
async function toWiki(id: number): Promise<void> {
|
||||
const cFind = AppCharacterData.find((item) => item.id === id);
|
||||
const wFind = AppWeaponData.find((item) => item.id === id);
|
||||
const jumpCheck = await showDialog.check("是否跳转到对应图鉴界面?");
|
||||
if (!jumpCheck) {
|
||||
showSnackbar.cancel("已取消");
|
||||
return;
|
||||
}
|
||||
if (cFind) {
|
||||
await router.push({ name: "角色图鉴", params: { id: id.toString() } });
|
||||
return;
|
||||
}
|
||||
if (wFind) {
|
||||
await router.push({ name: "武器图鉴", params: { id: id.toString() } });
|
||||
return;
|
||||
}
|
||||
showSnackbar.warn("未找到对应角色或武器");
|
||||
}
|
||||
|
||||
function getType(type: TGApp.App.Gacha.WishType): string {
|
||||
switch (type) {
|
||||
case 301:
|
||||
return "角色活动祈愿";
|
||||
case 400:
|
||||
return "角色活动祈愿2";
|
||||
case 302:
|
||||
return "武器活动祈愿";
|
||||
case 500:
|
||||
return "集录祈愿";
|
||||
default:
|
||||
return `未知类型 ${type}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeStr(pool: TGApp.App.Gacha.PoolItem): string {
|
||||
const startTime = timestampToDate(Date.parse(pool.from));
|
||||
const endTime = timestampToDate(Date.parse(pool.to));
|
||||
return `${startTime} ~ ${endTime}`;
|
||||
}
|
||||
|
||||
function getBox(id: number): TItemBoxData {
|
||||
const cFind = AppCharacterData.find((item) => item.id === id);
|
||||
const wFind = AppWeaponData.find((item) => item.id === id);
|
||||
if (cFind) {
|
||||
return {
|
||||
bg: `/icon/bg/${cFind.star}-Star.webp`,
|
||||
icon: `/WIKI/character/${cFind.id}.webp`,
|
||||
size: "80px",
|
||||
height: "80px",
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
lt: `/icon/element/${cFind.element}元素.webp`,
|
||||
ltSize: "20px",
|
||||
innerHeight: 20,
|
||||
innerIcon: `/icon/weapon/${cFind.weapon}.webp`,
|
||||
innerText: cFind.name,
|
||||
};
|
||||
}
|
||||
if (wFind) {
|
||||
return {
|
||||
bg: `/icon/bg/${wFind.star}-Star.webp`,
|
||||
icon: `/WIKI/weapon/${wFind.id}.webp`,
|
||||
size: "80px",
|
||||
height: "80px",
|
||||
display: "inner",
|
||||
clickable: true,
|
||||
lt: wFind.weaponIcon,
|
||||
ltSize: "20px",
|
||||
innerHeight: 20,
|
||||
innerText: wFind.name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
bg: "/icon/bg/0-Star.webp",
|
||||
icon: "/source/UI/empty/webp",
|
||||
size: "80px",
|
||||
height: "80px",
|
||||
display: "inner",
|
||||
clickable: false,
|
||||
lt: "",
|
||||
ltSize: "0",
|
||||
innerHeight: 20,
|
||||
innerText: "未找到对应角色或武器",
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.gro-tabs {
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.gro-container :deep(.v-tabs.v-slide-group--vertical) {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.gro-window {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 10px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.gro-window :deep(.v-window__container) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gro-pools {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.gro-pool {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: var(--box-bg-2);
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.gro-banner {
|
||||
width: 50vw;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.gro-banner:hover {
|
||||
box-shadow: 0 0 10px 5px var(--box-bg-2);
|
||||
scale: 0.95;
|
||||
transition: 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.gro-pool-info {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.gro-pi-title {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.gro-pi-title :first-child {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.gro-pi-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5px;
|
||||
border-radius: 5px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-5);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.gro-pi-sub {
|
||||
font-family: var(--font-title);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.gro-pool-up {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gro-pool-up.lv5 {
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
.gro-pool-up.lv4 {
|
||||
max-height: 170px;
|
||||
}
|
||||
</style>
|
||||
71
src/components/userGacha/gro-overview.vue
Normal file
71
src/components/userGacha/gro-overview.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="gro-o-container">
|
||||
<gro-dataview v-if="newData.length !== 0" :data-val="newData" data-type="new" />
|
||||
<gro-dataview :data-val="normalData" data-type="normal" />
|
||||
<gro-dataview :data-val="avatarData" data-type="avatar" />
|
||||
<gro-dataview :data-val="weaponData" data-type="weapon" />
|
||||
<gro-dataview v-if="mixData.length !== 0" :data-val="mixData" data-type="mix" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import GroDataview from "./gro-dataview.vue";
|
||||
|
||||
interface GachaOverviewProps {
|
||||
modelValue: TGApp.Sqlite.GachaRecords.SingleTable[];
|
||||
}
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
// data
|
||||
const newData = computed<TGApp.Sqlite.GachaRecords.SingleTable[]>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "100"),
|
||||
);
|
||||
const normalData = computed<TGApp.Sqlite.GachaRecords.SingleTable[]>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "200"),
|
||||
);
|
||||
const avatarData = computed<TGApp.Sqlite.GachaRecords.SingleTable[]>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "301"),
|
||||
);
|
||||
const weaponData = computed<TGApp.Sqlite.GachaRecords.SingleTable[]>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "302"),
|
||||
);
|
||||
const mixData = computed<TGApp.Sqlite.GachaRecords.SingleTable[]>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "500"),
|
||||
);
|
||||
|
||||
const cnCols = ref<string>(getCnCols());
|
||||
|
||||
function getCnCols(): string {
|
||||
let total = 5;
|
||||
if (newData.value.length === 0) {
|
||||
total -= 1;
|
||||
}
|
||||
if (mixData.value.length === 0) {
|
||||
total -= 1;
|
||||
}
|
||||
if (total === 5) {
|
||||
return "repeat(5, 0.2fr)";
|
||||
} else if (total === 4) {
|
||||
return "repeat(4, 0.25fr)";
|
||||
} else {
|
||||
return "repeat(3, 0.33fr)";
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
cnCols.value = getCnCols();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-o-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
grid-column-gap: 10px;
|
||||
grid-template-columns: v-bind(cnCols);
|
||||
}
|
||||
</style>
|
||||
67
src/components/userGacha/gro-table.vue
Normal file
67
src/components/userGacha/gro-table.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<!-- todo 优化,增加筛选功能 -->
|
||||
<div class="ua-gt-box">
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="props.modelValue"
|
||||
height="500px"
|
||||
fixed-header
|
||||
fixed-footer
|
||||
>
|
||||
<template v-slot:item="{ item }">
|
||||
<tr class="ua-gt-tr">
|
||||
<td>{{ item.time }}</td>
|
||||
<td>{{ getPool(item.uigfType) }}</td>
|
||||
<td>{{ item.type }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.rank }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface GroTableProps {
|
||||
modelValue: TGApp.Sqlite.GachaRecords.SingleTable[];
|
||||
}
|
||||
|
||||
const props = defineProps<GroTableProps>();
|
||||
|
||||
const headers = [
|
||||
{ title: "时间", align: "center", key: "time" },
|
||||
{ title: "卡池", align: "center", key: "uigfType" },
|
||||
{ title: "类型", align: "center", key: "type" },
|
||||
{ title: "名称", align: "center", key: "name" },
|
||||
{ title: "星级", align: "center", key: "rank" },
|
||||
];
|
||||
|
||||
function getPool(type: string) {
|
||||
switch (type) {
|
||||
case "100":
|
||||
return "新手祈愿";
|
||||
case "200":
|
||||
return "常驻祈愿";
|
||||
case "301":
|
||||
return "角色活动祈愿";
|
||||
case "302":
|
||||
return "武器活动祈愿";
|
||||
case "500":
|
||||
return "集录祈愿";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ua-gt-box {
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 120px);
|
||||
padding-right: 5px;
|
||||
border-radius: 5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ua-gt-tr {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
1
src/components/userGacha/gro-timeline.vue
Normal file
1
src/components/userGacha/gro-timeline.vue
Normal file
@@ -0,0 +1 @@
|
||||
<!-- todo 时间轴 -->
|
||||
Reference in New Issue
Block a user