胡桃深渊数据库

This commit is contained in:
BTMuli
2025-12-31 16:16:42 +08:00
parent b29c94bf02
commit 5894c46c1d
6 changed files with 276 additions and 8 deletions

View File

@@ -116,6 +116,11 @@
</v-list-item>
</template>
<v-list :nav="true" class="side-list-menu sub" density="compact">
<v-list-item class="side-item-menu" title="深渊数据库" :link="true" href="/wiki/abyss">
<template #prepend>
<img src="/source/UI/wikiAbyss.webp" alt="abyssIcon" class="side-icon-menu" />
</template>
</v-list-item>
<v-list-item :link="true" class="side-item-menu" href="/wiki/character" title="角色图鉴">
<template #prepend>
<img alt="characterIcon" class="side-icon-menu" src="/source/UI/wikiAvatar.webp" />

View File

@@ -50,6 +50,20 @@
</v-btn>
</div>
</template>
<template #extension>
<div class="uat-extension">
<v-btn :rounded="true" variant="elevated" class="ua-btn" @click="toWiki()">
<img alt="wiki" src="/source/UI/wikiAbyss.webp" />
<span>统计数据</span>
</v-btn>
<div class="uat-extension-right">
<span @click="editHutaoEmail()">{{ hutaoEmail ?? "胡桃云邮箱" }}</span>
<v-btn class="ua-btn" prepend-icon="mdi-upload" variant="elevated" @click="uploadAbyss()">
上传
</v-btn>
</div>
</div>
</template>
</v-app-bar>
<div class="ua-box">
<v-tabs v-model="userTab" center-active class="ua-tabs-box" direction="vertical">
@@ -110,8 +124,10 @@ import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TuaDetail from "@comp/userAbyss/tua-detail.vue";
import TuaOverview from "@comp/userAbyss/tua-overview.vue";
import hutao from "@Hutao/index.js";
import recordReq from "@req/recordReq.js";
import TSUserAbyss from "@Sqlm/userAbyss.js";
import TSUserAvatar from "@Sqlm/userAvatar.js";
import useAppStore from "@store/app.js";
import useUserStore from "@store/user.js";
import { getVersion } from "@tauri-apps/api/app";
@@ -125,7 +141,7 @@ import { useRouter } from "vue-router";
const router = useRouter();
const { isLogin } = storeToRefs(useAppStore());
const { account, cookie } = storeToRefs(useUserStore());
const { account, cookie, hutaoEmail } = storeToRefs(useUserStore());
const userTab = ref<number>(0);
const version = ref<string>();
const uidCur = ref<string>();
@@ -163,6 +179,33 @@ async function toChallenge(): Promise<void> {
await router.push({ name: "幽境危战" });
}
async function toWiki(): Promise<void> {
await router.push({ name: "深渊数据库" });
}
async function editHutaoEmail(): Promise<void> {
if (hutaoEmail.value) {
const chgCheck = await showDialog.check("是否更改胡桃云账号", `当前账号:${hutaoEmail.value}`);
if (!chgCheck) {
showSnackbar.cancel("已取消更改胡桃云账号");
return;
}
}
const newEmail = await showDialog.input("请输入胡桃云账号", "胡桃云账号", hutaoEmail.value);
if (!newEmail) {
showSnackbar.cancel("已取消设置胡桃云账号");
return;
}
// 简单验证邮箱格式
const mailReg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
if (!mailReg.test(newEmail)) {
showSnackbar.error("邮箱格式错误");
return;
}
hutaoEmail.value = newEmail;
showSnackbar.success("已设置胡桃云账号");
}
async function loadAbyss(): Promise<void> {
localAbyss.value = [];
if (uidCur.value === undefined || uidCur.value === "") return;
@@ -306,6 +349,67 @@ async function tryReadAbyss(): Promise<void> {
showSnackbar.error("导入深渊数据失败,请检查文件格式是否正确");
}
}
async function uploadAbyss(): Promise<void> {
if (!hutaoEmail.value || hutaoEmail.value === "") {
const check = await showDialog.check("确定上传?", "未设置胡桃云账号");
if (!check) return;
}
await TGLogger.Info("[UserAbyss][uploadAbyss] 上传深渊数据");
const maxId = Math.max(...localAbyss.value.map((i) => i.id));
const abyssData = localAbyss.value.find((item) => item.id === maxId);
if (!abyssData) {
showSnackbar.warn("未找到深渊数据");
await TGLogger.Warn("[UserAbyss][uploadAbyss] 未找到深渊数据");
return;
}
const maxFloor = Number(abyssData.maxFloor.split("-")[0]);
if (isNaN(maxFloor) || maxFloor <= 9) {
showSnackbar.warn("尚未完成深渊,请完成深渊后重试!");
await TGLogger.Warn(`[UserAbyss][uploadAbyss] 尚未完成深渊 ${abyssData.maxFloor}`);
return;
}
const startTime = new Date(abyssData.startTime).getTime();
const endTime = new Date(abyssData.endTime).getTime();
const nowTime = new Date().getTime();
if (nowTime < startTime || nowTime > endTime) {
showSnackbar.warn("非最新深渊数据,请刷新深渊数据后重试!");
await TGLogger.Warn("[UserAbyss][uploadAbyss] 非最新深渊数据");
return;
}
try {
await showLoading.start(`正在上传${account.value.gameUid}的深渊数据`, `期数:${abyssData.id}`);
const transAbyss = hutao.Abyss.utils.transData(abyssData);
if (hutaoEmail.value) transAbyss.ReservedUserName = hutaoEmail.value;
await showLoading.update("正在获取角色数据");
const roles = await TSUserAvatar.getAvatars(Number(account.value.gameUid));
if (!roles) {
await showLoading.end();
showSnackbar.warn("未找到角色数据");
return;
}
await showLoading.update("正在转换角色数据");
transAbyss.Avatars = hutao.Abyss.utils.transAvatars(roles);
await showLoading.update("正在上传深渊数据");
const res = await hutao.Abyss.upload(transAbyss);
if (res.retcode !== 0) {
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error("[UserAbyss][uploadAbyss] 上传深渊数据失败");
await TGLogger.Error(`[UserAbyss][uploadAbyss] ${res.retcode} ${res.message}`);
return;
}
showSnackbar.success(res.message ?? "上传深渊数据成功");
await TGLogger.Info("[UserAbyss][uploadAbyss] 上传深渊数据成功");
await TGLogger.Info(`[${res.retcode}] ${res.message}`);
} catch (e) {
if (e instanceof Error) {
showSnackbar.error(e.message);
await TGLogger.Error("[UserAbyss][uploadAbyss] 上传深渊数据失败");
await TGLogger.Error(`[UserAbyss][uploadAbyss] ${e.message}`);
}
}
await showLoading.end();
}
</script>
<style lang="css" scoped>
.uat-left {
@@ -352,6 +456,31 @@ async function tryReadAbyss(): Promise<void> {
}
}
.uat-extension {
position: relative;
display: flex;
width: 100%;
box-sizing: border-box;
align-items: center;
justify-content: space-between;
padding-right: 16px;
padding-left: 16px;
margin-bottom: 4px;
}
.uat-extension-right {
position: relative;
display: flex;
align-items: center;
justify-content: center;
column-gap: 8px;
span {
color: var(--tgc-od-red);
cursor: pointer;
}
}
.ua-box {
display: flex;
height: calc(100vh - 96px);

View File

@@ -55,6 +55,25 @@
</v-btn>
</div>
</template>
<template #extension>
<div class="uct-extension">
<v-btn class="uc-btn" variant="elevated" @click="loadWiki()">
<img alt="abyss" src="/source/UI/wikiAbyss.webp" />
<span>统计数据</span>
</v-btn>
<div class="uct-extension-right">
<span @click="editHutaoEmail()">{{ hutaoEmail ?? "设置胡桃云邮箱" }}</span>
<v-btn
class="uc-btn"
prepend-icon="mdi-cloud-upload"
variant="elevated"
@click="uploadCombat()"
>
上传
</v-btn>
</div>
</div>
</template>
</v-app-bar>
<div class="uc-box">
<v-tabs v-model="userTab" center-active class="uc-tabs-box" direction="vertical">
@@ -97,6 +116,7 @@
<span>暂无数据请尝试刷新</span>
</div>
</div>
<TucOverlay v-model="showData" :data="cloudCombat" />
</template>
<script lang="ts" setup>
import TSubLine from "@comp/app/t-subline.vue";
@@ -104,8 +124,10 @@ import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TucAvatars from "@comp/userCombat/tuc-avatars.vue";
import TucOverlay from "@comp/userCombat/tuc-overlay.vue";
import TucOverview from "@comp/userCombat/tuc-overview.vue";
import TucRound from "@comp/userCombat/tuc-round.vue";
import hutao from "@Hutao/index.js";
import recordReq from "@req/recordReq.js";
import TSUserCombat from "@Sqlm/userCombat.js";
import useAppStore from "@store/app.js";
@@ -121,11 +143,13 @@ import { useRouter } from "vue-router";
const router = useRouter();
const { isLogin } = storeToRefs(useAppStore());
const { account, cookie } = storeToRefs(useUserStore());
const { account, cookie, hutaoEmail } = storeToRefs(useUserStore());
const userTab = ref<number>(0);
const version = ref<string>();
const uidCur = ref<string>();
const showData = ref<boolean>(false);
const uidList = shallowRef<Array<string>>();
const cloudCombat = shallowRef<TGApp.Plugins.Hutao.Combat.Data>();
const localCombat = shallowRef<Array<TGApp.Sqlite.Combat.TableTrans>>([]);
onMounted(async () => {
@@ -151,6 +175,16 @@ async function toChallenge(): Promise<void> {
await router.push({ name: "幽境危战" });
}
async function loadWiki(): Promise<void> {
await showLoading.start("正在加载统计数据");
const res = await hutao.Combat.data();
if (res === undefined) showSnackbar.error("未获取到剧诗数据");
else cloudCombat.value = <TGApp.Plugins.Hutao.Combat.Data>res;
await showLoading.end();
showSnackbar.success("成功获取统计数据");
showData.value = true;
}
async function reloadUid(): Promise<void> {
uidList.value = await TSUserCombat.getAllUid();
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
@@ -168,6 +202,29 @@ async function loadCombat(): Promise<void> {
if (localCombat.value.length > 0) userTab.value = localCombat.value[0].id;
}
async function editHutaoEmail(): Promise<void> {
if (hutaoEmail.value) {
const chgCheck = await showDialog.check("是否更改胡桃云账号", `当前账号:${hutaoEmail.value}`);
if (!chgCheck) {
showSnackbar.cancel("已取消更改胡桃云账号");
return;
}
}
const newEmail = await showDialog.input("请输入胡桃云账号", "胡桃云账号", hutaoEmail.value);
if (!newEmail) {
showSnackbar.cancel("已取消设置胡桃云账号");
return;
}
// 简单验证邮箱格式
const mailReg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
if (!mailReg.test(newEmail)) {
showSnackbar.error("邮箱格式错误");
return;
}
hutaoEmail.value = newEmail;
showSnackbar.success("已设置胡桃云账号");
}
async function refreshCombat(): Promise<void> {
if (!cookie.value) {
showSnackbar.error("未登录");
@@ -254,6 +311,54 @@ async function deleteCombat(): Promise<void> {
await showLoading.end();
}
async function uploadCombat(): Promise<void> {
if (!hutaoEmail.value || hutaoEmail.value === "") {
const check = await showDialog.check("确定上传?", "未设置胡桃云账号");
if (!check) return;
}
await TGLogger.Info("[UserCombat][uploadCombat] 上传剧诗数据");
const maxId = Math.max(...localCombat.value.map((i) => i.id));
const combatData = localCombat.value.find((item) => item.id === maxId);
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 {
await showLoading.start("正在上传剧诗数据");
const transCombat = hutao.Combat.trans(combatData);
const res = await hutao.Combat.upload(transCombat);
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}`);
}
}
await showLoading.end();
}
/**
* 尝试读取胡桃工具箱导出的剧诗数据
* @since Beta v0.8.6
@@ -318,6 +423,31 @@ async function tryReadCombat(): Promise<void> {
}
}
.uct-extension {
position: relative;
display: flex;
width: 100%;
box-sizing: border-box;
align-items: center;
justify-content: space-between;
padding-right: 16px;
padding-left: 16px;
margin-bottom: 4px;
}
.uct-extension-right {
position: relative;
display: flex;
align-items: center;
justify-content: center;
column-gap: 8px;
span {
color: var(--tgc-od-red);
cursor: pointer;
}
}
.uct-right {
display: flex;
align-items: center;

View File

@@ -52,7 +52,6 @@ import HtaTabHold from "@comp/hutaoAbyss/hta-tab-hold.vue";
import HtaTabTeam from "@comp/hutaoAbyss/hta-tab-team.vue";
import HtaTabUp from "@comp/hutaoAbyss/hta-tab-up.vue";
import HtaTabUse from "@comp/hutaoAbyss/hta-tab-use.vue";
import Hutao from "@Hutao/index.js";
import hutao from "@Hutao/index.js";
import { timestampToDate } from "@utils/toolFunc.js";
import { onMounted, reactive, ref, type ShallowRef, shallowRef, watch } from "vue";
@@ -147,7 +146,7 @@ async function getUseData(): Promise<void> {
} else {
cur = curResp;
}
const lastResp = await Hutao.Abyss.avatar.use(true);
const lastResp = await hutao.Abyss.avatar.use(true);
if (!Array.isArray(lastResp)) {
await showLoading.update(`[${lastResp.retcode}] ${lastResp.message}`);
} else {
@@ -166,7 +165,7 @@ async function getUpData(): Promise<void> {
} else {
cur = curResp;
}
const lastResp = await Hutao.Abyss.avatar.use(true);
const lastResp = await hutao.Abyss.avatar.use(true);
if (!Array.isArray(lastResp)) {
await showLoading.update(`[${lastResp.retcode}] ${lastResp.message}`);
} else {
@@ -195,7 +194,7 @@ async function getHoldData(): Promise<void> {
} else {
cur = curResp;
}
const lastResp = await Hutao.Abyss.avatar.hold(true);
const lastResp = await hutao.Abyss.avatar.hold(true);
if (!Array.isArray(lastResp)) {
await showLoading.update(`[${lastResp.retcode}] ${lastResp.message}`);
} else {

View File

@@ -1,10 +1,15 @@
/**
* wiki 路由模块
* @since Beta v0.8.6
* @since Beta v0.9.1
*/
import type { RouteRecordRaw } from "vue-router";
const wikiRoutes = (<const>[
{
path: "/wiki/abyss",
name: "深渊数据库",
component: async () => await import("@/pages/WIKI/Abyss.vue"),
},
{
path: "/wiki/character/:id?",
name: "角色图鉴",

View File

@@ -33,7 +33,7 @@ const useUserStore = defineStore(
const cookie = ref<TGApp.App.Account.Cookie>();
const propMap = ref<TGApp.Game.Avatar.PropMap>();
// 胡桃账号(邮箱),用于上传深渊记录
/** 胡桃云邮箱 */
const hutaoEmail = ref<string>();
function getProp(prop: number): TGApp.Game.Avatar.PropMapItem | false {