Files
TeyvatGuide/src/pages/User/Combat.vue
2024-12-09 12:05:41 +08:00

429 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<v-app-bar>
<template #prepend>
<div class="uct-left">
<img alt="icon" src="/source/UI/userCombat.webp" />
<span>幻想真境剧诗</span>
<v-select
variant="outlined"
v-model="uidCur"
:items="uidList"
:hide-details="true"
title="游戏UID"
/>
<v-btn :rounded="true" class="uc-btn" @click="toAbyss()">
<template #prepend>
<img src="/source/UI/userAbyss.webp" alt="abyss" />
</template>
<span>深境螺旋</span>
</v-btn>
<v-btn :rounded="true" class="uc-btn" @click="loadWiki()">
<template #prepend>
<img src="/source/UI/wikiAbyss.webp" alt="abyss" />
</template>
<span>统计数据</span>
</v-btn>
</div>
</template>
<template #append>
<div class="uct-right">
<v-btn
class="uc-btn"
@click="shareCombat()"
:rounded="true"
:disabled="localCombat.length === 0"
>
<v-icon>mdi-share</v-icon>
<span>分享</span>
</v-btn>
<v-btn class="uc-btn" @click="refreshCombat()" :rounded="true">
<v-icon>mdi-refresh</v-icon>
<span>刷新</span>
</v-btn>
<v-btn class="uc-btn" @click="uploadCombat()" :rounded="true">
<v-icon>mdi-cloud-upload</v-icon>
<span>上传</span>
</v-btn>
<v-btn class="uc-btn" @click="deleteCombat()" :rounded="true">
<v-icon>mdi-delete</v-icon>
<span>删除</span>
</v-btn>
</div>
</template>
</v-app-bar>
<div class="uc-box">
<v-tabs v-model="userTab" direction="vertical" class="uc-tabs-box" center-active>
<v-tab v-for="item in localCombat" :key="item.id" :value="item.id"> {{ item.id }}</v-tab>
</v-tabs>
<v-window v-model="userTab" class="uc-window">
<v-window-item
v-for="item in localCombat"
:key="item.id"
:value="item.id"
class="uc-window-item"
>
<div :id="`user-combat-${item.id}`" class="ucw-i-ref">
<div class="ucw-top">
<div class="ucw-title">
<span></span>
<span>{{ item.id }}</span>
<span> UID</span>
<span>{{ uidCur }}</span>
<span>更新于</span>
<span>{{ item.updated }}</span>
</div>
<div class="ucw-share">真境剧诗 | Render by TeyvatGuide v{{ version }}</div>
</div>
<TSubLine>统计周期 {{ item.startTime }} ~ {{ item.endTime }}</TSubLine>
<TucOverview :data="item.stat" :fights="item.detail.fight_statisic" />
<TSubLine>使用角色({{ item.detail.backup_avatars.length }})</TSubLine>
<TucAvatars :model-value="item.detail.backup_avatars" />
<TSubLine>详情</TSubLine>
<div class="ucw-rounds">
<TucRound
v-for="(round, idx) in item.detail.rounds_data"
:key="idx"
:model-value="round"
/>
</div>
</div>
</v-window-item>
</v-window>
<div v-show="localCombat.length === 0" class="user-empty">
<img src="/source/UI/empty.webp" alt="empty" />
<span>暂无数据请尝试刷新</span>
</div>
</div>
<TucOverlay v-model="showData" :data="cloudCombat" />
</template>
<script lang="ts" setup>
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import TSubLine from "../../components/app/t-subline.vue";
import showDialog from "../../components/func/dialog.js";
import showLoading from "../../components/func/loading.js";
import showSnackbar from "../../components/func/snackbar.js";
import TucAvatars from "../../components/userCombat/tuc-avatars.vue";
import TucOverlay from "../../components/userCombat/tuc-overlay.vue";
import TucOverview from "../../components/userCombat/tuc-overview.vue";
import TucRound from "../../components/userCombat/tuc-round.vue";
import Hutao from "../../plugins/Hutao/index.js";
import TSUserCombat from "../../plugins/Sqlite/modules/userCombat.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import TakumiRecordGenshinApi from "../../web/request/recordReq.js";
// store
const userStore = storeToRefs(useUserStore());
// data
const userTab = ref<number>(0);
const user = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
const localCombat = ref<TGApp.Sqlite.Combat.SingleTable[]>([]);
const combatRef = ref<HTMLElement>(<HTMLElement>{});
const cloudCombat = ref<TGApp.Plugins.Hutao.Combat.Data>();
const showData = ref<boolean>(false);
const version = ref<string>();
const router = useRouter();
const uidList = ref<string[]>();
const uidCur = ref<string>();
const combatIdList = computed<number[]>(() => {
return localCombat.value.map((combat) => combat.id);
});
onMounted(async () => {
showLoading.start("正在加载剧诗数据...");
version.value = await getVersion();
await TGLogger.Info("[UserCombat][onMounted] 打开真境剧诗页面");
showLoading.update("正在加载用户数据...");
uidList.value = await TSUserCombat.getAllUid();
if (uidList.value.includes(user.value.gameUid)) uidCur.value = user.value.gameUid;
else if (uidList.value.length > 0) uidCur.value = uidList.value[0];
else uidCur.value = "";
await loadCombat();
showLoading.end();
});
watch(
() => uidCur.value,
async () => await loadCombat(),
);
async function toAbyss(): Promise<void> {
await router.push({ name: "深渊记录" });
}
async function loadCombat(): Promise<void> {
localCombat.value = [];
if (uidCur.value === undefined || uidCur.value === "") return;
localCombat.value = await TSUserCombat.getCombat(uidCur.value);
if (localCombat.value.length > 0) userTab.value = localCombat.value[0].id;
}
async function loadWiki(): Promise<void> {
showLoading.start("正在加载统计数据...");
const res = await Hutao.Combat.data();
if (res === undefined) showSnackbar.error("未获取到剧诗数据");
else cloudCombat.value = <TGApp.Plugins.Hutao.Combat.Data>res;
showLoading.end();
showSnackbar.success("成功获取统计数据");
showData.value = true;
}
async function refreshCombat(): Promise<void> {
if (!userStore.cookie.value) {
showSnackbar.error("未登录");
await TGLogger.Warn("[UserCombat][getAbyssData] 未登录");
return;
}
if (uidCur.value && uidCur.value !== user.value.gameUid) {
const switchCheck = await showDialog.check(
"是否切换游戏账户",
`确认则尝试切换至 ${uidCur.value}`,
);
if (switchCheck) {
await useUserStore().switchGameAccount(uidCur.value);
await refreshCombat();
return;
}
const freshCheck = await showDialog.check(
"确定刷新?",
`用户${user.value.gameUid}与当前UID${uidCur.value}不一致`,
);
if (!freshCheck) {
showSnackbar.cancel("已取消剧诗数据刷新");
return;
}
}
await TGLogger.Info("[UserCombat][getCombatData] 更新剧诗数据");
showLoading.start("正在获取剧诗数据...", `UID: ${user.value.gameUid}`);
const res = await TakumiRecordGenshinApi.roleCombat(userStore.cookie.value, user.value);
if (res === false) {
showLoading.end();
showSnackbar.warn("用户未解锁幻想真境剧诗");
return;
}
if ("retcode" in res) {
showLoading.end();
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error(`[UserCombat][getCombatData] 获取${user.value.gameUid}的剧诗数据失败`);
await TGLogger.Error(`[UserCombat][getCombatData] ${res.retcode} ${res.message}`);
return;
}
showLoading.update("正在保存剧诗数据...");
for (const combat of res) {
showLoading.update("正在保存剧诗数据...", `${combat.schedule.schedule_id}`);
await TSUserCombat.saveCombat(user.value.gameUid, combat);
}
showLoading.update("正在加载剧诗数据...");
uidList.value = await TSUserCombat.getAllUid();
uidCur.value = user.value.gameUid;
await loadCombat();
showLoading.end();
}
async function shareCombat(): Promise<void> {
await TGLogger.Info(`[UserCombat][shareCombat][${userTab.value}] 生成剧诗数据分享图片`);
const fileName = `【剧诗数据】${userTab.value}-${user.value.gameUid}`;
showLoading.start("正在生成图片", `${fileName}.png`);
combatRef.value = <HTMLElement>document.getElementById(`user-combat-${userTab.value}`);
await generateShareImg(fileName, combatRef.value);
showLoading.end();
await TGLogger.Info(`[UserCombat][shareCombat][${userTab.value}] 生成剧诗数据分享图片成功`);
}
async function uploadCombat(): Promise<void> {
await TGLogger.Info("[UserCombat][uploadCombat] 上传剧诗数据");
const combatData = localCombat.value.find((item) => item.id === Math.max(...combatIdList.value));
if (!combatData) {
showSnackbar.error("未找到剧诗数据");
await TGLogger.Warn("[UserCombat][uploadCombat] 未找到深渊数据");
return;
}
if (!combatData.hasDetailData) {
showSnackbar.error("未获取到详情数据");
await TGLogger.Warn(`[UserCombat][uploadCombat] 未获取到详细数据`);
return;
}
const startTime = new Date(combatData.startTime).getTime();
const endTime = new Date(combatData.endTime).getTime();
const nowTime = new Date().getTime();
if (nowTime < startTime || nowTime > endTime) {
showSnackbar.warn("非最新剧诗数据,请刷新剧诗数据后重试!");
await TGLogger.Warn("[UserCombat][uploadCombat] 非最新剧诗数据");
return;
}
try {
showLoading.start("正在上传剧诗数据");
const transCombat = Hutao.Combat.trans(combatData);
const res = await Hutao.Combat.upload(transCombat);
showLoading.end();
if (res.retcode === 0) {
showSnackbar.success(res.message ?? "上传剧诗数据成功");
await TGLogger.Info("[UserCombat][uploadCombat] 上传剧诗数据成功");
} else {
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error("[UserCombat][uploadCombat] 上传剧诗数据失败");
await TGLogger.Error(`[UserCombat][uploadCombat] ${res.retcode} ${res.message}`);
}
} catch (e) {
if (e instanceof Error) {
showSnackbar.error(e.message);
await TGLogger.Error("[UserCombat][uploadCombat] 上传剧诗数据失败");
await TGLogger.Error(`[UserCombat][uploadCombat] ${e.message}`);
}
}
showLoading.end();
}
async function deleteCombat(): Promise<void> {
if (uidCur.value === undefined || uidCur.value === "") {
showSnackbar.error("未找到符合条件的数据!");
return;
}
const delCheck = await showDialog.check("确定删除数据?", `将清除${uidCur.value}的所有剧诗数据`);
if (!delCheck) {
showSnackbar.cancel("已取消删除");
return;
}
showLoading.start(`正在删除 ${uidCur.value} 的剧诗数据`);
await TSUserCombat.delCombat(uidCur.value);
showLoading.update("正在加载剧诗数据...");
showSnackbar.success(`已清除 ${uidCur.value} 的剧诗数据`);
uidList.value = await TSUserCombat.getAllUid();
if (uidList.value.length > 0) uidCur.value = uidList.value[0];
else uidCur.value = undefined;
await loadCombat();
showLoading.end();
}
</script>
<style lang="css" scoped>
.uct-left {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 10px;
gap: 10px;
img {
width: 32px;
height: 32px;
}
span {
font-family: var(--font-title);
font-size: 20px;
}
span :first-child {
color: var(--common-text-title);
}
}
.uct-right {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 10px;
gap: 10px;
}
.uc-btn {
background: var(--tgc-btn-1);
color: var(--btn-text);
font-family: var(--font-text);
}
.uc-box {
display: flex;
height: calc(100vh - 100px);
align-items: flex-start;
justify-content: center;
border: 1px solid var(--common-shadow-4);
border-radius: 5px;
}
.uc-tabs-box {
max-height: 100%;
overflow-y: auto;
}
.uc-window {
overflow: hidden;
width: calc(100% - 100px);
height: 100%;
padding: 10px;
}
.uc-window-item {
height: 100%;
padding: 10px;
border-radius: 5px;
overflow-y: auto;
}
.ucw-i-ref {
display: flex;
flex-direction: column;
gap: 5px;
}
.ucw-top {
display: flex;
width: 100%;
align-items: flex-end;
justify-content: space-between;
}
.ucw-title {
display: flex;
align-items: center;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.ucw-title :nth-child(2n) {
margin-right: 10px;
margin-left: 10px;
color: var(--tgc-yellow-1);
}
.ucw-share {
z-index: -1;
font-size: 12px;
opacity: 0.8;
}
.user-empty {
position: absolute;
top: calc(50vh - 200px);
left: calc(50vw - 400px);
display: flex;
width: 800px;
height: 400px;
flex-direction: column;
align-items: center;
border-radius: 5px;
background: var(--common-shadow-t-2);
box-shadow: 0 0 5px var(--common-shadow-2);
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 1.5rem;
}
.ucw-rounds {
display: grid;
gap: 10px;
grid-template-columns: repeat(2, 1fr);
}
</style>