🎨 fix(db): 改进代码结构

This commit is contained in:
BTMuli
2023-04-25 16:08:35 +08:00
parent cc42d1fcb3
commit 35d9382643
13 changed files with 135 additions and 611 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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];
}

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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(

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}