mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-12 09:18:14 +08:00
@@ -6,7 +6,6 @@
|
||||
|
||||
import type { SchemaType } from "ajv/lib/types/index.js";
|
||||
|
||||
// 应用数据
|
||||
import achievements from "./app/achievements.json";
|
||||
import achievementSeries from "./app/achievementSeries.json";
|
||||
import calendar from "./app/calendar.json";
|
||||
@@ -14,17 +13,17 @@ import character from "./app/character.json";
|
||||
import gacha from "./app/gacha.json";
|
||||
import GCG from "./app/GCG.json";
|
||||
import nameCards from "./app/namecard.json";
|
||||
import uigfSchema from "./app/uigf-schema.json";
|
||||
import weapon from "./app/weapon.json";
|
||||
// 存档数据
|
||||
import arcBirCalendar from "./archive/birth_calendar.json";
|
||||
import arcBirDraw from "./archive/birth_draw.json";
|
||||
import arcBirRole from "./archive/birth_role.json";
|
||||
// Wiki 数据
|
||||
import schemaUiaf from "./schema/uiaf-schema.json";
|
||||
import schemaUigf from "./schema/uigf-schema.json";
|
||||
import wikiCharacter from "./WIKI/character.json";
|
||||
import wikiMaterial from "./WIKI/material.json";
|
||||
import wikiWeapon from "./WIKI/weapon.json";
|
||||
|
||||
// App
|
||||
export const AppAchievementsData: TGApp.App.Achievement.Item[] = achievements;
|
||||
export const AppAchievementSeriesData: TGApp.App.Achievement.Series[] = achievementSeries;
|
||||
export const AppCalendarData: TGApp.App.Calendar.Item[] = calendar;
|
||||
@@ -33,10 +32,14 @@ export const AppGachaData: TGApp.App.Gacha.PoolItem[] = gacha;
|
||||
export const AppGCGData: TGApp.App.GCG.WikiBriefInfo[] = GCG;
|
||||
export const AppNameCardsData: TGApp.App.NameCard.Item[] = nameCards;
|
||||
export const AppWeaponData: TGApp.App.Weapon.WikiBriefInfo[] = weapon;
|
||||
export const AppUigfSchema: SchemaType = uigfSchema;
|
||||
// Schema
|
||||
export const UiafSchema: SchemaType = schemaUiaf;
|
||||
export const UigfSchema: SchemaType = schemaUigf;
|
||||
// Archive
|
||||
export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar;
|
||||
export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw;
|
||||
export const ArcBirRole: TGApp.Archive.Birth.RoleItem[] = arcBirRole;
|
||||
// Wiki
|
||||
export const WikiCharacterData: TGApp.App.Character.WikiItem[] = wikiCharacter;
|
||||
export const WikiWeaponData: TGApp.App.Weapon.WikiItem[] = wikiWeapon;
|
||||
export const WikiMaterialData: TGApp.App.Material.WikiItem[] = wikiMaterial;
|
||||
|
||||
59
src/data/schema/uiaf-schema.json
Normal file
59
src/data/schema/uiaf-schema.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"description": "Export application name"
|
||||
},
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"description": "Export application version"
|
||||
},
|
||||
"uiaf_version": {
|
||||
"type": "string",
|
||||
"description": "UIAF version applied; Used to prevent application not working when UIGF have breaking update",
|
||||
"pattern": "v\\d+.\\d+"
|
||||
},
|
||||
"export_timestamp": {
|
||||
"type": "number",
|
||||
"description": "Export time in UNIX timestamp"
|
||||
}
|
||||
},
|
||||
"required": ["export_app", "uiaf_version"],
|
||||
"description": "Include basic information defined by export application"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"description": "Achievement ID"
|
||||
},
|
||||
"current": {
|
||||
"type": "number",
|
||||
"description": "Process"
|
||||
},
|
||||
"status": {
|
||||
"type": "number",
|
||||
"description": "Finished status",
|
||||
"enum": [0, 1, 2, 3],
|
||||
"enumDesc": "ACHIEVEMENT_INVALID = 0; ACHIEVEMENT_UNFINISHED = 1; ACHIEVEMENT_FINISHED = 2;ACHIEVEMENT_POINT_TAKEN = 3;"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "number",
|
||||
"description": "Finished time"
|
||||
}
|
||||
},
|
||||
"required": ["id", "current", "status", "timestamp"],
|
||||
"description": "To represent an achievement"
|
||||
},
|
||||
"description": "Include finished or unfinished achievements"
|
||||
}
|
||||
},
|
||||
"required": ["info", "list"]
|
||||
}
|
||||
@@ -126,11 +126,16 @@ import ToAchiInfo from "../../components/overlay/to-achiInfo.vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import ToNamecard from "../../components/overlay/to-namecard.vue";
|
||||
import { AppAchievementSeriesData, AppNameCardsData } from "../../data";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import { useAchievementsStore } from "../../store/modules/achievements";
|
||||
import TGLogger from "../../utils/TGLogger";
|
||||
import { getNowStr } from "../../utils/toolFunc";
|
||||
import { getUiafHeader, readUiafData, verifyUiafData } from "../../utils/UIAF";
|
||||
import {
|
||||
getUiafHeader,
|
||||
readUiafData,
|
||||
verifyUiafData,
|
||||
verifyUiafDataClipboard,
|
||||
} from "../../utils/UIAF";
|
||||
|
||||
// Store
|
||||
const achievementsStore = useAchievementsStore();
|
||||
@@ -185,7 +190,7 @@ async function switchHideFin() {
|
||||
|
||||
// 刷新概况
|
||||
async function flushOverview(): Promise<void> {
|
||||
const { total, fin } = await getAchiOverview();
|
||||
const { total, fin } = await TSUserAchi.getOverview();
|
||||
achievementsStore.flushData(total, fin);
|
||||
title.value = achievementsStore.title;
|
||||
}
|
||||
@@ -197,8 +202,8 @@ onMounted(async () => {
|
||||
loadingTitle.value = "正在获取成就系列数据";
|
||||
await flushOverview();
|
||||
await TGLogger.Info(`[Achievements][onMounted] ${title.value}`);
|
||||
allSeriesData.value = await getSeriesData();
|
||||
achievementsStore.lastVersion = await TGSqlite.getLatestAchievementVersion();
|
||||
allSeriesData.value = await TSUserAchi.getSeries();
|
||||
achievementsStore.lastVersion = await TSUserAchi.getLatestAchiVersion();
|
||||
loadingTitle.value = "正在获取成就数据";
|
||||
selectedAchievement.value = await getAchiData("all");
|
||||
loading.value = false;
|
||||
@@ -229,7 +234,7 @@ async function selectSeries(index: number): Promise<void> {
|
||||
selectedSeries.value = index;
|
||||
selectedAchievement.value = await getAchiData("series", index.toString());
|
||||
loadingTitle.value = "正在查找对应的成就名片";
|
||||
curCardName.value = await getNameCardName(index);
|
||||
curCardName.value = await TSUserAchi.getSeriesNameCard(index);
|
||||
if (curCardName.value !== "") {
|
||||
curCard.value = AppNameCardsData.find((item) => item.name === curCardName.value);
|
||||
}
|
||||
@@ -360,7 +365,7 @@ async function importJson(): Promise<void> {
|
||||
loadingTitle.value = "正在解析数据";
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在合并成就数据";
|
||||
await TGSqlite.mergeUIAF(remoteRaw.list);
|
||||
await TSUserAchi.mergeUIAF(remoteRaw.list);
|
||||
loadingTitle.value = "即将刷新页面";
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
@@ -382,7 +387,7 @@ async function exportJson(): Promise<void> {
|
||||
// 获取本地数据
|
||||
const UiafData = {
|
||||
info: await getUiafHeader(),
|
||||
list: await TGSqlite.getUIAF(),
|
||||
list: await TSUserAchi.getUIAF(),
|
||||
};
|
||||
const fileName = `UIAF_${UiafData.info.export_app}_${UiafData.info.export_app_version}_${UiafData.info.export_timestamp}`;
|
||||
const isSave = await dialog.save({
|
||||
@@ -430,33 +435,22 @@ async function handleImportOuter(app: string): Promise<void> {
|
||||
}
|
||||
// 读取 剪贴板
|
||||
const clipboard = await window.navigator.clipboard.readText();
|
||||
let data: TGApp.Plugins.UIAF.Achievement[];
|
||||
// 里面是完整的 uiaf 数据
|
||||
try {
|
||||
data = JSON.parse(clipboard).list;
|
||||
const check = await verifyUiafDataClipboard();
|
||||
if (!check) return;
|
||||
const data: TGApp.Plugins.UIAF.Data = JSON.parse(clipboard);
|
||||
loadingTitle.value = "正在导入数据";
|
||||
loading.value = true;
|
||||
await TGSqlite.mergeUIAF(data);
|
||||
await TSUserAchi.mergeUIAF(data);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
color: "success",
|
||||
text: "导入成功,即将刷新页面",
|
||||
});
|
||||
await TGLogger.Info("[Achievements][handleImportOuter] 导入成功");
|
||||
} catch (e) {
|
||||
if (e instanceof Error)
|
||||
await TGLogger.Error(`[Achievements][handleImportOuter] 导入失败 ${e.name}: ${e.message}`);
|
||||
else console.error(e);
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "读取 UIAF 数据失败,请检查文件是否符合规范",
|
||||
});
|
||||
} finally {
|
||||
setTimeout(async () => {
|
||||
await router.push("/achievements");
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
// 改变成就状态
|
||||
async function setAchi(
|
||||
@@ -474,10 +468,12 @@ async function setAchi(
|
||||
}
|
||||
renderSelect.value[renderSelect.value.findIndex((item) => item.id === achievement.id)] =
|
||||
newAchievement;
|
||||
await setAchiDB(newAchievement);
|
||||
await TSUserAchi.updateAchievement(newAchievement);
|
||||
await flushOverview();
|
||||
allSeriesData.value[allSeriesData.value.findIndex((item) => item.id === newAchievement.series)] =
|
||||
(await getSeriesData(newAchievement.series))[0];
|
||||
const seriesIndex = allSeriesData.value.findIndex((item) => item.id === newAchievement.series);
|
||||
if (seriesIndex === -1) return;
|
||||
const seriesGet = await TSUserAchi.getSeries(newAchievement.series);
|
||||
allSeriesData.value[seriesIndex] = seriesGet[0];
|
||||
showSnackbar({
|
||||
text: `已将成就 ${newAchievement.name}[${newAchievement.id}] 标记为 ${
|
||||
target ? "已完成" : "未完成"
|
||||
@@ -490,46 +486,14 @@ async function setAchi(
|
||||
);
|
||||
}
|
||||
|
||||
/* 以下为数据库操作 */
|
||||
// 获取成就概况
|
||||
async function getAchiOverview(): Promise<{
|
||||
total: number;
|
||||
fin: number;
|
||||
}> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
|
||||
const res: Array<{ total: number; fin: number }> = await db.select(sql);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
// 获取成就系列
|
||||
async function getSeriesData(series?: number): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let sql = "SELECT * FROM AchievementSeries ORDER BY `order`;";
|
||||
if (series) {
|
||||
sql = `SELECT *
|
||||
FROM AchievementSeries
|
||||
WHERE id = ${series}
|
||||
ORDER BY \`order\`;`;
|
||||
}
|
||||
return await db.select(sql);
|
||||
}
|
||||
|
||||
// 获取成就(某个系列)
|
||||
async function getAchiData(
|
||||
type: "all" | "series" | "search",
|
||||
value?: string,
|
||||
): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let sql = "";
|
||||
if (type === "all" || (type == "series" && value === undefined)) {
|
||||
sql = "SELECT * FROM Achievements ORDER BY isCompleted, `order`;";
|
||||
} else if (type === "series") {
|
||||
sql = `SELECT *
|
||||
FROM Achievements
|
||||
WHERE series = ${value}
|
||||
ORDER BY isCompleted, \`order\`;`;
|
||||
} else if (type === "search") {
|
||||
if (type !== "search") {
|
||||
return TSUserAchi.getAchievements(value);
|
||||
}
|
||||
if (value === undefined) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
@@ -537,41 +501,7 @@ async function getAchiData(
|
||||
});
|
||||
return [];
|
||||
}
|
||||
if (value.startsWith("v")) {
|
||||
const version = value.replace("v", "");
|
||||
sql = `SELECT *
|
||||
FROM Achievements
|
||||
WHERE version LIKE '%${version}%'
|
||||
ORDER BY isCompleted, \`order\`;`;
|
||||
} else {
|
||||
sql = `SELECT *
|
||||
FROM Achievements
|
||||
WHERE name LIKE '%${value}%'
|
||||
OR description LIKE '%${value}%'
|
||||
ORDER BY isCompleted, \`order\`;`;
|
||||
}
|
||||
}
|
||||
return await db.select(sql);
|
||||
}
|
||||
|
||||
// 获取成就名片
|
||||
async function getNameCardName(series: number): Promise<string> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const sql = `SELECT nameCard
|
||||
FROM AchievementSeries
|
||||
WHERE id = ${series};`;
|
||||
const res: Array<{ nameCard: string }> = await db.select(sql);
|
||||
return res[0].nameCard;
|
||||
}
|
||||
|
||||
// 更新成就数据
|
||||
async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const sql = `UPDATE Achievements
|
||||
SET isCompleted = ${achievement.isCompleted},
|
||||
completedTime = '${achievement.completedTime}'
|
||||
WHERE id = ${achievement.id};`;
|
||||
await db.execute(sql);
|
||||
return TSUserAchi.searchAchievements(value);
|
||||
}
|
||||
</script>
|
||||
<!-- 顶部栏跟 wrap 大概布局 -->
|
||||
|
||||
@@ -98,6 +98,7 @@ import showConfirm from "../../components/func/confirm";
|
||||
import showSnackbar from "../../components/func/snackbar";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import { useAchievementsStore } from "../../store/modules/achievements";
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import { useHomeStore } from "../../store/modules/home";
|
||||
@@ -237,7 +238,7 @@ async function confirmUpdate(title?: string): Promise<void> {
|
||||
loadingTitle.value = "正在更新数据库...";
|
||||
loading.value = true;
|
||||
await TGSqlite.update();
|
||||
achievementsStore.lastVersion = await TGSqlite.getLatestAchievementVersion();
|
||||
achievementsStore.lastVersion = await TSUserAchi.getLatestAchiVersion();
|
||||
appStore.buildTime = getBuildTime();
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
/**
|
||||
* @file plugins/Sqlite/index.ts
|
||||
* @description Sqlite 数据库操作类
|
||||
* @since Beta v0.4.5
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app } from "@tauri-apps/api";
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
|
||||
import { getUiafStatus } from "../../utils/UIAF";
|
||||
|
||||
import initDataSql from "./sql/initData";
|
||||
import {
|
||||
importAbyssData,
|
||||
@@ -18,7 +16,6 @@ import {
|
||||
insertRecordData,
|
||||
insertRoleData,
|
||||
} from "./sql/insertData";
|
||||
import { importUIAFData } from "./sql/updateData";
|
||||
|
||||
class Sqlite {
|
||||
/**
|
||||
@@ -201,57 +198,6 @@ class Sqlite {
|
||||
await this.initDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取最新成就版本
|
||||
* @since Beta v0.3.3
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public async getLatestAchievementVersion(): Promise<string> {
|
||||
const db = await this.getDB();
|
||||
const sql = "SELECT version FROM Achievements ORDER BY version DESC LIMIT 1;";
|
||||
const res: Array<{ version: string }> = await db.select(sql);
|
||||
return res[0].version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并 UIAF 数据
|
||||
* @since Beta v0.3.3
|
||||
* @param {TGApp.Plugins.UIAF.Achievement[]} achievements UIAF 数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async mergeUIAF(achievements: TGApp.Plugins.UIAF.Achievement[]): Promise<void> {
|
||||
const db = await this.getDB();
|
||||
const sql = importUIAFData(achievements);
|
||||
for (const item of sql) {
|
||||
await db.execute(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取 UIAF 数据
|
||||
* @since Beta v0.3.3
|
||||
* @returns {Promise<TGApp.Plugins.UIAF.Achievement[]>}
|
||||
*/
|
||||
public async getUIAF(): Promise<TGApp.Plugins.UIAF.Achievement[]> {
|
||||
const db = await this.getDB();
|
||||
const sql = "SELECT * FROM Achievements WHERE isCompleted = 1 OR progress > 0";
|
||||
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
|
||||
|
||||
const achievements: TGApp.Plugins.UIAF.Achievement[] = [];
|
||||
for (const item of res) {
|
||||
const completed = item.isCompleted === 1;
|
||||
const status = getUiafStatus(completed, item.progress);
|
||||
achievements.push({
|
||||
id: item.id,
|
||||
status,
|
||||
timestamp:
|
||||
completed && item.completedTime ? new Date(item.completedTime).getTime() / 1000 : 0,
|
||||
current: item.progress,
|
||||
});
|
||||
}
|
||||
return achievements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 保存深渊数据
|
||||
* @since Beta v0.3.3
|
||||
|
||||
194
src/plugins/Sqlite/modules/userAchi.ts
Normal file
194
src/plugins/Sqlite/modules/userAchi.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* @file plugins/Sqlite/modules/userAchi.ts
|
||||
* @description 用户成就模块
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { getUiafStatus } from "../../../utils/UIAF.js";
|
||||
import TGSqlite from "../index";
|
||||
import { importUIAFData } from "../sql/updateData";
|
||||
|
||||
/**
|
||||
* @description 获取成就概况
|
||||
* @since Beta v0.4.7
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.Overview}> 成就概况
|
||||
*/
|
||||
async function getOverview(): Promise<TGApp.Sqlite.Achievement.Overview> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const res = await db.select<TGApp.Sqlite.Achievement.Overview>(
|
||||
"SELECT SUM(totalCount) as total,SUM(finCount) AS fin From AchievementSeries",
|
||||
);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取最新成就版本
|
||||
* @since Beta v0.4.7
|
||||
* @returns {Promise<string>} 最新成就版本
|
||||
*/
|
||||
async function getLatestAchiVersion(): Promise<string> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = { version: string };
|
||||
const res = await db.select<resType>(
|
||||
"SELECT version FROM Achievements ORDER BY version DESC LIMIT 1;",
|
||||
);
|
||||
return res[0].version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就系列数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {number|undefined} id 成就系列ID
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.SeriesTable[]>} 成就系列数据
|
||||
*/
|
||||
async function getSeries(id?: number): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let res: TGApp.Sqlite.Achievement.SeriesTable[] = [];
|
||||
if (id === undefined) {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SeriesTable>(
|
||||
"SELECT * FROM AchievementSeries ORDER BY `order`;",
|
||||
);
|
||||
} else {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SeriesTable>(
|
||||
"SELECT * FROM AchievementSeries WHERE id = ?;",
|
||||
[id],
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {number|undefined} id 成就系列ID
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>} 成就数据
|
||||
*/
|
||||
async function getAchievements(id?: string): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let res: TGApp.Sqlite.Achievement.SingleTable[] = [];
|
||||
if (id === undefined) {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements ORDER BY isCompleted,`order`;",
|
||||
);
|
||||
} else {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements WHERE series = ? ORDER BY `order`;",
|
||||
[id],
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就名片
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} id 成就系列ID
|
||||
* @returns {Promise<string>} 成就名片
|
||||
*/
|
||||
async function getSeriesNameCard(id: string): Promise<string> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = { nameCard: string };
|
||||
const res = await db.select<resType>("SELECT nameCard FROM AchievementSeries WHERE id = ?;", [
|
||||
id,
|
||||
]);
|
||||
return res[0].nameCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查找成就数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} keyword 关键词
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>} 成就数据
|
||||
*/
|
||||
async function searchAchievements(
|
||||
keyword: string,
|
||||
): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
|
||||
if (keyword === "") return await getAchievements();
|
||||
const db = await TGSqlite.getDB();
|
||||
const versionReg = /^v\d+(\.\d+)?$/;
|
||||
if (versionReg.test(keyword)) {
|
||||
return await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements WHERE version LIKE ? ORDER BY isCompleted,`order`;",
|
||||
[keyword],
|
||||
);
|
||||
}
|
||||
return await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements WHERE name LIKE ? OR description LIKE ? ORDER BY isCompleted,`order`;",
|
||||
[`%${keyword}%`, `%${keyword}%`],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新成就数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Sqlite.Achievement.SingleTable} data UIAF数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateAchievement(data: TGApp.Sqlite.Achievement.SingleTable): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
await db.execute("UPDATE Achievements SET isCompleted = ?, completedTime = ? WHERE id = ?;", [
|
||||
data.isCompleted,
|
||||
data.completedTime.toString(),
|
||||
data.id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将数据库数据转换为UIAF数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Sqlite.Achievement.SingleTable} data 数据库数据
|
||||
* @returns {TGApp.Plugins.UIAF.Achievement} UIAF数据
|
||||
*/
|
||||
function transDb2Uiaf(data: TGApp.Sqlite.Achievement.SingleTable): TGApp.Plugins.UIAF.Achievement {
|
||||
const isCompleted = data.isCompleted === 1;
|
||||
const status = getUiafStatus(isCompleted, data.progress);
|
||||
return {
|
||||
id: data.id,
|
||||
timestamp: data.timestamp,
|
||||
current: data.progress,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取UIAF数据
|
||||
* @since Beta v0.4.7
|
||||
* @returns {Promise<TGApp.Plugins.UIAF.Achievement[]>}
|
||||
*/
|
||||
async function getUIAF(): Promise<TGApp.Plugins.UIAF.Achievement[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const data = await db.select<TGApp.Sqlite.Achievement.SingleTable>("SELECT * FROM Achievements;");
|
||||
const res: TGApp.Plugins.UIAF.Achievement[] = [];
|
||||
for (const item: TGApp.Sqlite.Achievement.SingleTable of data) {
|
||||
res.push(transDb2Uiaf(item));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并UIAF数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Plugins.UIAF.Achievement[]} data UIAF数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function mergeUIAF(data: TGApp.Plugins.UIAF.Achievement[]): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
for (const item of data) {
|
||||
const sql = importUIAFData(item);
|
||||
await db.execute(sql);
|
||||
}
|
||||
}
|
||||
|
||||
const TSUserAchi = {
|
||||
getOverview,
|
||||
getLatestAchiVersion,
|
||||
getSeries,
|
||||
getSeriesNameCard,
|
||||
getAchievements,
|
||||
searchAchievements,
|
||||
updateAchievement,
|
||||
getUIAF,
|
||||
mergeUIAF,
|
||||
};
|
||||
|
||||
export default TSUserAchi;
|
||||
@@ -130,7 +130,6 @@ async function mergeUIGF(uid: string, data: TGApp.Plugins.UIGF.GachaItem[]): Pro
|
||||
const db = await TGSqlite.getDB();
|
||||
for (const gacha of data) {
|
||||
const trans = transGacha(gacha);
|
||||
console.log(trans);
|
||||
const sql = importUIGFData(uid, trans);
|
||||
await db.execute(sql);
|
||||
}
|
||||
|
||||
@@ -7,19 +7,16 @@
|
||||
import minifySql from "../../../utils/minifySql";
|
||||
|
||||
/**
|
||||
* @description 导入UIAF数据
|
||||
* @since Alpha v0.2.3
|
||||
* @param {TGApp.Plugins.UIAF.Achievement[]} data
|
||||
* @returns {string[]} sql
|
||||
* @description 导入UIAF数据-单项
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Plugins.UIAF.Achievement} data
|
||||
* @returns {string} sql
|
||||
*/
|
||||
export function importUIAFData(data: TGApp.Plugins.UIAF.Achievement[]): string[] {
|
||||
const sqlRes: string[] = [];
|
||||
data.map((achievement) => {
|
||||
export function importUIAFData(data: TGApp.Plugins.UIAF.Achievement): string[] {
|
||||
let sql;
|
||||
// 获取完成状态
|
||||
const isCompleted = achievement.status === 2 || achievement.status === 3;
|
||||
const isCompleted = data.status === 2 || data.status === 3;
|
||||
if (isCompleted) {
|
||||
const completedTime = new Date(achievement.timestamp * 1000)
|
||||
const completedTime = new Date(data.timestamp * 1000)
|
||||
.toISOString()
|
||||
.replace("T", " ")
|
||||
.slice(0, 19);
|
||||
@@ -27,10 +24,11 @@ export function importUIAFData(data: TGApp.Plugins.UIAF.Achievement[]): string[]
|
||||
UPDATE Achievements
|
||||
SET isCompleted = 1,
|
||||
completedTime = '${completedTime}',
|
||||
progress = ${achievement.current},
|
||||
progress = ${data.current},
|
||||
updated = datetime('now', 'localtime')
|
||||
WHERE id = ${achievement.id}
|
||||
AND (isCompleted = 0 OR completedTime != '${completedTime}' OR progress != ${achievement.current});
|
||||
WHERE id = ${data.id}
|
||||
AND (isCompleted = 0 OR completedTime != '${completedTime}'
|
||||
OR progress != ${data.current});
|
||||
`;
|
||||
} else {
|
||||
sql = `
|
||||
@@ -41,9 +39,7 @@ export function importUIAFData(data: TGApp.Plugins.UIAF.Achievement[]): string[]
|
||||
AND progress != ${achievement.current};
|
||||
`;
|
||||
}
|
||||
return sqlRes.push(minifySql(sql));
|
||||
});
|
||||
return sqlRes;
|
||||
return minifySql(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
6
src/types/Plugins/UIAF.d.ts
vendored
6
src/types/Plugins/UIAF.d.ts
vendored
@@ -14,7 +14,7 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
* @property {Achievement[]} list UIAF 成就列表
|
||||
* @return Data
|
||||
*/
|
||||
export interface Data {
|
||||
interface Data {
|
||||
info: Export;
|
||||
list: Achievement[];
|
||||
}
|
||||
@@ -29,7 +29,7 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
* @property {string} uiaf_version UIAF 版本
|
||||
* @return Export
|
||||
*/
|
||||
export interface Export {
|
||||
interface Export {
|
||||
export_app: string;
|
||||
export_timestamp: number;
|
||||
export_app_version: string;
|
||||
@@ -46,7 +46,7 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
* @property {number} status 成就状态,0 为未完成,1 为已完成
|
||||
* @return Achievement
|
||||
*/
|
||||
export interface Achievement {
|
||||
interface Achievement {
|
||||
id: number;
|
||||
timestamp: number;
|
||||
current: number;
|
||||
|
||||
15
src/types/Sqlite/Achievement.d.ts
vendored
15
src/types/Sqlite/Achievement.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file types/Sqlite/Achievement.d.ts
|
||||
* @description 数据库成就相关类型定义文件
|
||||
* @since Alpha v0.2.0
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
declare namespace TGApp.Sqlite.Achievement {
|
||||
@@ -60,4 +60,17 @@ declare namespace TGApp.Sqlite.Achievement {
|
||||
nameCard: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 成就概况
|
||||
* @since Beta v0.4.7
|
||||
* @interface Overview
|
||||
* @property {number} total - 总成就数
|
||||
* @property {number} fin - 已完成成就数
|
||||
* @returns Overview
|
||||
*/
|
||||
interface Overview {
|
||||
total: number;
|
||||
fin: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
/**
|
||||
* @file utils/UIAF.ts
|
||||
* @description UIAF工具类
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app, fs } from "@tauri-apps/api";
|
||||
import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { UiafSchema } from "../data/index.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
|
||||
/**
|
||||
* @description 根据 completed 跟 progress 获取 status
|
||||
@@ -40,16 +47,64 @@ export async function getUiafHeader(): Promise<TGApp.Plugins.UIAF.Export> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测是否存在 UIAF 数据
|
||||
* @description 粗略检测,不保证数据完整性
|
||||
* @since Alpha v0.2.3
|
||||
* @description 检测是否存在 UIAF 数据,采用 ajv 验证 schema
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} path - UIAF 数据路径
|
||||
* @returns {Promise<boolean>} 是否存在 UIAF 数据
|
||||
*/
|
||||
export async function verifyUiafData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const UiafData: TGApp.Plugins.UIAF.Export = JSON.parse(fileData)?.info;
|
||||
return UiafData?.uiaf_version !== undefined;
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UiafSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIAF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIAF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIAF 数据格式错误,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证UIAF数据-剪贴板
|
||||
* @since Beta v0.4.7
|
||||
* @returns {boolean} 是否验证通过
|
||||
*/
|
||||
export async function verifyUiafDataClipboard(): Promise<boolean> {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UiafSchema);
|
||||
const data = await window.navigator.clipboard.readText();
|
||||
try {
|
||||
const fileJson = JSON.parse(data);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIAF 数据验证失败,剪贴板数据:${data}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIAF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIAF 数据格式错误,剪贴板数据:${data}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,8 +9,9 @@ import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { AppUigfSchema } from "../data/index.js";
|
||||
import { UigfSchema } from "../data/index.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
import { timestampToDate } from "./toolFunc";
|
||||
|
||||
/**
|
||||
@@ -78,7 +79,7 @@ export function convertDataToUigf(
|
||||
export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(AppUigfSchema);
|
||||
const validate = ajv.compile(UigfSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
@@ -87,11 +88,15 @@ export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIGF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIGF 数据格式错误,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
/**
|
||||
* @file utils/dataBS.ts
|
||||
* @description 用户数据的备份、恢复、迁移
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { fs, path } from "@tauri-apps/api";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar";
|
||||
import TGSqlite from "../plugins/Sqlite";
|
||||
import TSUserAchi from "../plugins/Sqlite/modules/userAchi.js";
|
||||
import TSUserGacha from "../plugins/Sqlite/modules/userGacha.js";
|
||||
|
||||
import { exportUigfData, readUigfData, verifyUigfData } from "./UIGF.js";
|
||||
|
||||
/**
|
||||
* @description 备份用户数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} dir 备份目录路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -20,8 +24,7 @@ export async function backUpUserData(dir: string): Promise<void> {
|
||||
console.log("备份目录不存在,创建备份目录");
|
||||
await fs.createDir(dir, { recursive: true });
|
||||
}
|
||||
// 备份成就数据
|
||||
const dataAchi = await TGSqlite.getUIAF();
|
||||
const dataAchi = await TSUserAchi.getUIAF();
|
||||
await fs.writeTextFile(`${dir}${path.sep}UIAF.json`, JSON.stringify(dataAchi));
|
||||
// 备份 ck
|
||||
const dataCK = await TGSqlite.getCookie();
|
||||
@@ -29,12 +32,17 @@ export async function backUpUserData(dir: string): Promise<void> {
|
||||
// 备份深渊数据
|
||||
const dataAbyss = await TGSqlite.getAbyss();
|
||||
await fs.writeTextFile(`${dir}${path.sep}abyss.json`, JSON.stringify(dataAbyss));
|
||||
// todo 添加祈愿数据备份支持
|
||||
// 备份祈愿数据
|
||||
const uidList = await TSUserGacha.getUidList();
|
||||
for (const uid of uidList) {
|
||||
const dataGacha = await TSUserGacha.getGachaRecords(uid);
|
||||
await exportUigfData(uid, dataGacha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 恢复用户数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} dir 备份目录路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -47,17 +55,18 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const files = (await fs.readDir(dir)).filter((item) => item.type === "File");
|
||||
// 恢复成就数据
|
||||
const dataAchiPath = `${dir}${path.sep}UIAF.json`;
|
||||
if (await fs.exists(dataAchiPath)) {
|
||||
const achiFind = files.find((item) => item.name === "UIAF.json");
|
||||
if (achiFind) {
|
||||
try {
|
||||
const dataAchi: TGApp.Plugins.UIAF.Achievement[] = JSON.parse(
|
||||
await fs.readTextFile(dataAchiPath),
|
||||
await fs.readTextFile(achiFind.path),
|
||||
);
|
||||
await TGSqlite.mergeUIAF(dataAchi);
|
||||
await TSUserAchi.mergeUIAF(dataAchi);
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: "成就数据恢复失败",
|
||||
text: `成就数据恢复失败 ${e}`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
@@ -69,14 +78,14 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
// 恢复 ck
|
||||
const dataCKPath = `${dir}${path.sep}cookie.json`;
|
||||
if (await fs.exists(dataCKPath)) {
|
||||
const ckFind = files.find((item) => item.name === "cookie.json");
|
||||
if (ckFind) {
|
||||
try {
|
||||
const dataCK = await fs.readTextFile(dataCKPath);
|
||||
const dataCK = await fs.readTextFile(ckFind.path);
|
||||
await TGSqlite.saveAppData("cookie", JSON.stringify(JSON.parse(dataCK)));
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: "Cookie 数据恢复失败",
|
||||
text: `Cookie 数据恢复失败 ${e}`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
@@ -88,11 +97,11 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
// 恢复深渊数据
|
||||
const dataAbyssPath = `${dir}${path.sep}abyss.json`;
|
||||
if (await fs.exists(dataAbyssPath)) {
|
||||
const abyssFind = files.find((item) => item.name === "abyss.json");
|
||||
if (abyssFind) {
|
||||
try {
|
||||
const dataAbyss: TGApp.Sqlite.Abyss.SingleTable[] = JSON.parse(
|
||||
await fs.readTextFile(dataAbyssPath),
|
||||
await fs.readTextFile(abyssFind.path),
|
||||
);
|
||||
await TGSqlite.restoreAbyss(dataAbyss);
|
||||
} catch (e) {
|
||||
@@ -108,6 +117,29 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
color: "warn",
|
||||
});
|
||||
}
|
||||
// 恢复祈愿数据
|
||||
const reg = /UIGF_(\d+).json/;
|
||||
const dataGachaList = files.filter((item) => reg.test(item.name));
|
||||
for (const item of dataGachaList) {
|
||||
const check = await verifyUigfData(item.path);
|
||||
if (!check) {
|
||||
errNum++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const data = await readUigfData(item.path);
|
||||
const uid = data.info.uid;
|
||||
for (const item of data.list) {
|
||||
await TSUserGacha.mergeUIGF(uid, item);
|
||||
}
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: `UID: ${uid} 祈愿数据恢复失败`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
}
|
||||
}
|
||||
if (errNum === 0) {
|
||||
showSnackbar({
|
||||
text: "数据恢复成功",
|
||||
|
||||
Reference in New Issue
Block a user