🌱 幽境危战数据库操作

#157
This commit is contained in:
BTMuli
2025-07-01 16:21:13 +08:00
parent 8f4d3d2285
commit 1718b87adf
11 changed files with 754 additions and 86 deletions

View File

@@ -37,7 +37,7 @@
label="服务器"
width="200px"
density="compact"
:disabled="isReq"
:disabled="reqPop"
/>
</div>
</template>
@@ -72,14 +72,52 @@
</div>
</template>
</v-app-bar>
<div class="user-challenge-box">
<v-tabs
v-model="userTab"
direction="vertical"
class="ucb-tabs"
center-active
v-if="localChallenge.length > 0"
>
<v-tab v-for="item in localChallenge" :key="item.id" :value="item.id">
<div class="ucb-tab">
<span>{{ item.name }}</span>
<span>{{ item.startTime.slice(0, 10) }} ~ {{ item.endTime.slice(0, 10) }}</span>
</div>
</v-tab>
</v-tabs>
<v-window v-model="userTab" class="ucb-window">
<v-window-item
v-for="item in localChallenge"
:key="item.id"
:value="item.id"
class="ucb-window-item"
>
<div :id="`user-challenge-${item.id}`" class="ucb-window-box">
{{ JSON.stringify(item, null, 2) }}
</div>
</v-window-item>
</v-window>
<div v-show="localChallenge.length === 0" class="ucb-empty">
<img src="/source/UI/empty.webp" alt="empty" />
<span>暂无数据请尝试刷新</span>
</div>
</div>
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TucPopItem from "@comp/userChallenge/tuc-pop-item.vue";
import { GameServerEnum, getGameServerDesc } from "@enum/game.js";
import recordReq from "@req/recordReq.js";
import TSUserChallenge from "@Sqlm/userChallenge.js";
import useUserStore from "@store/user.js";
import { getVersion } from "@tauri-apps/api/app";
import TGLogger from "@utils/TGLogger.js";
import { generateShareImg } from "@utils/TGShare.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
@@ -95,19 +133,25 @@ const serverList: ReadonlyArray<SelectItem<TGApp.Game.Base.ServerTypeEnum>> = [
].map((i) => ({ text: getGameServerDesc(i), value: i }));
const router = useRouter();
const { account, cookie } = storeToRefs(useUserStore());
const version = ref<string>();
const isReq = ref<boolean>(false);
const userTab = ref<number>(0);
const uidCur = ref<string>();
const uidList = shallowRef<Array<string>>();
const localChallenge = shallowRef<unknown[]>([]);
const localChallenge = shallowRef<Array<TGApp.Sqlite.Challenge.SingleTable>>([]);
const server = ref<TGApp.Game.Base.ServerTypeEnum>(GameServerEnum.CN_GF01);
const reqPop = ref<boolean>(false);
const popList = shallowRef<Array<TGApp.Game.Challenge.PopularityItem>>([]);
onMounted(async () => {
version.value = await getVersion();
await TGLogger.Info("[UserCombat][onMounted] 打开幽境危战页面");
await refreshPopList();
// TODO: 获取数据库数据
await reloadChallenge();
});
watch(
@@ -132,11 +176,109 @@ async function loadWiki(): Promise<void> {
}
async function shareChallenge(): Promise<void> {
// TODO: 实现分享功能
await TGLogger.Info(`[UserChallenge][shareChallenge][${userTab.value}] 生成幽境危战分享图片`);
const challengeFind = localChallenge.value.find((i) => i.id === userTab.value);
if (!challengeFind) {
showSnackbar.warn("未找到对应的挑战记录");
await TGLogger.Warn("[UserChallenge][shareChallenge] 未找到对应的挑战记录");
return;
}
const fileName = `【幽境危战】【${challengeFind.name}${challengeFind.id}-${uidCur.value}.png`;
const shareDom = document.querySelector<HTMLDivElement>(`#user-challenge-${challengeFind.id}`);
if (shareDom === null) {
showSnackbar.warn("未找到对应的挑战记录DOM");
await TGLogger.Warn("[UserChallenge][shareChallenge] 未找到对应的挑战记录DOM");
return;
}
await showLoading.start("正在生成分享图片", fileName);
await generateShareImg(fileName, shareDom);
await showLoading.end();
await TGLogger.Info(`[UserChallenge][shareChallenge][${userTab.value}] 成功生成分享图片`);
}
async function reloadChallenge(): Promise<void> {
localChallenge.value = [];
uidList.value = [];
await showLoading.start("正在加载UID列表");
uidList.value = await TSUserChallenge.getAllUid();
if (uidList.value.length === 0) {
uidCur.value = "";
} else {
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
else uidCur.value = uidList.value[0];
await showLoading.update(`正在加载UID${uidCur.value}的幽境危战数据`);
await loadChallenge();
}
await showLoading.end();
if (uidCur.value?.length > 0) {
showSnackbar.success(
`已加载UID ${uidCur.value}${localChallenge.value.length} 条幽境危战数据`,
);
} else {
showSnackbar.warn("未检测到可用UID请尝试刷新数据");
}
}
async function loadChallenge(): Promise<void> {
localChallenge.value = [];
if (uidCur.value === undefined || uidCur.value === "") return;
localChallenge.value = await TSUserChallenge.getChallenge(uidCur.value);
if (localChallenge.value.length > 0) userTab.value = localChallenge.value[0].id;
}
async function refreshChallenge(): Promise<void> {
// TODO: 实现刷新挑战记录功能
if (isReq.value) return;
if (!cookie.value) {
showSnackbar.error("未登录");
await TGLogger.Warn("[UserChallenge][refreshChallenge] 未登录");
return;
}
if (uidCur.value && uidCur.value !== account.value.gameUid) {
const switchCheck = await showDialog.check(
"是否切换游戏账户",
`确认则尝试切换至 ${uidCur.value}`,
);
if (switchCheck) {
await useUserStore().switchGameAccount(uidCur.value);
await refreshChallenge();
return;
}
const freshCheck = await showDialog.check(
"确定刷新?",
`用户${account.value.gameUid}与当前UID${uidCur.value}不一致`,
);
if (!freshCheck) {
showSnackbar.cancel("已取消幽境危战数据刷新");
return;
}
}
isReq.value = true;
await TGLogger.Info("[UserChallenge][refreshChallenge] 开始刷新挑战数据");
await showLoading.start(`正在获取${account.value.gameUid}的幽境危战数据`);
const res = await recordReq.challenge.detail(cookie.value, account.value);
if ("retcode" in res) {
await showLoading.end();
isReq.value = false;
showSnackbar.error(`[${res.retcode}] ${res.message}`);
await TGLogger.Error(`[UserChallenge][refreshChallenge] ${res.retcode} - ${res.message}`);
return;
}
if (!res.is_unlock) {
await showLoading.end();
isReq.value = false;
showSnackbar.warn("幽境危战未解锁");
await TGLogger.Warn("[UserChallenge][refreshChallenge] 幽境危战未解锁");
return;
}
await showLoading.update("", { title: "正在保存幽境危战数据" });
for (const challenge of res.data) {
if (challenge.schedule.schedule_id === "0") continue;
await showLoading.update(`ScheduleID${challenge.schedule.schedule_id}`);
await TSUserChallenge.saveChallenge(account.value.gameUid, challenge);
}
isReq.value = false;
await showLoading.end();
await reloadChallenge();
}
async function uploadChallenge(): Promise<void> {
@@ -144,11 +286,19 @@ async function uploadChallenge(): Promise<void> {
}
async function deleteChallenge(): Promise<void> {
if (localChallenge.value.length === 0) {
if (uidCur.value === undefined || uidCur.value === "" || localChallenge.value.length === 0) {
showSnackbar.warn("没有可删除的挑战记录");
return;
}
// TODO: 实现删除挑战记录功能
const delCheck = await showDialog.check("确认删除?", "此操作将删除当前UID的所有幽境危战记录");
if (!delCheck) {
showSnackbar.cancel("已取消幽境危战数据删除");
return;
}
await showLoading.start("正在删除幽境危战数据", `UID: ${uidCur.value}`);
await TSUserChallenge.delChallenge(uidCur.value);
showSnackbar.success(`已清除 ${uidCur.value} 的幽境危战数据`);
await reloadChallenge();
}
async function refreshPopList(): Promise<void> {
@@ -168,7 +318,9 @@ async function refreshPopList(): Promise<void> {
popList.value = resp.data.avatar_list;
await showLoading.end();
reqPop.value = false;
showSnackbar.success(`加载完成,共 ${popList.value.length} 位赋光之人`);
showSnackbar.success(
`已刷新 ${getGameServerDesc(server.value)}${popList.value.length} 位赋光之人`,
);
}
</script>
<style lang="scss" scoped>
@@ -256,4 +408,75 @@ async function refreshPopList(): Promise<void> {
.dark .pop-btn {
border: 1px solid var(--common-shadow-2);
}
.user-challenge-box {
display: flex;
height: calc(100vh - 144px);
align-items: flex-start;
justify-content: center;
border: 1px solid var(--common-shadow-2);
border-radius: 4px;
background: var(--box-bg-1);
}
.ucb-tabs {
max-width: 200px;
max-height: 100%;
overflow-y: auto;
}
.ucb-tab {
position: relative;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
align-items: flex-start;
justify-content: center;
span {
&:last-child {
font-size: 10px;
opacity: 0.6;
}
}
}
.ucb-window {
overflow: hidden;
width: 100%;
height: 100%;
padding: 8px;
background: var(--app-page-bg);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
}
.ucb-window-item {
height: 100%;
padding-right: 8px;
overflow-y: auto;
}
.ucb-window-box {
display: flex;
flex-direction: column;
gap: 8px;
}
.ucb-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);
}
</style>

View File

@@ -135,14 +135,15 @@ onMounted(async () => {
await TGLogger.Info("[UserCombat][onMounted] 打开真境剧诗页面");
await showLoading.update("正在加载UID列表");
uidList.value = await TSUserCombat.getAllUid();
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
else if (uidList.value.length > 0) {
uidCur.value = uidList.value[0];
if (uidList.value.length === 0) {
uidCur.value = "";
} else {
if (uidList.value.includes(account.value.gameUid)) uidCur.value = account.value.gameUid;
else uidCur.value = uidList.value[0];
await showLoading.update(`正在加载UID${uidCur.value}的剧诗数据`);
} else uidCur.value = "";
}
await loadCombat();
await showLoading.end();
console.log("UserCombat", localCombat.value);
});
watch(() => uidCur.value, loadCombat);
@@ -150,6 +151,7 @@ watch(() => uidCur.value, loadCombat);
async function toAbyss(): Promise<void> {
await router.push({ name: "深境螺旋" });
}
async function toChallenge(): Promise<void> {
await router.push({ name: "幽境危战" });
}
@@ -174,7 +176,7 @@ async function loadWiki(): Promise<void> {
async function refreshCombat(): Promise<void> {
if (!cookie.value) {
showSnackbar.error("未登录");
await TGLogger.Warn("[UserCombat][getAbyssData] 未登录");
await TGLogger.Warn("[UserCombat][refreshCombat] 未登录");
return;
}
if (uidCur.value && uidCur.value !== account.value.gameUid) {
@@ -196,7 +198,7 @@ async function refreshCombat(): Promise<void> {
return;
}
}
await TGLogger.Info("[UserCombat][getCombatData] 更新剧诗数据");
await TGLogger.Info("[UserCombat][refreshCombat] 更新剧诗数据");
await showLoading.start(`正在获取${account.value.gameUid}的剧诗数据`);
const res = await recordReq.roleCombat(cookie.value, account.value);
if (res === false) {
@@ -207,8 +209,8 @@ async function refreshCombat(): Promise<void> {
if ("retcode" in res) {
await showLoading.end();
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error(`[UserCombat][getCombatData] 获取${account.value.gameUid}的剧诗数据失败`);
await TGLogger.Error(`[UserCombat][getCombatData] ${res.retcode} ${res.message}`);
await TGLogger.Error(`[UserCombat][refreshCombat] 获取${account.value.gameUid}的剧诗数据失败`);
await TGLogger.Error(`[UserCombat][refreshCombat] ${res.retcode} ${res.message}`);
return;
}
await showLoading.update("正在保存剧诗数据");
@@ -225,7 +227,7 @@ async function refreshCombat(): Promise<void> {
async function shareCombat(): Promise<void> {
await TGLogger.Info(`[UserCombat][shareCombat][${userTab.value}] 生成剧诗数据分享图片`);
const fileName = `【剧诗数据${userTab.value}-${account.value.gameUid}.png`;
const fileName = `真境剧诗】${userTab.value}-${uidCur.value}.png`;
const shareDom = document.querySelector<HTMLElement>(`#user-combat-${userTab.value}`);
if (shareDom === null) {
showSnackbar.error("未找到分享数据");

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/index.ts
* @description Sqlite 数据库操作类
* @since Beta v0.6.1
* @since Beta v0.8.0
*/
import { app } from "@tauri-apps/api";
@@ -19,6 +19,8 @@ class Sqlite {
"GachaRecords",
"GameAccount",
"SpiralAbyss",
"RoleCombat",
"HardChallenge",
"UFCollection",
"UFMap",
"UFPost",

View File

@@ -0,0 +1,177 @@
/**
* @file sqlite/modules/userChallenge.ts
* @description 幽境危战模块
* @since Beta v0.8.0
*/
import { path } from "@tauri-apps/api";
import { exists, mkdir, readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
import { timestampToDate } from "@utils/toolFunc.js";
import TGSqlite from "../index.js";
/**
* @description 将通过 api 获取到的数据转换为数据库中的数据
* @since Beta v0.8.0
* @param {TGApp.Game.Challenge.ChallengeItem} data - 挑战数据
* @returns {TGApp.Sqlite.Challenge.SingleTable} 转换后的数据
*/
function transUserChallenge(
data: TGApp.Game.Challenge.ChallengeItem,
): TGApp.Sqlite.Challenge.SingleTable {
return {
uid: "",
id: Number(data.schedule.schedule_id),
startTime: timestampToDate(Number(data.schedule.start_time) * 1000),
endTime: timestampToDate(Number(data.schedule.end_time) * 1000),
name: data.schedule.name,
single: data.single,
mp: data.mp,
updated: timestampToDate(new Date().getTime()),
};
}
/**
* @description 直接插入数据
* @since Beta v0.8.0
* @param {TGApp.Sqlite.Challenge.SingleTable} data - 挑战数据
* @param {string} [uid] - 用户UID
* @returns {string} - 插入 SQL 语句
*/
function getInsertSql(data: TGApp.Sqlite.Challenge.SingleTable, uid?: string): string {
const timeNow = timestampToDate(new Date().getTime());
return `
INSERT INTO HardChallenge(uid, id, startTime, endTime, name, single, mp, updated)
VALUES ('${uid ?? data.uid}', ${data.id}, '${data.startTime}', '${data.endTime}', '${data.name}',
'${JSON.stringify(data.single)}', '${JSON.stringify(data.mp)}', '${timeNow}') ON CONFLICT(uid,id) DO
UPDATE
SET startTime = '${data.startTime}',
endTime = '${data.endTime}',
name = '${data.name}',
single = '${JSON.stringify(data.single)}',
mp = '${JSON.stringify(data.mp)}',
updated = '${timeNow}'
`;
}
/**
* @description 获取所有数据的UID
* @since Beta v0.8.0
* @return {Promise<Array<string>} - 所有数据的UID
*/
async function getAllUid(): Promise<Array<string>> {
const db = await TGSqlite.getDB();
type resType = Array<{ uid: string }>;
const res = await db.select<resType>("SELECT DISTINCT uid FROM HardChallenge;");
return res.map((i) => i.uid);
}
/**
* @description 获取挑战数据
* @since Beta v0.8.0
* @param {string} [uid] - 游戏UID
* @return {Promise<Array<TGApp.Sqlite.Challenge.SingleTable>>} - 挑战数据
*/
async function getChallenge(uid?: string): Promise<Array<TGApp.Sqlite.Challenge.SingleTable>> {
const db = await TGSqlite.getDB();
let resR: Array<TGApp.Sqlite.Challenge.RawTable>;
if (uid === undefined) {
resR = await db.select<Array<TGApp.Sqlite.Challenge.RawTable>>(
"SELECT * FROM HardChallenge ORDER BY id DESC;",
);
} else {
resR = await db.select<Array<TGApp.Sqlite.Challenge.RawTable>>(
`SELECT *
FROM HardChallenge
WHERE uid = ?;`,
[uid],
);
}
const res: Array<TGApp.Sqlite.Challenge.SingleTable> = [];
for (const raw of resR) {
res.push({
uid: raw.uid,
id: raw.id,
startTime: raw.startTime,
endTime: raw.endTime,
name: raw.name,
single: JSON.parse(raw.single),
mp: JSON.parse(raw.mp),
updated: raw.updated,
});
}
return res;
}
/**
* @description 保存挑战数据
* @since Beta v0.8.0
* @param {string} uid - 游戏UID
* @param {TGApp.Game.Challenge.ChallengeItem} data - 挑战数据
* @return {Promise<void>}
*/
async function saveChallenge(uid: string, data: TGApp.Game.Challenge.ChallengeItem): Promise<void> {
const db = await TGSqlite.getDB();
await db.execute(getInsertSql(transUserChallenge(data), uid));
}
/**
* @description 删除指定UID的挑战数据
* @since Beta v0.8.0
* @param {string} uid - 游戏UID
* @return {Promise<void>}
*/
async function delChallenge(uid: string): Promise<void> {
const db = await TGSqlite.getDB();
await db.execute("DELETE FROM HardChallenge WHERE uid = ?;", [uid]);
}
/**
* @description 备份挑战数据
* @since Beta v0.8.0
* @param {string} dir - 备份目录
* @return {Promise<void>}
*/
async function backupChallenge(dir: string): Promise<void> {
if (!(await exists(dir))) {
await mkdir(dir, { recursive: true });
await TGLogger.Warn(`[TSUserChallenge][Backup] 未检测到备份目录,已创建`);
}
const data = await getChallenge();
await writeTextFile(`${dir}${path.sep()}challenge.json`, JSON.stringify(data));
}
/**
* @description 恢复挑战数据
* @since Beta v0.8.0
* @param {string} dir - 备份目录
* @return {Promise<boolean>}
*/
async function restoreChallenge(dir: string): Promise<boolean> {
const filePath = `${dir}${path.sep()}challenge.json`;
if (!(await exists(filePath))) return false;
try {
const data: Array<TGApp.Sqlite.Challenge.SingleTable> = JSON.parse(
await readTextFile(filePath),
);
const db = await TGSqlite.getDB();
for (const challenge of data) await db.execute(getInsertSql(challenge));
return true;
} catch (e) {
await TGLogger.Error(`[TSUserChallenge][Restore] 恢复挑战数据失败: `);
await TGLogger.Error(`${e}`);
return false;
}
}
const TSUserChallenge = {
getAllUid,
getChallenge,
saveChallenge,
delChallenge,
backupChallenge,
restoreChallenge,
};
export default TSUserChallenge;

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/modules/userCombat.ts
* @description Sqlite-幻想真境剧诗模块
* @since Beta v0.6.3
* @since Beta v0.8.0
*/
import { path } from "@tauri-apps/api";
@@ -10,13 +10,32 @@ import TGLogger from "@utils/TGLogger.js";
import { timestampToDate } from "@utils/toolFunc.js";
import TGSqlite from "../index.js";
import { transUserCombat } from "../utils/transUserCombat.js";
/**
* @description 将通过 api 获取到的数据转换为数据库中的数据
* @since Beta v0.8.0
* @param {TGApp.Game.Combat.Combat} data - 剧诗数据
* @returns {TGApp.Sqlite.Combat.SingleTable} 转换后端数据
*/
function transUserCombat(data: TGApp.Game.Combat.Combat): TGApp.Sqlite.Combat.SingleTable {
return {
uid: "",
detail: data.detail,
endTime: timestampToDate(Number(data.schedule.end_time) * 1000),
hasData: data.has_data,
hasDetailData: data.has_detail_data,
id: data.schedule.schedule_id,
startTime: timestampToDate(Number(data.schedule.start_time) * 1000),
stat: data.stat,
updated: timestampToDate(new Date().getTime()),
};
}
/**
* @description 直接插入数据
* @since Beta v0.6.3
* @param {string} uid 用户UID
* @param {TGApp.Sqlite.Combat.SingleTable} data 剧诗数据
* @param {string} [uid] 用户UID
* @returns {string}
*/
function getInsertSql(data: TGApp.Sqlite.Combat.SingleTable, uid?: string): string {
@@ -41,7 +60,7 @@ function getInsertSql(data: TGApp.Sqlite.Combat.SingleTable, uid?: string): stri
/**
* @description 获取所有有数据的UID
* @since Beta v0.6.3
* @returns {Promise<void>}
* @returns {Promise<Array<string>>}
*/
async function getAllUid(): Promise<Array<string>> {
const db = await TGSqlite.getDB();
@@ -52,20 +71,20 @@ async function getAllUid(): Promise<Array<string>> {
/**
* @description 获取剧诗数据
* @since Beta v0.6.3
* @param {string} uid - 游戏UID
* @since Beta v0.8.0
* @param {string} [uid] - 游戏UID
* @returns {Promise<TGApp.Sqlite.Abyss.TableRaw[]>}
*/
async function getCombat(uid?: string): Promise<TGApp.Sqlite.Combat.SingleTable[]> {
const db = await TGSqlite.getDB();
let resR: TGApp.Sqlite.Combat.RawTable[];
let resR: Array<TGApp.Sqlite.Combat.RawTable>;
if (uid === undefined) {
resR = await db.select<TGApp.Sqlite.Combat.RawTable[]>(
"SELECT * FROM RoleCombat order by id DESC;",
resR = await db.select<Array<TGApp.Sqlite.Combat.RawTable>>(
"SELECT * FROM RoleCombat ORDER BY id DESC;",
);
} else {
resR = await db.select<TGApp.Sqlite.Combat.RawTable[]>(
"SELECT * FROM RoleCombat WHERE uid = ? order by id DESC;",
resR = await db.select<Array<TGApp.Sqlite.Combat.RawTable>>(
"SELECT * FROM RoleCombat WHERE uid = ?;",
[uid],
);
}
@@ -118,7 +137,7 @@ async function delCombat(uid: string): Promise<void> {
async function backupCombat(dir: string): Promise<void> {
if (!(await exists(dir))) {
await mkdir(dir, { recursive: true });
await TGLogger.Warn(`未检测到备份目录,已创建`);
await TGLogger.Warn(`[TSUserCombat][Backup] 未检测到备份目录,已创建`);
}
const data = await getCombat();
await writeTextFile(`${dir}${path.sep()}combat.json`, JSON.stringify(data));
@@ -126,7 +145,7 @@ async function backupCombat(dir: string): Promise<void> {
/**
* @description 恢复剧诗数据
* @since Beta v0.6.3
* @since Beta v0.8.0
* @param {string} dir - 备份文件目录
* @returns {Promise<boolean>}
*/
@@ -134,12 +153,12 @@ async function restoreCombat(dir: string): Promise<boolean> {
const filePath = `${dir}${path.sep()}combat.json`;
if (!(await exists(filePath))) return false;
try {
const data: TGApp.Sqlite.Combat.SingleTable[] = JSON.parse(await readTextFile(filePath));
const data: Array<TGApp.Sqlite.Combat.SingleTable> = JSON.parse(await readTextFile(filePath));
const db = await TGSqlite.getDB();
for (const abyss of data) await db.execute(getInsertSql(abyss));
return true;
} catch (e) {
await TGLogger.Error(`恢复剧诗数据失败${filePath}`);
await TGLogger.Error(`[TSUserCombat][Restore] 恢复剧诗数据失败${filePath}`);
await TGLogger.Error(`${e}`);
return false;
}

View File

@@ -1,6 +1,6 @@
-- @file plugins/Sqlite/sql/createTable.sql
-- @brief sqlite数据库创建表语句
-- @since Beta v0.7.2
-- @since Beta v0.8.0
-- @brief 创建成就数据表
create table if not exists Achievements
@@ -47,27 +47,6 @@ create table if not exists GameAccount
primary key (uid, gameBiz, gameUid)
);
-- @brief 创建临时游戏账号数据表用于迁移数据
create table GameAccountTemp
(
uid text,
gameBiz text,
gameUid text,
isChosen boolean,
isOfficial boolean,
level integer,
nickname text,
region text,
regionName text,
updated text,
primary key (uid, gameBiz, gameUid)
);
--- @brief 迁移数据
insert into GameAccountTemp select * from GameAccount;
drop table GameAccount;
alter table GameAccountTemp rename to GameAccount;
-- @brief 创建深渊数据表
create table if not exists SpiralAbyss
(
@@ -107,6 +86,20 @@ create table if not exists RoleCombat
primary key (uid, id)
);
-- @brief 创建幽境危战数据表
create table if not exists HardChallenge
(
uid text,
id integer,
startTime text,
endTime text,
name text,
single text,
mp text,
updated text,
primary key (uid, id)
);
-- @brief 创建战绩数据表
create table if not exists UserRecord
(

View File

@@ -1,27 +0,0 @@
/**
* @file plugins/Sqlite/utils/transUserCombat.ts
* @description Sqlite 数据转换-幻想真境剧诗
* @since Beta v0.6.3
*/
import { timestampToDate } from "@utils/toolFunc.js";
/**
* @description 将通过 api 获取到的数据转换为数据库中的数据
* @since Beta v0.6.3
* @param {TGApp.Game.Combat.Combat} data - 剧诗数据
* @returns {TGApp.Sqlite.Combat.SingleTable} 转换后端数据
*/
export function transUserCombat(data: TGApp.Game.Combat.Combat): TGApp.Sqlite.Combat.SingleTable {
return {
uid: "",
detail: data.detail,
endTime: timestampToDate(Number(data.schedule.end_time) * 1000),
hasData: data.has_data,
hasDetailData: data.has_detail_data,
id: data.schedule.schedule_id,
startTime: timestampToDate(Number(data.schedule.start_time) * 1000),
stat: data.stat,
updated: timestampToDate(new Date().getTime()),
};
}

View File

@@ -166,12 +166,34 @@ async function hardChallengePopularity(
return resp;
}
/**
* @description 获取挑战数据
* @since Beta v0.8.0
* @param {TGApp.App.Account.Cookie} cookie Cookie
* @param {TGApp.Sqlite.Account.Game} user 用户
* @returns {Promise<TGApp.Game.Challenge.ChallengeRes | TGApp.BBS.Response.Base>}
*/
async function hardChallengeDetail(
cookie: TGApp.App.Account.Cookie,
user: TGApp.Sqlite.Account.Game,
): Promise<TGApp.Game.Challenge.ChallengeRes | TGApp.BBS.Response.Base> {
const ck = { account_id: cookie.account_id, cookie_token: cookie.cookie_token };
const params = { need_detail: true, role_id: user.gameUid, server: user.region };
const resp = await TGHttp<TGApp.Game.Challenge.ChallengeResp | TGApp.BBS.Response.Base>(
`${trgAbu}hard_challenge`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
const recordReq = {
index: index,
character: { list: characterList, detail: characterDetail },
roleCombat: roleCombat,
spiralAbyss: spiralAbyss,
challenge: {
detail: hardChallengeDetail,
pop: hardChallengePopularity,
},
};

View File

@@ -38,4 +38,197 @@ declare namespace TGApp.Game.Challenge {
image: string;
rarity: number;
};
/**
* @description 挑战数据返回响应(详细)
* @since Beta v0.8.0
* @interface ChallengeResp
* @extends TGApp.BBS.Response.BaseWithData<ChallengeRes>
*/
type ChallengeResp = TGApp.BBS.Response.BaseWithData<ChallengeRes>;
/**
* @description 挑战数据返回(详细)
* @since Beta v0.8.0
* @interface ChallengeRes
* @property {Array<ChallengeItem>} data - 挑战数据列表
* @property {boolean} is_unlock - 是否解锁挑战
* @property {ChallengeLink} link - 挑战链接信息
*/
type ChallengeRes = { data: Array<ChallengeItem>; is_unlock: boolean; link: ChallengeLink };
/**
* @description 挑战链接信息
* @since Beta v0.8.0
* @interface ChallengeLink
* @property {string} lineup_link - 队伍配置链接
* @property {string} play_link - 挑战链接
*/
type ChallengeLink = { lineup_link: string; play_link: string };
/**
* @description 挑战数据项
* @since Beta v0.8.0
* @interface ChallengeItem
* @property {ChallengeSchedule} schedule - 挑战周期信息
* @property {ChallengeSingle} single - 单次挑战数据
* @property {ChallengeMp} mp - 多人挑战数据
* @property {Array<unknown>} blings - 挑战相关的其他数据(如成就等)
*/
type ChallengeItem = {
schedule: ChallengeSchedule;
single: ChallengeSingle;
mp: ChallengeMp;
blings: Array<unknown>;
};
/**
* @description 多人挑战数据
* @since Beta v0.8.0
* @interface ChallengeMp
* @todo 待测试
*/
type ChallengeMp = ChallengeSingle;
/**
* @description 时间对象
* @since Beta v0.8.0
* @interface DateTime
* @property {number} year - 年份
* @property {number} month - 月份1-12
* @property {number} day - 日1-31
* @property {number} hour - 小时0-23
* @property {number} minute - 分钟0-59
* @property {number} second - 秒0-59
*/
type DateTime = {
year: number;
month: number;
day: number;
hour: number;
minute: number;
second: number;
};
/**
* @description 挑战周期信息
* @since Beta v0.8.0
* @interface ChallengeSchedule
* @property {string} schedule_id - 挑战周期 ID
* @property {string} start_time - 挑战开始时间(秒级时间戳)
* @property {string} end_time - 挑战结束时间(秒级时间戳)
* @property {DateTime} start_date_time - 挑战开始时间DateTime 对象)
* @property {DateTime} end_date_time - 挑战结束时间DateTime 对象)
* @property {boolean} is_valid - 是否有效
* @property {string} name - 挑战名称
*/
type ChallengeSchedule = {
schedule_id: string;
start_time: string;
end_time: string;
start_date_time: DateTime;
end_date_time: DateTime;
is_valid: boolean;
name: string;
};
/**
* @description 单次挑战数据
* @since Beta v0.8.0
* @interface ChallengeSingle
* @property {SingleBest} best - 最佳挑战数据
* @property {Array<SingleChallenge>} challenge - 挑战列表
* @property {boolean} has_data - 是否有数据
*/
type ChallengeSingle = { best: SingleBest; challenge: Array<SingleChallenge>; has_data: boolean };
/**
* @description 单次挑战最佳数据
* @since Beta v0.8.0
* @interface SingleBest
* @property {number} difficulty - 挑战难度
* @property {number} second - 挑战用时(秒)
* @property {string} icon - 挑战图标名称
*/
type SingleBest = { difficulty: number; second: number; icon: string };
/**
* @description 单次挑战数据项
* @since Beta v0.8.0
* @interface SingleChallenge
* @property {string} name - 怪物名称
* @property {number} seconds - 挑战用时(秒)
* @property {Array<SingleTeam>} teams - 挑战队伍列表
* @property {Array<SingleAvatar>} best_avatar - 最佳角色列表
* @property {SingleMonster} monster - 挑战怪物数据
*/
type SingleChallenge = {
name: string;
seconds: number;
teams: Array<SingleTeam>;
best_avatar: Array<SingleAvatar>;
monster: SingleMonster;
};
/**
* @description 单次挑战队伍数据
* @since Beta v0.8.0
* @interface SingleTeam
* @property {number} avatar_id - 角色 ID
* @property {string} name - 角色名称
* @property {string} element - 角色元素
* @property {string} image - 角色头像图片 URL
* @property {number} level - 角色等级
* @property {number} rarity - 角色稀有度
* @property {number} rank - 角色命座
*/
type SingleTeam = {
avatar_id: number;
name: string;
element: string;
image: string;
level: number;
rarity: number;
rank: number;
};
/**
* @description 单次挑战最佳角色数据
* @since Beta v0.8.0
* @interface SingleAvatar
* @property {number} avatar_id - 角色 ID
* @property {string} side_icon - 角色侧边图标 URL
* @property {string} dps - 角色 DPS 数据
* @property {string} type - 1-最强一击2-最高总伤害
*/
type SingleAvatar = { avatar_id: number; side_icon: string; dps: string; type: string };
/**
* @description 单次挑战怪物数据
* @since Beta v0.8.0
* @interface SingleMonster
* @property {string} name - 怪物名称
* @property {number} level - 怪物等级
* @property {string} icon - 怪物图标 URL
* @property {Array<string>} desc - 怪物描述列表
* @property {Array<MonsterTag>} tags - 怪物标签列表
* @property {string} monster_id - 怪物 ID
*/
type SingleMonster = {
name: string;
level: number;
icon: string;
desc: Array<string>;
tags: Array<MonsterTag>;
monster_id: string;
};
/**
* @description 怪物标签
* @since Beta v0.8.0
* @interface MonsterTag
* @property {string} type - 标签类型
* @property {string} desc - 标签描述
*/
type MonsterTag = { type: string; desc: string };
}

55
src/types/Sqlite/Challenge.d.ts vendored Normal file
View File

@@ -0,0 +1,55 @@
/**
* @file types/Sqlite/Challenge.d.ts
* @description 幽境危战类型定义文件
* @since Beta v0.8.0
*/
declare namespace TGApp.Sqlite.Challenge {
/**
* @description 数据库-幽境危战表(原始数据)
* @since Beta v0.8.0
* @interface RawTable
* @property {string} uid - 用户 UID
* @property {number} id - 挑战 ID
* @property {string} startTime - 开始时间
* @property {string} endTime - 结束时间
* @property {string} name - 挑战名称
* @property {string} single - 挑战单个数据JSON 字符串)
* @property {string} mp - 挑战多人数据JSON 字符串)
* @property {string} updated - 更新时间
*/
type RawTable = {
uid: string;
id: number;
startTime: string;
endTime: string;
name: string;
single: string; // JSON 字符串
mp: string; // JSON 字符串
updated: string;
};
/**
* @description 数据库-幽境危战表
* @since Beta v0.8.0
* @interface SingleTable
* @property {string} uid - 用户 UID
* @property {number} id - 挑战 ID
* @property {string} startTime - 开始时间
* @property {string} endTime - 结束时间
* @property {string} name - 挑战名称
* @property {TGApp.Game.Challenge.ChallengeSingle} single - 挑战单个数据
* @property {TGApp.Game.Challenge.ChallengeMp} mp - 挑战多人数据
* @property {string} updated - 更新时间
*/
type SingleTable = {
uid: string;
id: number;
startTime: string;
endTime: string;
name: string;
single: TGApp.Game.Challenge.ChallengeSingle;
mp: TGApp.Game.Challenge.ChallengeMp;
updated: string;
};
}

View File

@@ -1,13 +1,14 @@
/**
* @file utils/dataBS.ts
* @description 用户数据的备份、恢复、迁移
* @since Beta v0.6.7
* @since Beta v0.8.0
*/
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TSUserAbyss from "@Sqlm/userAbyss.js";
import TSUserAccount from "@Sqlm/userAccount.js";
import TSUserAchi from "@Sqlm/userAchi.js";
import TSUserChallenge from "@Sqlm/userChallenge.js";
import TSUserCombat from "@Sqlm/userCombat.js";
import TSUserGacha from "@Sqlm/userGacha.js";
import { exists, mkdir } from "@tauri-apps/plugin-fs";
@@ -16,7 +17,7 @@ import TGLogger from "./TGLogger.js";
/**
* @description 备份用户数据
* @since Beta v0.6.7
* @since Beta v0.8.0
* @param {string} dir 备份目录路径
* @returns {Promise<void>}
*/
@@ -33,13 +34,15 @@ export async function backUpUserData(dir: string): Promise<void> {
await TSUserAbyss.backupAbyss(dir);
await showLoading.update("正在备份真境剧诗数据");
await TSUserCombat.backupCombat(dir);
await showLoading.update("正在备份幽境危战数据");
await TSUserChallenge.backupChallenge(dir);
await showLoading.update("正在备份UIGF祈愿数据");
await TSUserGacha.backUpUigf(dir);
}
/**
* @description 恢复用户数据
* @since Beta v0.6.7
* @since Beta v0.8.0
* @param {string} dir 备份目录路径
* @returns {Promise<void>}
*/
@@ -73,6 +76,12 @@ export async function restoreUserData(dir: string): Promise<void> {
showSnackbar.error("真境剧诗数据恢复失败");
errNum++;
}
await showLoading.update("正在恢复幽境危战数据");
const restoreChallenge = await TSUserChallenge.restoreChallenge(dir);
if (!restoreChallenge) {
showSnackbar.error("幽境危战数据恢复失败");
errNum++;
}
await showLoading.update("正在恢复祈愿数据");
const restoreGacha = await TSUserGacha.restoreUigf(dir);
if (!restoreGacha) {