@@ -101,6 +113,7 @@
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { onMounted, ref, watch, computed } from "vue";
+import { useRouter } from "vue-router";
import showConfirm from "../../components/func/confirm.js";
import showSnackbar from "../../components/func/snackbar.js";
@@ -130,6 +143,7 @@ const user = computed
(() => userStore.account.value);
const localAbyss = ref([]);
const abyssRef = ref({});
const version = ref();
+const router = useRouter();
const uidList = ref();
const uidCur = ref();
@@ -154,6 +168,14 @@ watch(
async () => await loadAbyss(),
);
+async function toCombat(): Promise {
+ await router.push({ name: "真境剧诗" });
+}
+
+async function toWiki(): Promise {
+ await router.push({ name: "深渊数据库" });
+}
+
async function loadAbyss(): Promise {
localAbyss.value = [];
if (uidCur.value === undefined || uidCur.value === "") return;
@@ -330,10 +352,13 @@ async function deleteAbyss(): Promise {
}
span {
- color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
+
+ span :first-child {
+ color: var(--common-text-title);
+ }
}
.uat-right {
diff --git a/src/pages/User/Combat.vue b/src/pages/User/Combat.vue
new file mode 100644
index 00000000..c177e552
--- /dev/null
+++ b/src/pages/User/Combat.vue
@@ -0,0 +1,424 @@
+
+
+
+
+
+

+
幻想真境剧诗
+
+
+
+
+
+ 深境螺旋
+
+
+
+
+
+
+ mdi-share
+ 分享
+
+
+ mdi-refresh
+ 刷新
+
+
+ mdi-cloud-upload
+ 上传
+
+
+ mdi-delete
+ 删除
+
+
+
+
+
+
+ 第{{ item.id }}期
+
+
+
+
+
+
+ 第
+ {{ item.id }}
+ 期 UID
+ {{ uidCur }}
+ 更新于
+ {{ item.updated }}
+
+
真境剧诗 | Render by TeyvatGuide v{{ version }}
+
+
统计周期 {{ item.startTime }} ~ {{ item.endTime }}
+
+
使用角色
+
+
详情
+
+
+
+
+
+
+
+

+
暂无数据,请尝试刷新
+
+
+
+
+
diff --git a/src/plugins/Hutao/utils/combatUtil.ts b/src/plugins/Hutao/utils/combatUtil.ts
index ff470bee..9505c761 100644
--- a/src/plugins/Hutao/utils/combatUtil.ts
+++ b/src/plugins/Hutao/utils/combatUtil.ts
@@ -7,21 +7,17 @@
/**
* @description 将本地数据转为上传用的数据
* @since Beta v0.6.3
- * @param {number[]} avatars 角色
- * @param {number} schedule 期数
- * @param {number} uid UID
+ * @param {TGApp.Sqlite.Combat.SingleTable} data 数据
* @returns {TGApp.Plugins.Hutao.Combat.UploadData} 上传用的数据
*/
export function transCombatLocal(
- avatars: number[],
- schedule: number,
- uid: string,
+ data: TGApp.Sqlite.Combat.SingleTable,
): TGApp.Plugins.Hutao.Combat.UploadData {
return {
Version: 1,
- Uid: uid,
+ Uid: data.uid,
Identity: "TeyvatGuide",
- BackupAvatars: avatars,
- ScheduleId: schedule,
+ BackupAvatars: data.detail.backup_avatars.map((i) => i.avatar_id),
+ ScheduleId: data.id,
};
}
diff --git a/src/plugins/Sqlite/modules/userCombat.ts b/src/plugins/Sqlite/modules/userCombat.ts
new file mode 100644
index 00000000..bd2745e2
--- /dev/null
+++ b/src/plugins/Sqlite/modules/userCombat.ts
@@ -0,0 +1,159 @@
+/**
+ * @file plugins/Sqlite/modules/userCombat.ts
+ * @description Sqlite-幻想真境剧诗模块
+ * @since Beta v0.6.3
+ */
+
+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";
+import { transUserCombat } from "../utils/transUserCombat.js";
+
+/**
+ * @description 直接插入数据
+ * @since Beta v0.6.3
+ * @param {string} uid 用户UID
+ * @param {TGApp.Sqlite.Combat.SingleTable} data 剧诗数据
+ * @returns {string}
+ */
+function getInsertSql(data: TGApp.Sqlite.Combat.SingleTable, uid?: string): string {
+ const timeNow = timestampToDate(new Date().getTime());
+ const hasData = data.hasData ? 1 : 0;
+ const hasDetailData = data.hasDetailData ? 1 : 0;
+ return `
+ INSERT INTO RoleCombat(uid, id, startTime, endTime, hasData, hasDetailData, stat, detail, updated)
+ VALUES ('${uid ?? data.uid}', ${data.id}, '${data.startTime}', '${data.endTime}', ${hasData}, ${hasDetailData},
+ '${JSON.stringify(data.stat)}', '${JSON.stringify(data.detail)}', '${timeNow}')
+ ON CONFLICT(uid,id) DO UPDATE
+ SET startTime = '${data.startTime}',
+ endTime = '${data.endTime}',
+ hasData = ${hasData},
+ hasDetailData = ${hasDetailData},
+ stat = '${JSON.stringify(data.stat)}',
+ detail = '${JSON.stringify(data.detail)}',
+ updated = '${timeNow}'
+ `;
+}
+
+/**
+ * @description 获取所有有数据的UID
+ * @since Beta v0.6.3
+ * @returns {Promise}
+ */
+async function getAllUid(): Promise> {
+ const db = await TGSqlite.getDB();
+ type resType = Array<{ uid: string }>;
+ const res = await db.select("SELECT DISTINCT uid FROM RoleCombat;");
+ return res.map((i) => i.uid);
+}
+
+/**
+ * @description 获取剧诗数据
+ * @since Beta v0.6.3
+ * @param {string} uid - 游戏UID
+ * @returns {Promise}
+ */
+async function getCombat(uid?: string): Promise {
+ const db = await TGSqlite.getDB();
+ let resR: TGApp.Sqlite.Combat.RawTable[];
+ if (uid === undefined) {
+ resR = await db.select(
+ "SELECT * FROM RoleCombat order by id DESC;",
+ );
+ } else {
+ resR = await db.select(
+ "SELECT * FROM RoleCombat WHERE uid = ? order by id DESC;",
+ [uid],
+ );
+ }
+ const res: TGApp.Sqlite.Combat.SingleTable[] = [];
+ for (const raw of resR) {
+ res.push({
+ uid: raw.uid,
+ detail: JSON.parse(raw.detail),
+ endTime: raw.endTime,
+ hasData: raw.hasData === 1,
+ hasDetailData: raw.hasDetailData === 1,
+ id: raw.id,
+ startTime: raw.startTime,
+ stat: JSON.parse(raw.stat),
+ updated: raw.updated,
+ });
+ }
+ return res;
+}
+
+/**
+ * @description 保存剧诗数据
+ * @since Beta v0.6.3
+ * @param {string} uid - 游戏UID
+ * @param {TGApp.Game.Abyss.FullData} data - 深渊数据
+ * @returns {Promise}
+ */
+async function saveCombat(uid: string, data: TGApp.Game.Combat.Combat): Promise {
+ const db = await TGSqlite.getDB();
+ await db.execute(getInsertSql(transUserCombat(data), uid));
+}
+
+/**
+ * @description 删除指定UID存档的数据
+ * @since Beta v0.6.3
+ * @param {string} uid - 游戏UID
+ * @returns {Promise}
+ */
+async function delCombat(uid: string): Promise {
+ const db = await TGSqlite.getDB();
+ await db.execute("DELETE FROM RoleCombat WHERE uid = ?;", [uid]);
+}
+
+/**
+ * @description 备份剧诗数据
+ * @since Beta v0.6.3
+ * @param {string} dir - 备份目录
+ * @returns {Promise}
+ */
+async function backupCombat(dir: string): Promise {
+ if (!(await exists(dir))) {
+ await mkdir(dir, { recursive: true });
+ await TGLogger.Warn(`未检测到备份目录,已创建`);
+ }
+ const data = await getCombat();
+ await writeTextFile(`${dir}${path.sep()}combat.json`, JSON.stringify(data));
+}
+
+/**
+ * @description 恢复剧诗数据
+ * @since Beta v0.6.3
+ * @param {string} dir - 备份文件目录
+ * @returns {Promise}
+ */
+async function restoreCombat(dir: string): Promise {
+ 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 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(`${e}`);
+ return false;
+ }
+}
+
+const TSUserCombat = {
+ getAllUid,
+ getCombat,
+ saveCombat,
+ delCombat,
+ backupCombat,
+ restoreCombat,
+};
+
+export default TSUserCombat;
diff --git a/src/plugins/Sqlite/sql/createTable.sql b/src/plugins/Sqlite/sql/createTable.sql
index 565d8d4c..3cc3f5ba 100644
--- a/src/plugins/Sqlite/sql/createTable.sql
+++ b/src/plugins/Sqlite/sql/createTable.sql
@@ -1,6 +1,6 @@
-- @file plugins/Sqlite/sql/createTable.sql
-- @brief sqlite数据库创建表语句
--- @since Beta v0.6.1
+-- @since Beta v0.6.3
-- @brief 创建成就数据表
create table if not exists Achievements
@@ -71,6 +71,21 @@ create table if not exists SpiralAbyss
primary key (uid, id)
);
+-- @brief 创建幻想真境剧诗数据表
+create table if not exists RoleCombat
+(
+ uid text,
+ id integer,
+ startTime text,
+ endTime text,
+ hasData boolean,
+ hasDetailData boolean,
+ stat text,
+ detail text,
+ updated text,
+ primary key (uid, id)
+);
+
-- @brief 创建战绩数据表
create table if not exists UserRecord
(
diff --git a/src/plugins/Sqlite/utils/transUserCombat.ts b/src/plugins/Sqlite/utils/transUserCombat.ts
new file mode 100644
index 00000000..06403e44
--- /dev/null
+++ b/src/plugins/Sqlite/utils/transUserCombat.ts
@@ -0,0 +1,27 @@
+/**
+ * @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()),
+ };
+}
diff --git a/src/router/modules/user.ts b/src/router/modules/user.ts
index 0c6501bb..d110890f 100644
--- a/src/router/modules/user.ts
+++ b/src/router/modules/user.ts
@@ -1,7 +1,7 @@
/**
* @file router modules user.ts
* @description user 路由模块
- * @since Beta v0.3.3
+ * @since Beta v0.6.3
*/
const userRoutes = [
@@ -10,6 +10,11 @@ const userRoutes = [
name: "深渊记录",
component: async () => await import("../../pages/User/Abyss.vue"),
},
+ {
+ path: "/user/combat",
+ name: "真境剧诗",
+ component: async () => await import("../../pages/User/Combat.vue"),
+ },
{
path: "/user/characters",
name: "我的角色",
diff --git a/src/types/Game/Combat.d.ts b/src/types/Game/Combat.d.ts
index 338e0a32..6f768a0d 100644
--- a/src/types/Game/Combat.d.ts
+++ b/src/types/Game/Combat.d.ts
@@ -43,7 +43,7 @@ declare namespace TGApp.Game.Combat {
* @interface Avatar
* @since Beta v0.6.3
* @property {number} avatar_id 角色id
- * @property {number} avatar_type 角色武器类型 // todo
+ * @property {number} avatar_type 角色类型 // 0-自己角色,1-试用角色,2-助演角色
* @property {string} name 角色名称
* @property {string} element 角色元素 // todo Dendro
* @property {string} image 角色图像
diff --git a/src/types/Sqlite/Combat.d.ts b/src/types/Sqlite/Combat.d.ts
new file mode 100644
index 00000000..690ac02b
--- /dev/null
+++ b/src/types/Sqlite/Combat.d.ts
@@ -0,0 +1,61 @@
+/**
+ * @file types/Sqlite/Combat.d.ts
+ * @description 幻想真境剧诗类型定义文件
+ * @since Beta v0.6.3
+ */
+
+declare namespace TGApp.Sqlite.Combat {
+ /**
+ * @description 数据库-幻想真境剧诗表
+ * @since Beta v0.6.3
+ * @interface SingleTable
+ * @property {string} uid - 用户 UID
+ * @property {number} id - 剧诗 ID
+ * @property {string} startTime - 开始时间
+ * @property {string} endTime - 结束时间
+ * @property {boolean} hasData - 是否有数据
+ * @property {boolean} hasDetailData - 是否有详细数据
+ * @property {TGApp.Game.Combat.Stat} stat - 概况
+ * @property {TGApp.Game.Combat.Detail} detail - 详情
+ * @property {string} updated - 更新时间
+ * @return SingleTable
+ */
+ interface RawTable {
+ uid: string;
+ id: number;
+ startTime: string;
+ endTime: string;
+ hasData: 0 | 1;
+ hasDetailData: 0 | 1;
+ stat: string;
+ detail: string;
+ updated: string;
+ }
+
+ /**
+ * @description 数据库-幻想真境剧诗表
+ * @since Beta v0.6.3
+ * @interface SingleTable
+ * @property {string} uid - 用户 UID
+ * @property {number} id - 剧诗 ID
+ * @property {string} startTime - 开始时间
+ * @property {string} endTime - 结束时间
+ * @property {boolean} hasData - 是否有数据
+ * @property {boolean} hasDetailData - 是否有详细数据
+ * @property {TGApp.Game.Combat.Stat} stat - 概况
+ * @property {TGApp.Game.Combat.Detail} detail - 详情
+ * @property {string} updated - 更新时间
+ * @return SingleTable
+ */
+ interface SingleTable {
+ uid: string;
+ id: number;
+ startTime: string;
+ endTime: string;
+ hasData: boolean;
+ hasDetailData: boolean;
+ stat: TGApp.Game.Combat.Stat;
+ detail: TGApp.Game.Combat.Detail;
+ updated: string;
+ }
+}
diff --git a/src/utils/dataBS.ts b/src/utils/dataBS.ts
index 5af1b64f..ab3c06bd 100644
--- a/src/utils/dataBS.ts
+++ b/src/utils/dataBS.ts
@@ -1,7 +1,7 @@
/**
* @file utils/dataBS.ts
* @description 用户数据的备份、恢复、迁移
- * @since Beta v0.6.0
+ * @since Beta v0.6.3
*/
import { exists, mkdir } from "@tauri-apps/plugin-fs";
@@ -9,13 +9,14 @@ import showSnackbar from "../components/func/snackbar.js";
import TSUserAbyss from "../plugins/Sqlite/modules/userAbyss.js";
import TSUserAccount from "../plugins/Sqlite/modules/userAccount.js";
import TSUserAchi from "../plugins/Sqlite/modules/userAchi.js";
+import TSUserCombat from "../plugins/Sqlite/modules/userCombat.js";
import TSUserGacha from "../plugins/Sqlite/modules/userGacha.js";
import TGLogger from "./TGLogger.js";
/**
* @description 备份用户数据
- * @since Beta v0.6.0
+ * @since Beta v0.6.3
* @param {string} dir 备份目录路径
* @returns {Promise}
*/
@@ -27,12 +28,13 @@ export async function backUpUserData(dir: string): Promise {
await TSUserAchi.backupUiaf(dir);
await TSUserAccount.account.backup(dir);
await TSUserAbyss.backupAbyss(dir);
+ await TSUserCombat.backupCombat(dir);
await TSUserGacha.backUpUigf(dir);
}
/**
* @description 恢复用户数据
- * @since Beta v0.6.0
+ * @since Beta v0.6.3
* @param {string} dir 备份目录路径
* @returns {Promise}
*/
@@ -57,6 +59,11 @@ export async function restoreUserData(dir: string): Promise {
showSnackbar({ text: "深渊数据恢复失败", color: "error" });
errNum++;
}
+ const restoreCombat = await TSUserCombat.restoreCombat(dir);
+ if (!restoreCombat) {
+ showSnackbar({ text: "真境剧诗数据恢复失败", color: "error" });
+ errNum++;
+ }
const restoreGacha = await TSUserGacha.restoreUigf(dir);
if (!restoreGacha) {
showSnackbar({ text: "祈愿数据恢复失败", color: "error" });
diff --git a/src/web/request/TGRequest.ts b/src/web/request/TGRequest.ts
index 62b87d58..4e33310b 100644
--- a/src/web/request/TGRequest.ts
+++ b/src/web/request/TGRequest.ts
@@ -1,7 +1,7 @@
/**
* @file web/request/TGRequest.ts
* @description 应用用到的请求函数
- * @since Beta v0.6.2
+ * @since Beta v0.6.3
*/
import { genAuthkey, genAuthkey2 } from "./genAuthkey.js";
@@ -17,6 +17,7 @@ import { getGachaLog } from "./getGachaLog.js";
import { getGameAccountsByCookie, getGameAccountsBySToken } from "./getGameAccounts.js";
import { getGameRecord } from "./getGameRecord.js";
import { getLTokenBySToken } from "./getLToken.js";
+import { getRoleCombat } from "./getRoleCombat.js";
import { getStokenByGameToken, getTokenBySToken } from "./getStoken.js";
import { getUserCollect } from "./getUserCollect.js";
import { getUserInfoByCookie } from "./getUserInfo.js";
@@ -45,6 +46,7 @@ const TGRequest = {
getAvatarIndex,
getAvatarList,
getAvatarDetail,
+ getCombat: getRoleCombat,
},
bySToken: {
update: getTokenBySToken,