mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-12 09:18:14 +08:00
🎨 fix(db): 改进代码结构
This commit is contained in:
44
src/App.vue
44
src/App.vue
@@ -33,12 +33,6 @@ import TBackTop from "./components/t-backTop.vue";
|
||||
import { fs, window, app, event } from "@tauri-apps/api";
|
||||
// store
|
||||
import { useAppStore } from "./store/modules/app";
|
||||
import { useAchievementsStore } from "./store/modules/achievements";
|
||||
// utils
|
||||
import { getBuildTime } from "./utils/TGBuild";
|
||||
// data
|
||||
import { restoreUiafData } from "./utils/UIAF";
|
||||
import TGSqlite from "./core/database/TGSqlite";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const isMain = ref(true as boolean);
|
||||
@@ -70,22 +64,11 @@ async function listenOnTheme () {
|
||||
}
|
||||
|
||||
async function checkLoad () {
|
||||
const localBuildTime = appStore.buildTime;
|
||||
const buildTime = getBuildTime();
|
||||
if (!buildTime.startsWith("dev")) {
|
||||
if (localBuildTime.startsWith("dev") || localBuildTime < buildTime) {
|
||||
appStore.buildTime = buildTime;
|
||||
console.info("数据已过期,开始加载数据...");
|
||||
appStore.loading = false;
|
||||
}
|
||||
}
|
||||
if (appStore.loading) {
|
||||
console.info("数据已加载!");
|
||||
return;
|
||||
}
|
||||
await createDataDir();
|
||||
await writeDB();
|
||||
await writeData();
|
||||
appStore.loading = true;
|
||||
console.info("数据加载完成!");
|
||||
}
|
||||
@@ -97,33 +80,6 @@ async function createDataDir () {
|
||||
if (!await fs.exists("tempData", { dir: fs.BaseDirectory.AppLocalData })) { await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData, recursive: true }); }
|
||||
console.info("数据文件夹创建完成!");
|
||||
}
|
||||
// 写入 IndexedDB
|
||||
async function writeDB () {
|
||||
const res = await TGSqlite.checkDB();
|
||||
if (!res) {
|
||||
console.info("检测到数据库不完整,正在重置数据库...");
|
||||
await TGSqlite.resetDB();
|
||||
} else {
|
||||
console.info("正在更新成就系列数据...");
|
||||
await TGSqlite.update.achievementSeries();
|
||||
console.info("正在更新成就数据...");
|
||||
await TGSqlite.update.achievement();
|
||||
}
|
||||
}
|
||||
// 恢复数据
|
||||
async function writeData () {
|
||||
console.info("开始恢复数据...");
|
||||
const res = await restoreUiafData();
|
||||
if (res !== false) {
|
||||
const { total, fin } = res;
|
||||
console.info("开始恢复成就数据...");
|
||||
const achievementsStore = useAchievementsStore();
|
||||
achievementsStore.flushData(total, fin);
|
||||
console.info("成就数据恢复完成!");
|
||||
} else {
|
||||
console.info("未找到成就数据!");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css">
|
||||
.app-main {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file core database TGSqlite.ts
|
||||
* @description SQLite 数据库相关
|
||||
* @author BTMuli<bt-muli@outlook.com>
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
|
||||
import { initDatabase, resetDatabase, checkDatabase, sqlitePath } from "./init";
|
||||
import { getAllSeries, getAchievementsBySeries, searchAchievement, getAchievementOverview } from "./select";
|
||||
import { importUIAFData, exportUIAFData } from "./UIAF";
|
||||
import { checkAchievement, checkAchievementSeries } from "./update";
|
||||
|
||||
const TGSqlite = {
|
||||
initDB: initDatabase,
|
||||
resetDB: resetDatabase,
|
||||
checkDB: checkDatabase,
|
||||
search: {
|
||||
achievement: {
|
||||
bySeries: getAchievementsBySeries,
|
||||
bySearch: searchAchievement,
|
||||
},
|
||||
achievementSeries: getAllSeries,
|
||||
overview: getAchievementOverview,
|
||||
},
|
||||
UIAF: {
|
||||
import: importUIAFData,
|
||||
export: exportUIAFData,
|
||||
},
|
||||
update: {
|
||||
achievement: checkAchievement,
|
||||
achievementSeries: checkAchievementSeries,
|
||||
},
|
||||
dbPath: sqlitePath,
|
||||
};
|
||||
|
||||
export default TGSqlite;
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* @file core database UIAF.ts
|
||||
* @description UIAF 数据导入导出
|
||||
* @author BTMuli<bt-muli@outlook.com>
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
|
||||
// tauri
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
// local
|
||||
import { sqlitePath } from "./init";
|
||||
// utils
|
||||
import { timestampToDate, getUiafStatus } from "../../utils/UIAF";
|
||||
|
||||
/**
|
||||
* @description 根据本地数据跟导入数据获取更新 SQL
|
||||
* @since Alpha v0.1.4
|
||||
* @param {TGPlugin.UIAF.Achievement} importData - 导入数据
|
||||
* @param {BTMuli.SQLite.Achievements} localData - 本地数据
|
||||
* @returns {string} SQL
|
||||
*/
|
||||
function getUpdateSql (importData: TGPlugin.UIAF.Achievement, localData: BTMuli.SQLite.Achievements): string {
|
||||
// 如果本地为未完成状态,直接更新
|
||||
if (localData.isCompleted === 0) {
|
||||
// 已完成
|
||||
if (importData.status === 2 || importData.status === 3) {
|
||||
const completedTime = timestampToDate(importData.timestamp);
|
||||
return `UPDATE Achievements SET isCompleted = 1, progress = ${importData.current}, completedTime = '${completedTime}' WHERE id = ${importData.id}`;
|
||||
} else if (importData.current > localData.progress) {
|
||||
return `UPDATE Achievements SET progress = ${importData.current} WHERE id = ${importData.id}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
// 本地为已完成状态,判断进度
|
||||
if (importData.current > localData.progress) {
|
||||
// 进度大于本地进度,更新
|
||||
return `UPDATE Achievements SET progress = ${importData.current} WHERE id = ${importData.id}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 导入 UIAF 数据,更新数据库
|
||||
* @since Alpha v0.1.4
|
||||
* @param {TGPlugin.UIAF.Achievement[]} achievements - 成就列表
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function importUIAFData (achievements: TGPlugin.UIAF.Achievement[]): Promise<void> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
for (const achievement of achievements) {
|
||||
const id = achievement.id;
|
||||
const selects: BTMuli.SQLite.Achievements[] = await db.select(`SELECT * FROM Achievements WHERE id = ${id}`);
|
||||
if (selects.length === 1) {
|
||||
const sql = getUpdateSql(achievement, selects[0]);
|
||||
if (sql !== "") await db.execute(sql);
|
||||
}
|
||||
}
|
||||
await db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 导出 UIAF 数据
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<TGPlugin.UIAF.Achievement[]>}
|
||||
*/
|
||||
export async function exportUIAFData (): Promise<TGPlugin.UIAF.Achievement[]> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
const sql = "SELECT * FROM Achievements WHERE isCompleted = 1 OR progress > 0";
|
||||
const selects: BTMuli.SQLite.Achievements[] = await db.select(sql);
|
||||
await db.close();
|
||||
const achievements: TGPlugin.UIAF.Achievement[] = [];
|
||||
for (const select of selects) {
|
||||
const completed = select.isCompleted === 1;
|
||||
const status = getUiafStatus(completed, select.progress);
|
||||
achievements.push({
|
||||
id: select.id,
|
||||
status,
|
||||
timestamp: completed && select.completedTime ? Math.round(new Date(select.completedTime).getTime() / 1000) : 0,
|
||||
current: select.progress,
|
||||
});
|
||||
}
|
||||
return achievements;
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* @file core database init.sql
|
||||
* @description SQLite 初始化
|
||||
* @author BTMuli<bt-muli@outlook>
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
|
||||
-- /////////////////////////////
|
||||
-- 创建各种表
|
||||
-- /////////////////////////////
|
||||
-- ----------------------------
|
||||
-- 成就系列表
|
||||
-- @table AchievementSeries
|
||||
-- @field {number} id 成就系列ID,主键
|
||||
-- @field {number} order 成就系列排列顺序
|
||||
-- @field {string} name 成就系列名称
|
||||
-- @field {string} version 成就系列版本
|
||||
-- @field {number} totalCount 成就系列总成就数
|
||||
-- @field {number} finCount 成就系列已完成成就数
|
||||
-- @field {string} icon 成就系列图标
|
||||
-- @field {string} nameCard 成就系列名片名称
|
||||
-- @field {string} updated 数据更新时间
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS AchievementSeries
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
"order" INTEGER,
|
||||
name TEXT DEFAULT NULL,
|
||||
version TEXT DEFAULT NULL,
|
||||
totalCount INTEGER DEFAULT 0,
|
||||
finCount INTEGER DEFAULT 0,
|
||||
icon TEXT NOT NULL,
|
||||
nameCard TEXT DEFAULT NULL,
|
||||
updated TEXT DEFAULT NULL
|
||||
);
|
||||
-- ----------------------------
|
||||
-- 成就数据表
|
||||
-- @table Achievements
|
||||
-- @field {number} id 成就ID,主键
|
||||
-- @field {number} series 成就系列ID,为成就系列表的主键
|
||||
-- @field {number} order 成就排序
|
||||
-- @field {string} name 成就名称
|
||||
-- @field {string} description 成就描述
|
||||
-- @field {number} reward 成就奖励
|
||||
-- @field {boolean} isCompleted 成就是否已完成,默认为false
|
||||
-- @field {string} completedTime 成就完成时间,默认为null
|
||||
-- @field {number} progress 成就进度,默认为0
|
||||
-- @field {string} version 成就版本
|
||||
-- @field {string} updated 数据更新时间
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS Achievements
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
series INTEGER,
|
||||
"order" INTEGER,
|
||||
name TEXT DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
reward INTEGER DEFAULT 0,
|
||||
isCompleted BOOLEAN DEFAULT 0,
|
||||
completedTime TEXT DEFAULT NULL,
|
||||
progress INTEGER DEFAULT 0,
|
||||
version TEXT DEFAULT NULL,
|
||||
updated TEXT DEFAULT NULL
|
||||
);
|
||||
|
||||
-- /////////////////////////////
|
||||
-- 创建各种触发器
|
||||
-- /////////////////////////////
|
||||
-- ----------------------------
|
||||
-- 成就系列数据插入触发器
|
||||
-- @trigger SeriesInsert
|
||||
-- @description 当成就系列数据插入时,更新更新时间
|
||||
-- ----------------------------
|
||||
CREATE TRIGGER IF NOT EXISTS SeriesInsert
|
||||
AFTER INSERT
|
||||
ON AchievementSeries
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE AchievementSeries SET updated = datetime('now') WHERE id = NEW.id;
|
||||
END;
|
||||
-- ----------------------------
|
||||
-- 成就系列数据更新触发器
|
||||
-- @trigger SeriesUpdate
|
||||
-- @description 当成就系列数据更新时,更新更新时间
|
||||
-- ----------------------------
|
||||
CREATE TRIGGER IF NOT EXISTS SeriesUpdate
|
||||
AFTER UPDATE
|
||||
OF totalCount, finCount, version
|
||||
ON AchievementSeries
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE AchievementSeries SET updated = datetime('now') WHERE id = NEW.id;
|
||||
END;
|
||||
-- ----------------------------
|
||||
-- 成就数据插入触发器
|
||||
-- @trigger AchievementInsert
|
||||
-- @description 当成就数据插入时,更新成就系列表中的数据
|
||||
-- ----------------------------
|
||||
CREATE TRIGGER IF NOT EXISTS AchievementInsert
|
||||
AFTER INSERT
|
||||
ON Achievements
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
-- 更新更新时间
|
||||
UPDATE Achievements SET updated = datetime('now') WHERE id = NEW.id;
|
||||
-- 更新成就系列总数
|
||||
UPDATE AchievementSeries SET totalCount = AchievementSeries.totalCount + 1 WHERE id = NEW.series;
|
||||
-- 如果是已完成成就,则更新成就系列完成数
|
||||
UPDATE AchievementSeries
|
||||
SET finCount = AchievementSeries.finCount + 1
|
||||
WHERE NEW.isCompleted = 1 AND id = NEW.series;
|
||||
-- 如果成就系列版本低于成就版本,则更新成就系列版本
|
||||
UPDATE AchievementSeries SET version = NEW.version WHERE version < NEW.version AND id = NEW.series;
|
||||
END;
|
||||
-- ----------------------------
|
||||
-- 成就数据更新触发器
|
||||
-- @trigger AchievementUpdate
|
||||
-- @description 当成就数据更新时,更新成就系列表中的数据
|
||||
-- ----------------------------
|
||||
CREATE TRIGGER IF NOT EXISTS AchievementUpdate
|
||||
AFTER UPDATE
|
||||
OF isCompleted, completedTime, progress
|
||||
ON Achievements
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
-- 更新更新时间
|
||||
UPDATE Achievements
|
||||
SET updated = datetime('now')
|
||||
WHERE id = NEW.id;
|
||||
-- 如果是已完成成就,则更新成就系列完成数
|
||||
UPDATE AchievementSeries
|
||||
SET finCount = AchievementSeries.finCount + 1
|
||||
WHERE id = NEW.series
|
||||
AND OLD.isCompleted = 0
|
||||
AND NEW.isCompleted = 1;
|
||||
-- 如果是未完成成就,则更新成就系列完成数
|
||||
UPDATE AchievementSeries
|
||||
SET finCount = AchievementSeries.finCount - 1
|
||||
WHERE id = NEW.series
|
||||
AND OLD.isCompleted = 1
|
||||
AND NEW.isCompleted = 0;
|
||||
END;
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* @file core database init.ts
|
||||
* @description SQLite 数据库初始化
|
||||
* @author BTMuli<bt-muli@outlook.com>
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
|
||||
// tauri
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
// sql
|
||||
import initSql from "./init.sql?raw";
|
||||
// local
|
||||
import { TGAppData } from "../../data";
|
||||
|
||||
export const sqlitePath = "sqlite:tauri-genshin.db";
|
||||
export const sqliteTBList = ["Achievements", "AchievementSeries"];
|
||||
export const sqliteTGList = ["SeriesInsert", "SeriesUpdate", "AchievementInsert", "AchievementUpdate"];
|
||||
|
||||
/**
|
||||
* @description 初始化数据库
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function initDatabase (): Promise<void> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
// 初始化表格、触发器
|
||||
await db.execute(initSql);
|
||||
// 初始化数据
|
||||
await insertData(db);
|
||||
await db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 初始化数据
|
||||
* @since Alpha v0.1.4
|
||||
* @param {Database} db
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function insertData (db: Database): Promise<void> {
|
||||
// 插入成就系列数据
|
||||
await Promise.all(Object.values(TGAppData.achievementSeries).map(async (item) => {
|
||||
let sql;
|
||||
if (item.card) {
|
||||
sql = `INSERT INTO AchievementSeries (id, \`order\`, name, version, icon, nameCard) VALUES (${item.id}, ${item.order}, '${item.name}', '${item.version}', '${item.icon}', '${item.card}')`;
|
||||
} else {
|
||||
sql = `INSERT INTO AchievementSeries (id, \`order\`, name, version, icon, nameCard) VALUES (${item.id}, ${item.order}, '${item.name}', '${item.version}', '${item.icon}', NULL)`;
|
||||
}
|
||||
await db.execute(sql);
|
||||
},
|
||||
));
|
||||
// 插入成就数据
|
||||
await Promise.all(Object.values(TGAppData.achievements).map(async (item) => {
|
||||
const sql = `INSERT INTO Achievements (id, series, \`order\`, name, description, reward, version) VALUES (${item.id}, ${item.series}, ${item.order}, '${item.name}', '${item.description}', ${item.reward}, '${item.version}')`;
|
||||
await db.execute(sql);
|
||||
},
|
||||
));
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 删除所有数据,重新创建数据
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function resetDatabase (): Promise<void> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
// 删除所有触发器
|
||||
await Promise.all(sqliteTGList.map(async (trigger) => {
|
||||
await db.execute(`DROP TRIGGER IF EXISTS ${trigger}`);
|
||||
}));
|
||||
// 删除所有表格
|
||||
await Promise.all(sqliteTBList.map(async (table) => {
|
||||
await db.execute(`DROP TABLE IF EXISTS ${table}`);
|
||||
}));
|
||||
await db.close();
|
||||
await initDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测表单是否完整
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export async function checkDatabase (): Promise<boolean> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
// 获取所有表格
|
||||
const tables: Array<{ name: string }> = await db.select("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
|
||||
// 获取所有触发器
|
||||
const triggers: Array<{ name: string }> = await db.select("SELECT name FROM sqlite_master WHERE type='trigger' ORDER BY name");
|
||||
await db.close();
|
||||
let res;
|
||||
if (tables.length !== sqliteTBList.length || triggers.length !== sqliteTGList.length) {
|
||||
res = false;
|
||||
} else if (tables.every((item) => sqliteTBList.includes(item.name)) && triggers.every((item) => sqliteTGList.includes(item.name))) {
|
||||
res = true;
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* @file core database select.ts
|
||||
* @description SQLite 数据库查询操作封装模块
|
||||
* @author BTMuli<bt-muli@outlook.com>
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
|
||||
// tauri
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
// local
|
||||
import { sqlitePath } from "./init";
|
||||
|
||||
/**
|
||||
* @description 返回所有成就系列数据
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<BTMuli.SQLite.AchievementSeries[]>}
|
||||
*/
|
||||
export async function getAllSeries (): Promise<BTMuli.SQLite.AchievementSeries[]> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
const sql = "SELECT * FROM AchievementSeries ORDER BY `order` ASC";
|
||||
const result = await db.select(sql);
|
||||
await db.close();
|
||||
return result as BTMuli.SQLite.AchievementSeries[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询成就数据,无参默认查询所有成就
|
||||
* @since Alpha v0.1.4
|
||||
* @param {number} series
|
||||
* @returns {Promise<BTMuli.SQLite.Achievements[]>}
|
||||
*/
|
||||
export async function getAchievementsBySeries (series?: number): Promise<BTMuli.SQLite.Achievements[]> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
let sql;
|
||||
// 无参默认查询所有成就
|
||||
if (!series) {
|
||||
sql = "SELECT * FROM Achievements";
|
||||
} else {
|
||||
sql = `SELECT * FROM Achievements WHERE series = ${series}`;
|
||||
}
|
||||
const result = await db.select(sql);
|
||||
await db.close();
|
||||
return result as BTMuli.SQLite.Achievements[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 条件查询
|
||||
* @since Alpha v0.1.4
|
||||
* @param {string} search
|
||||
* @returns {Promise<BTMuli.SQLite.Achievements[]>}
|
||||
*/
|
||||
export async function searchAchievement (search: string): Promise<BTMuli.SQLite.Achievements[]> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
const sql = `SELECT * FROM Achievements WHERE name LIKE '%${search}%' OR description LIKE '%${search}%'`;
|
||||
const result = await db.select(sql);
|
||||
await db.close();
|
||||
return result as BTMuli.SQLite.Achievements[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 返回成就概况
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<{total: number, fin:number}>}
|
||||
*/
|
||||
export async function getAchievementOverview (): Promise<{ total: number, fin: number }> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
const sql = "SELECT COUNT(*) AS total, SUM(isCompleted) AS fin FROM Achievements";
|
||||
const res: Array<{ total: number, fin: number }> = await db.select(sql);
|
||||
await db.close();
|
||||
return res[0];
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @file core database update.ts
|
||||
* @description SQLite 数据库更新相关
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
|
||||
// tauri
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
// local
|
||||
import { TGAppData } from "../../data";
|
||||
import { sqlitePath } from "./init";
|
||||
|
||||
/**
|
||||
* @description 数据比对-成就系列数据
|
||||
* @description 数据只有两种情况:新增、更新
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export async function checkAchievement (): Promise<void> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
await Promise.all(Object.values(TGAppData.achievements).map(async (item) => {
|
||||
// 检测是否存在
|
||||
const selectRes: BTMuli.SQLite.Achievements[] = await db.select(`SELECT * FROM Achievements WHERE id = ${item.id}`);
|
||||
if (!selectRes || selectRes.length === 0) {
|
||||
// 不存在则插入
|
||||
const sql = `INSERT INTO Achievements (id, series, \`order\`, name, description, reward, version) VALUES (${item.id}, ${item.series}, ${item.order}, '${item.name}', '${item.description}', ${item.reward}, '${item.version}')`;
|
||||
await db.execute(sql);
|
||||
} else {
|
||||
// 比对两者数据
|
||||
const selectItem = selectRes[0];
|
||||
if (selectItem.name !== item.name || selectItem.description !== item.description || selectItem.reward !== item.reward) {
|
||||
// 更新
|
||||
const sql = `UPDATE Achievements SET name = '${item.name}', description = '${item.description}', reward = ${item.reward}, version = '${item.version}' WHERE id = ${item.id}`;
|
||||
await db.execute(sql);
|
||||
}
|
||||
}
|
||||
}));
|
||||
await db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 数据比对-成就系列数据
|
||||
* @description 只有新增,更新靠触发器解决
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function checkAchievementSeries (): Promise<void> {
|
||||
const db = await Database.load(sqlitePath);
|
||||
await Promise.all(Object.values(TGAppData.achievementSeries).map(async (item) => {
|
||||
// 检测是否存在
|
||||
const selectRes: BTMuli.SQLite.AchievementSeries[] = await db.select(`SELECT * FROM AchievementSeries WHERE id = ${item.id}`);
|
||||
if (!selectRes || selectRes.length === 0) {
|
||||
// 不存在则插入
|
||||
let sql;
|
||||
if (item.card) {
|
||||
sql = `INSERT INTO AchievementSeries (id, \`order\`, name, version, icon, nameCard) VALUES (${item.id}, ${item.order}, '${item.name}', '${item.version}', '${item.icon}', '${item.card}')`;
|
||||
} else {
|
||||
sql = `INSERT INTO AchievementSeries (id, \`order\`, name, version, icon, nameCard) VALUES (${item.id}, ${item.order}, '${item.name}', '${item.version}', '${item.icon}', NULL)`;
|
||||
}
|
||||
await db.execute(sql);
|
||||
}
|
||||
}));
|
||||
await db.close();
|
||||
}
|
||||
@@ -112,8 +112,8 @@ import { useAchievementsStore } from "../store/modules/achievements";
|
||||
// Utils
|
||||
import { TGAppData } from "../data";
|
||||
import { createTGWindow } from "../utils/TGWindow";
|
||||
import { getUiafHeader, readUiafData, verifyUiafData, backupUiafData } from "../utils/UIAF";
|
||||
import TGSqlite from "../core/database/TGSqlite";
|
||||
import { getUiafHeader, readUiafData, verifyUiafData } from "../utils/UIAF";
|
||||
import TGSqlite from "../utils/TGSqlite";
|
||||
|
||||
// Store
|
||||
const achievementsStore = useAchievementsStore();
|
||||
@@ -144,13 +144,13 @@ onMounted(async () => {
|
||||
|
||||
// 加载数据,数据源:合并后的本地数据
|
||||
async function loadData () {
|
||||
const { total, fin } = await TGSqlite.search.overview();
|
||||
const { total, fin } = await TGSqlite.getAchievementsOverview();
|
||||
achievementsStore.flushData(total, fin);
|
||||
loadingTitle.value = "正在获取成就系列数据";
|
||||
CardsInfo.value = TGAppData.nameCards[1];
|
||||
seriesList.value = await TGSqlite.search.achievementSeries();
|
||||
seriesList.value = await TGSqlite.getAchievementSeries();
|
||||
loadingTitle.value = "正在获取成就数据";
|
||||
const getAchievements = await TGSqlite.search.achievement.bySeries();
|
||||
const getAchievements = await TGSqlite.getAchievements();
|
||||
getAchievements.sort((a, b) => {
|
||||
if (a.isCompleted === b.isCompleted) {
|
||||
return a.id - b.id;
|
||||
@@ -174,7 +174,7 @@ async function selectSeries (index: number) {
|
||||
loadingTitle.value = "正在获取对应的成就数据";
|
||||
selectedIndex.value = index;
|
||||
selectedSeries.value = seriesList.value[index].id;
|
||||
const getAchievements = await TGSqlite.search.achievement.bySeries(selectedSeries.value);
|
||||
const getAchievements = await TGSqlite.getAchievements(selectedSeries.value);
|
||||
loadingTitle.value = "正在查找对应的成就名片";
|
||||
let getCard: BTMuli.Genshin.NameCard;
|
||||
if (selectedSeries.value !== 0 && selectedSeries.value !== 17) {
|
||||
@@ -182,13 +182,6 @@ async function selectSeries (index: number) {
|
||||
} else {
|
||||
getCard = {} as BTMuli.Genshin.NameCard;
|
||||
}
|
||||
getAchievements.sort((a, b) => {
|
||||
if (a.isCompleted === b.isCompleted) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.isCompleted ? 1 : -1;
|
||||
}
|
||||
});
|
||||
selectedAchievement.value = getAchievements;
|
||||
getCardInfo.value = getCard;
|
||||
loading.value = false;
|
||||
@@ -209,7 +202,7 @@ async function searchCard () {
|
||||
}
|
||||
loadingTitle.value = "正在搜索";
|
||||
loading.value = true;
|
||||
const res = await TGSqlite.search.achievement.bySearch(search.value);
|
||||
const res = await TGSqlite.searchAchievements(search.value);
|
||||
selectedIndex.value = -1;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
@@ -218,15 +211,8 @@ async function searchCard () {
|
||||
snackbarColor.value = "#F5810A";
|
||||
snackbarText.value = "没有找到对应的成就";
|
||||
snackbar.value = true;
|
||||
selectedAchievement.value = await TGSqlite.search.achievement.bySeries();
|
||||
selectedAchievement.value = await TGSqlite.getAchievements();
|
||||
} else {
|
||||
res.sort((a, b) => {
|
||||
if (a.isCompleted === b.isCompleted) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.isCompleted ? 1 : -1;
|
||||
}
|
||||
});
|
||||
selectedAchievement.value = res;
|
||||
}
|
||||
}
|
||||
@@ -252,11 +238,9 @@ async function importJson () {
|
||||
loading.value = true;
|
||||
const remoteData: TGPlugin.UIAF.BaseData = JSON.parse(remoteRaw);
|
||||
loadingTitle.value = "正在合并成就数据";
|
||||
await TGSqlite.UIAF.import(remoteData.list);
|
||||
loadingTitle.value = "正在备份数据";
|
||||
await backupAchievementData();
|
||||
await TGSqlite.mergeUIAF(remoteData.list);
|
||||
loadingTitle.value = "正在刷新数据";
|
||||
const overview = await TGSqlite.search.overview();
|
||||
const overview = await TGSqlite.getAchievementsOverview();
|
||||
achievementsStore.flushData(overview.total, overview.fin);
|
||||
// 刷新数据
|
||||
await loadData();
|
||||
@@ -264,12 +248,6 @@ async function importJson () {
|
||||
}
|
||||
}
|
||||
|
||||
// 备份成就数据
|
||||
async function backupAchievementData () {
|
||||
const achievements = await TGSqlite.UIAF.export();
|
||||
await backupUiafData(achievements);
|
||||
}
|
||||
|
||||
// 导出
|
||||
async function exportJson () {
|
||||
// 判断是否有数据
|
||||
@@ -282,7 +260,7 @@ async function exportJson () {
|
||||
// 获取本地数据
|
||||
const UiafData = {
|
||||
info: await getUiafHeader(),
|
||||
list: await TGSqlite.UIAF.export(),
|
||||
list: await TGSqlite.getUIAF(),
|
||||
};
|
||||
const isSave = await dialog.save({
|
||||
// TODO: 设置保存文件名
|
||||
@@ -295,10 +273,14 @@ async function exportJson () {
|
||||
});
|
||||
if (isSave) {
|
||||
await fs.writeTextFile(isSave, JSON.stringify(UiafData));
|
||||
}
|
||||
snackbarColor.value = "#00BFA5";
|
||||
snackbarText.value = "导出成功";
|
||||
snackbar.value = true;
|
||||
} else {
|
||||
snackbarColor.value = "#F5810A";
|
||||
snackbarText.value = "导出已取消";
|
||||
snackbar.value = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ import { useHk4eStore } from "../store/modules/hk4e";
|
||||
import { useAchievementsStore } from "../store/modules/achievements";
|
||||
// utils
|
||||
import { backupUiafData, restoreUiafData } from "../utils/UIAF";
|
||||
import TGSqlite from "../core/database/TGSqlite";
|
||||
import TGSqlite from "../utils/TGSqlite";
|
||||
|
||||
// Store
|
||||
const appStore = useAppStore();
|
||||
@@ -308,7 +308,7 @@ async function doConfirm (oper: string) {
|
||||
async function backupData () {
|
||||
loadingTitle.value = "正在备份数据...";
|
||||
loading.value = true;
|
||||
const achievements = await TGSqlite.UIAF.export();
|
||||
const achievements = await TGSqlite.getUIAF();
|
||||
await backupUiafData(achievements);
|
||||
loading.value = false;
|
||||
snackbarText.value = "数据已备份!";
|
||||
@@ -319,7 +319,6 @@ async function backupData () {
|
||||
async function restoreData () {
|
||||
const res = await restoreUiafData();
|
||||
if (res !== false) {
|
||||
achievementsStore.flushData(res.total, res.fin);
|
||||
snackbarText.value = "数据已恢复!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
@@ -420,18 +419,14 @@ function delDB () {
|
||||
// 检查 SQLite 数据库
|
||||
async function checkDB () {
|
||||
loadingTitle.value = "正在检查数据库表单完整性...";
|
||||
const res = await TGSqlite.checkDB();
|
||||
const res = await TGSqlite.check();
|
||||
if (!res) {
|
||||
confirmOper.value = "resetDB";
|
||||
confirmText.value = "数据库表单不完整,是否重置数据库?";
|
||||
loading.value = false;
|
||||
confirmShow.value = true;
|
||||
} else {
|
||||
loadingTitle.value = "正在检查数据库数据完整性...";
|
||||
await TGSqlite.update.achievementSeries();
|
||||
await TGSqlite.update.achievement();
|
||||
loading.value = false;
|
||||
snackbarText.value = "数据库检查完毕!";
|
||||
snackbarText.value = "数据库表单完整!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
@@ -439,7 +434,7 @@ async function checkDB () {
|
||||
|
||||
// 重置 SQLite 数据库
|
||||
async function resetDB () {
|
||||
await TGSqlite.resetDB();
|
||||
await TGSqlite.reset();
|
||||
snackbarText.value = "数据库已重置!请载入备份数据。";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
|
||||
@@ -12,6 +12,8 @@ import TPosition from "../components/t-position.vue";
|
||||
import TCalendar from "../components/t-calendar.vue";
|
||||
// store
|
||||
import { useHomeStore } from "../store/modules/home";
|
||||
// utils
|
||||
import TGSqlite from "../utils/TGSqlite";
|
||||
|
||||
// store
|
||||
const homeStore = useHomeStore();
|
||||
@@ -40,6 +42,12 @@ function readLoading (): void {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = "正在检测数据完整性";
|
||||
const isOK = await TGSqlite.check();
|
||||
if (!isOK) {
|
||||
loadingTitle.value = "正在修复数据";
|
||||
await TGSqlite.reset();
|
||||
}
|
||||
loadingTitle.value = "正在加载首页";
|
||||
const showItems = homeStore.getShowValue();
|
||||
await Promise.allSettled(
|
||||
|
||||
@@ -27,6 +27,7 @@ const TGSql = {
|
||||
achievement: insertAchievementData,
|
||||
achievementSeries: insertAchievementSeriesData,
|
||||
bbsPost: insertBBSPostData,
|
||||
UIAF: importUIAFData,
|
||||
},
|
||||
update: {
|
||||
achievement: updateAchievementData,
|
||||
@@ -151,7 +152,7 @@ function initBbsPostTable (): string[] {
|
||||
created TEXT DEFAULT NULL,
|
||||
modified TEXT DEFAULT NULL,
|
||||
isRead BOOLEAN DEFAULT 0,
|
||||
updated TEXT DEFAULT NULL,
|
||||
updated TEXT DEFAULT NULL
|
||||
);
|
||||
`);
|
||||
return sqlRes;
|
||||
@@ -347,4 +348,35 @@ function updateBBSPostData (data: BTMuli.SQLite.BBSPost, isRead: boolean = false
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 导入UIAF数据
|
||||
* @since Alpha v0.1.4
|
||||
* @param {TGPlugin.UIAF.Achievement[]} data
|
||||
* @returns {string[]} sql
|
||||
*/
|
||||
function importUIAFData (data: TGPlugin.UIAF.Achievement[]): string[] {
|
||||
const sqlRes: string[] = [];
|
||||
data.map((achievement) => {
|
||||
let sql;
|
||||
// 获取完成状态
|
||||
const isCompleted = achievement.status === 2 || achievement.status === 3;
|
||||
if (isCompleted) {
|
||||
const completedTime = new Date(achievement.timestamp * 1000).toISOString().replace("T", " ").replace("Z", "");
|
||||
sql = `
|
||||
UPDATE Achievements
|
||||
SET isCompleted = 1, completedTime = '${completedTime}', progress = ${achievement.current}, updated = datetime('now', 'localtime')
|
||||
WHERE id = ${achievement.id} AND (isCompleted = 0 OR completedTime != '${completedTime}' OR progress != ${achievement.current});
|
||||
`;
|
||||
} else {
|
||||
sql = `
|
||||
UPDATE Achievements
|
||||
SET progress = ${achievement.current}, updated = datetime('now', 'localtime')
|
||||
WHERE id = ${achievement.id} AND progress != ${achievement.current};
|
||||
`;
|
||||
}
|
||||
return sqlRes.push(sql);
|
||||
});
|
||||
return sqlRes;
|
||||
}
|
||||
|
||||
export default TGSql;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
import TGSql from "./TGSql";
|
||||
import { getUiafStatus } from "./UIAF";
|
||||
import { TGAppData } from "../data";
|
||||
|
||||
const dbLink = await Database.load("sqlite:tauri-genshin.db");
|
||||
|
||||
class TGSqlite {
|
||||
/**
|
||||
@@ -46,7 +50,8 @@ class TGSqlite {
|
||||
* @since Alpha v0.1.4
|
||||
*/
|
||||
constructor () {
|
||||
this.db = new Database(this.dbPath);
|
||||
// 异步
|
||||
this.db = dbLink;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,8 +96,10 @@ class TGSqlite {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async reset (): Promise<void> {
|
||||
const sql = "DROP TABLE IF EXISTS AppData, Achievement, AchievementSeries, BbsPost;";
|
||||
this.tables.map(async (item) => {
|
||||
const sql = `DROP TABLE IF EXISTS ${item};`;
|
||||
await this.db.execute(sql);
|
||||
});
|
||||
await this.init();
|
||||
}
|
||||
|
||||
@@ -129,17 +136,11 @@ class TGSqlite {
|
||||
/**
|
||||
* @description 获取成就系列列表
|
||||
* @memberof TGSqlite
|
||||
* @param {number} [seriesId] 系列 ID
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<BTMuli.SQLite.AchievementSeries[]>}
|
||||
*/
|
||||
public async getAchievementSeries (seriesId?: number): Promise<BTMuli.SQLite.AchievementSeries[]> {
|
||||
let sql;
|
||||
if (seriesId) {
|
||||
sql = `SELECT * FROM AchievementSeries WHERE id=${seriesId};`;
|
||||
} else {
|
||||
sql = "SELECT * FROM AchievementSeries;";
|
||||
}
|
||||
public async getAchievementSeries (): Promise<BTMuli.SQLite.AchievementSeries[]> {
|
||||
const sql = "SELECT * FROM AchievementSeries ORDER BY `order` ASC;";
|
||||
const res: BTMuli.SQLite.AchievementSeries[] = await this.db.select(sql);
|
||||
return res;
|
||||
}
|
||||
@@ -154,14 +155,26 @@ class TGSqlite {
|
||||
public async getAchievements (seriesId?: number): Promise<BTMuli.SQLite.Achievements[]> {
|
||||
let sql;
|
||||
if (seriesId) {
|
||||
sql = `SELECT * FROM Achievement WHERE seriesId=${seriesId};`;
|
||||
sql = `SELECT * FROM Achievements WHERE series=${seriesId} ORDER BY isCompleted DESC, \`order\` ASC;`;
|
||||
} else {
|
||||
sql = "SELECT * FROM Achievement;";
|
||||
sql = "SELECT * FROM Achievements ORDER BY isCompleted DESC, `order` ASC;";
|
||||
}
|
||||
const res: BTMuli.SQLite.Achievements[] = await this.db.select(sql);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就概况
|
||||
* @since Alpha v0.1.4
|
||||
* @memberof TGSqlite
|
||||
* @returns {Promise<{total:number,fin:number}>}
|
||||
*/
|
||||
public async getAchievementsOverview (): Promise<{ total: number, fin: number }> {
|
||||
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
|
||||
const res: Array<{ total: number, fin: number }> = await this.db.select(sql);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询成就
|
||||
* @memberof TGSqlite
|
||||
@@ -173,13 +186,50 @@ class TGSqlite {
|
||||
let sql;
|
||||
if (keyword.startsWith("v")) {
|
||||
const version = keyword.replace("v", "");
|
||||
sql = `SELECT * FROM Achievement WHERE version='${version}';`;
|
||||
sql = `SELECT * FROM Achievements WHERE version='${version}' ORDER BY isCompleted DESC, \`order\` ASC;`;
|
||||
} else {
|
||||
sql = `SELECT * FROM Achievement WHERE name LIKE '%${keyword}%' OR description LIKE '%${keyword}%';`;
|
||||
sql = `SELECT * FROM Achievements WHERE name LIKE '%${keyword}%' OR description LIKE '%${keyword}%' ORDER BY isCompleted DESC, \`order\` ASC;`;
|
||||
}
|
||||
const res: BTMuli.SQLite.Achievements[] = await this.db.select(sql);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并 UIAF 数据
|
||||
* @memberof TGSqlite
|
||||
* @param {BTMuli.UIAF.Achievement[]} achievements UIAF 数据
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async mergeUIAF (achievements: TGPlugin.UIAF.Achievement[]): Promise<void> {
|
||||
const sql = TGSql.insert.UIAF(achievements);
|
||||
for (const item of sql) {
|
||||
await this.db.execute(item);
|
||||
}
|
||||
}
|
||||
|
||||
export default TGSqlite;
|
||||
/**
|
||||
* @description 获取 UIAF 数据
|
||||
* @memberof TGSqlite
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<TGPlugin.UIAF.Achievement[]>}
|
||||
*/
|
||||
public async getUIAF (): Promise<TGPlugin.UIAF.Achievement[]> {
|
||||
const sql = "SELECT * FROM Achievements WHERE isCompleted = 1 OR progress > 0";
|
||||
const res: BTMuli.SQLite.Achievements[] = await this.db.select(sql);
|
||||
const achievements: TGPlugin.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() : 0,
|
||||
current: item.progress,
|
||||
});
|
||||
}
|
||||
return achievements;
|
||||
}
|
||||
}
|
||||
|
||||
export default new TGSqlite();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// tauri
|
||||
import { app, fs, path } from "@tauri-apps/api";
|
||||
// utils
|
||||
import TGSqlite from "../core/database/TGSqlite";
|
||||
import TGSqlite from "./TGSqlite";
|
||||
|
||||
/**
|
||||
* @description 时间戳转换为日期
|
||||
@@ -110,16 +110,15 @@ export async function backupUiafData (achievementData: TGPlugin.UIAF.Achievement
|
||||
/**
|
||||
* @description 根据 UIAF 数据恢复成就数据
|
||||
* @since Alpha v0.1.4
|
||||
* @returns {Promise<{total: number, fin: number}> | false} 恢复的成就数量
|
||||
* @returns {Promise<boolean>} 恢复的成就数量
|
||||
*/
|
||||
export async function restoreUiafData (): Promise<{ total: number, fin: number } | false> {
|
||||
export async function restoreUiafData (): Promise<boolean> {
|
||||
const uiafPath = `${await path.appLocalDataDir()}\\userData\\UIAF.json`;
|
||||
// 检测是否存在 UIAF 数据
|
||||
if (!await fs.exists(uiafPath)) {
|
||||
return false;
|
||||
}
|
||||
const uiafData = JSON.parse(await fs.readTextFile(uiafPath)) as TGPlugin.UIAF.Achievement[];
|
||||
await TGSqlite.UIAF.import(uiafData);
|
||||
// 返回
|
||||
return await TGSqlite.search.overview();
|
||||
await TGSqlite.mergeUIAF(uiafData);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user