mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-13 09:28:14 +08:00
♻️ uid 直接读取数据库,优化渲染
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div v-if="!loading" class="gro-dv-container">
|
||||
<div class="gro-dv-container">
|
||||
<div class="gro-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gro-dvt-subtitle">{{ startDate }} ~ {{ endDate }}</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>
|
||||
@@ -64,7 +67,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
interface GachaDataViewProps {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal";
|
||||
@@ -147,24 +150,27 @@ function getTitle(type: "top" | "5" | "4" | "3"): string {
|
||||
if (props.dataType === "normal") return "常驻祈愿";
|
||||
return "";
|
||||
} else if (type === "5") {
|
||||
// 5星物品统计
|
||||
return `${star5List.value.length} [${(star5List.value.length / props.dataVal.length).toFixed(
|
||||
2,
|
||||
)}%]`;
|
||||
// 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 / props.dataVal.length).toFixed(
|
||||
2,
|
||||
)}%]`;
|
||||
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
} else {
|
||||
// 3星物品统计
|
||||
return `${star3count.value} [${(star3count.value / props.dataVal.length).toFixed(2)}%]`;
|
||||
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.gachaCount);
|
||||
if (resetList.length === 0) return "0";
|
||||
const total = resetList.reduce((a, b) => a + b);
|
||||
return (total / star5List.value.length).toFixed(2);
|
||||
}
|
||||
@@ -177,6 +183,23 @@ function getIcon(itemId: string, type: string): string {
|
||||
return "/WIKI/weapon/icon/" + itemId + ".webp";
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.dataVal,
|
||||
(newVal) => {
|
||||
star5List.value = [];
|
||||
star4List.value = [];
|
||||
reset5count.value = 0;
|
||||
reset4count.value = 0;
|
||||
star3count.value = 0;
|
||||
startDate.value = "";
|
||||
endDate.value = "";
|
||||
star5avg.value = "";
|
||||
tab.value = "5";
|
||||
loadData();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-dv-container {
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
<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" />
|
||||
<div
|
||||
class="gro-o-container"
|
||||
:style="{
|
||||
gridTemplateColumns: newData.length !== 0 ? 'repeat(4, 1fr)' : 'repeat(3, 1fr)',
|
||||
}"
|
||||
>
|
||||
<gro-dataview v-if="newData.length !== 0" v-model:data-val="newData" data-type="new" />
|
||||
<gro-dataview v-model:data-val="normalData" data-type="normal" />
|
||||
<gro-dataview v-model:data-val="avatarData" data-type="avatar" />
|
||||
<gro-dataview v-model:data-val="weaponData" data-type="weapon" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { watch } from "vue";
|
||||
import GroDataview from "./gro-dataview.vue";
|
||||
|
||||
interface GachaOverviewProps {
|
||||
@@ -15,13 +21,22 @@ interface GachaOverviewProps {
|
||||
}
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
|
||||
// data
|
||||
const newData = props.modelValue.filter((item) => item.uigfType === "100");
|
||||
const normalData = props.modelValue.filter((item) => item.uigfType === "200");
|
||||
const avatarData = props.modelValue.filter((item) => item.uigfType === "301");
|
||||
const weaponData = props.modelValue.filter((item) => item.uigfType === "302");
|
||||
const getColNum = newData.length === 0 ? 3 : 4;
|
||||
let newData = props.modelValue.filter((item) => item.uigfType === "100");
|
||||
let normalData = props.modelValue.filter((item) => item.uigfType === "200");
|
||||
let avatarData = props.modelValue.filter((item) => item.uigfType === "301");
|
||||
let weaponData = props.modelValue.filter((item) => item.uigfType === "302");
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
newData = newVal.filter((item) => item.uigfType === "100");
|
||||
normalData = newVal.filter((item) => item.uigfType === "200");
|
||||
avatarData = newVal.filter((item) => item.uigfType === "301");
|
||||
weaponData = newVal.filter((item) => item.uigfType === "302");
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-o-container {
|
||||
@@ -29,6 +44,6 @@ const getColNum = newData.length === 0 ? 3 : 4;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: repeat(v-bind(getColNum), 1fr);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
<ToLoading v-model="loading" :title="loadingTitle" />
|
||||
<v-app-bar class="gacha-top-bar">
|
||||
<template #prepend>
|
||||
<v-app-bar-title>
|
||||
<span>祈愿记录</span>
|
||||
<span v-if="isLogin()"> - {{ user.nickname }} UID:{{ user.gameUid }}</span>
|
||||
</v-app-bar-title>
|
||||
<v-app-bar-title> 祈愿记录 </v-app-bar-title>
|
||||
</template>
|
||||
<template #default>
|
||||
<v-select
|
||||
v-model="uidCur"
|
||||
class="gacha-top-select"
|
||||
:items="selectItem"
|
||||
variant="underlined"
|
||||
/>
|
||||
<v-spacer />
|
||||
</template>
|
||||
<v-spacer />
|
||||
<template #append>
|
||||
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn"> 导入</v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="gacha-top-btn" @click="handleExportBtn"> 导出</v-btn>
|
||||
@@ -30,7 +35,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import showSnackbar from "../../components/func/snackbar";
|
||||
import showConfirm from "../../components/func/confirm";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
@@ -38,29 +43,34 @@ import GroEcharts from "../../components/gachaRecord/gro-echarts.vue";
|
||||
import GroOverview from "../../components/gachaRecord/gro-overview.vue";
|
||||
// tauri
|
||||
import { dialog } from "@tauri-apps/api";
|
||||
// store
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
// utils
|
||||
import { exportUigfData, readUigfData, verifyUigfData } from "../../utils/UIGF";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
|
||||
// todo: 不读取用户数据,直接读取数据库,获取 uid 列表,然后根据 uid 获取祈愿数据
|
||||
|
||||
// store
|
||||
const userStore = useUserStore();
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
const loadingTitle = ref<string>();
|
||||
|
||||
// data
|
||||
const user = userStore.getCurAccount();
|
||||
const selectItem = ref<string[]>([]);
|
||||
const uidCur = ref<string>("");
|
||||
const gachaListCur = ref<TGApp.Sqlite.GachaRecords.SingleTable[]>([]);
|
||||
const tab = ref<string>("");
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = `正在获取用户 ${user.gameUid} 的祈愿数据`;
|
||||
gachaListCur.value = await TGSqlite.getGachaRecords(user.gameUid);
|
||||
loadingTitle.value = `正在获取祈愿 UID 列表`;
|
||||
selectItem.value = await TGSqlite.getUidList();
|
||||
if (selectItem.value.length === 0) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: `暂无祈愿数据,请先导入祈愿数据`,
|
||||
});
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
uidCur.value = selectItem.value[0];
|
||||
loadingTitle.value = `正在获取祈愿数据,默认 UID:${uidCur.value}`;
|
||||
gachaListCur.value = await TGSqlite.getGachaRecords(uidCur.value);
|
||||
loadingTitle.value = `正在渲染数据`;
|
||||
tab.value = "echarts";
|
||||
loading.value = false;
|
||||
@@ -69,11 +79,6 @@ onMounted(async () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 判断用户是否登录
|
||||
function isLogin(): boolean {
|
||||
return user?.gameUid !== undefined;
|
||||
}
|
||||
|
||||
// 导入按钮点击事件
|
||||
async function handleImportBtn(): Promise<void> {
|
||||
const selectedFile = await dialog.open({
|
||||
@@ -95,38 +100,31 @@ async function handleImportBtn(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
const remoteData = await readUigfData(<string>selectedFile);
|
||||
if (remoteData.info.uid !== user.gameUid) {
|
||||
await showConfirm({
|
||||
title: "UID 不匹配,是否继续导入?",
|
||||
text: `当前 UID:${user.gameUid},导入 UID:${remoteData.info.uid}`,
|
||||
const res = await showConfirm({
|
||||
title: "是否导入祈愿数据?",
|
||||
text: `UID:${remoteData.info.uid} 共 ${remoteData.list.length} 条数据`,
|
||||
});
|
||||
if (!res) {
|
||||
showSnackbar({
|
||||
color: "grey",
|
||||
text: `已取消祈愿数据导入`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在导入祈愿数据";
|
||||
loading.value = true;
|
||||
if (remoteData.list.length === 0) {
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: `导入的祈愿数据为空`,
|
||||
});
|
||||
} else {
|
||||
const res = await showConfirm({
|
||||
title: "是否导入祈愿数据?",
|
||||
text: `UID:${remoteData.info.uid} 共 ${remoteData.list.length} 条数据`,
|
||||
await TGSqlite.mergeUIGF(remoteData.info.uid, remoteData.list);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `成功导入 ${remoteData.list.length} 条祈愿数据`,
|
||||
});
|
||||
if (!res) {
|
||||
showSnackbar({
|
||||
color: "grey",
|
||||
text: `已取消祈愿数据导入`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在导入祈愿数据";
|
||||
loading.value = true;
|
||||
if (remoteData.list.length === 0) {
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: `导入的祈愿数据为空`,
|
||||
});
|
||||
} else {
|
||||
await TGSqlite.mergeUIGF(user.gameUid, remoteData.list);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `成功导入 ${remoteData.list.length} 条祈愿数据`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showSnackbar({
|
||||
@@ -138,17 +136,17 @@ async function handleImportBtn(): Promise<void> {
|
||||
|
||||
// 导出按钮点击事件
|
||||
async function handleExportBtn(): Promise<void> {
|
||||
const gachaList = await TGSqlite.getGachaRecords(user.gameUid);
|
||||
const gachaList = await TGSqlite.getGachaRecords(uidCur.value);
|
||||
if (gachaList.length === 0) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: `用户 ${user.gameUid} 暂无祈愿数据`,
|
||||
text: `UID ${uidCur.value} 暂无祈愿数据`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const res = await showConfirm({
|
||||
title: `是否导出祈愿数据?`,
|
||||
text: `UID:${user.gameUid},共 ${gachaList.length} 条数据`,
|
||||
text: `UID:${uidCur.value},共 ${gachaList.length} 条数据`,
|
||||
});
|
||||
if (!res) {
|
||||
showSnackbar({
|
||||
@@ -158,7 +156,7 @@ async function handleExportBtn(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
const file = await dialog.save({
|
||||
defaultPath: `UIGF_${user.gameUid}.json`,
|
||||
defaultPath: `UIGF_${uidCur.value}.json`,
|
||||
filters: [
|
||||
{
|
||||
name: `UIGF`,
|
||||
@@ -175,20 +173,36 @@ async function handleExportBtn(): Promise<void> {
|
||||
}
|
||||
loadingTitle.value = "正在导出祈愿数据";
|
||||
loading.value = true;
|
||||
await exportUigfData(user.gameUid, gachaList, file);
|
||||
await exportUigfData(uidCur.value, gachaList, file);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `祈愿数据已成功导出`,
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 UID 变化
|
||||
watch(uidCur, async (newUid) => {
|
||||
gachaListCur.value = await TGSqlite.getGachaRecords(newUid);
|
||||
showSnackbar({
|
||||
text: `成功获取 ${gachaListCur.value.length} 条祈愿数据`,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gacha-top-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: rgb(0 0 0/50%);
|
||||
color: #f4d8a8;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.gacha-top-select {
|
||||
height: 60px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.gacha-top-btn {
|
||||
margin: 0 10px;
|
||||
background: #393b40;
|
||||
|
||||
@@ -470,6 +470,19 @@ class Sqlite {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取已有 uid 列表
|
||||
* @since Alpha v0.2.0
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
public async getUidList(): Promise<string[]> {
|
||||
const db = await Database.load(this.dbPath);
|
||||
const sql = "SELECT DISTINCT uid FROM GachaRecords";
|
||||
const res: Array<{ uid: string }> = await db.select(sql);
|
||||
await db.close();
|
||||
return res.map((item) => item.uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取指定 uid 的用户角色数据
|
||||
* @since Alpha v0.2。3
|
||||
|
||||
Reference in New Issue
Block a user