mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-10 08:58:15 +08:00
✨ 采用 ajv 验证 UIGF #109
This commit is contained in:
27
package.json
27
package.json
@@ -68,6 +68,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"@tauri-apps/api": "^1.5.4",
|
||||
"ajv": "^8.13.0",
|
||||
"artplayer": "^5.1.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"color-convert": "^2.0.1",
|
||||
@@ -80,42 +81,42 @@
|
||||
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1",
|
||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.4.25",
|
||||
"vue": "^3.4.26",
|
||||
"vue-echarts": "^6.7.1",
|
||||
"vue-json-viewer": "^3.0.4",
|
||||
"vue-router": "^4.3.2",
|
||||
"vuetify": "^3.5.17",
|
||||
"vuetify": "^3.6.3",
|
||||
"wcag-color": "^1.1.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.0.2",
|
||||
"@eslint/js": "^9.1.1",
|
||||
"@eslint/js": "^9.2.0",
|
||||
"@tauri-apps/cli": "^1.5.12",
|
||||
"@types/color-convert": "^2.0.3",
|
||||
"@types/js-md5": "^0.7.2",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/node": "^20.12.8",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/parser": "^7.7.1",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^9.1.1",
|
||||
"eslint": "^9.2.0",
|
||||
"eslint-config-love": "^47.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsonc": "^2.15.1",
|
||||
"eslint-plugin-n": "^17.3.1",
|
||||
"eslint-plugin-n": "^17.4.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"eslint-plugin-yml": "^1.14.0",
|
||||
"globals": "^15.0.0",
|
||||
"globals": "^15.1.0",
|
||||
"husky": "^9.0.11",
|
||||
"jsonc-eslint-parser": "^2.4.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"oxlint": "^0.3.1",
|
||||
"oxlint": "^0.3.2",
|
||||
"prettier": "3.2.5",
|
||||
"stylelint": "^16.4.0",
|
||||
"stylelint": "^16.5.0",
|
||||
"stylelint-config-idiomatic-order": "^10.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^2.8.0",
|
||||
@@ -123,9 +124,9 @@
|
||||
"stylelint-order": "^6.0.4",
|
||||
"stylelint-prettier": "^5.0.0",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^7.7.1",
|
||||
"vite": "^5.2.10",
|
||||
"vite-plugin-vue-devtools": "^7.1.2",
|
||||
"typescript-eslint": "^7.8.0",
|
||||
"vite": "^5.2.11",
|
||||
"vite-plugin-vue-devtools": "^7.1.3",
|
||||
"vite-plugin-vuetify": "^2.0.3",
|
||||
"vue-eslint-parser": "^9.4.2",
|
||||
"yaml-eslint-parser": "^1.2.2"
|
||||
|
||||
771
pnpm-lock.yaml
generated
771
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
96
src/data/app/uigf-schema.json
Normal file
96
src/data/app/uigf-schema.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"title": "UID of the export record"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"title": "language in the format of languagecode2-country/regioncode2"
|
||||
},
|
||||
"export_timestamp": {
|
||||
"type": "number",
|
||||
"title": "Export UNIX timestamp (accurate to the second)"
|
||||
},
|
||||
"export_time": {
|
||||
"type": "string",
|
||||
"title": "Export time",
|
||||
"description": "yyyy-MM-dd HH:mm:ss"
|
||||
},
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"title": "Name of the export application"
|
||||
},
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"title": "Version of the export application"
|
||||
},
|
||||
"uigf_version": {
|
||||
"type": "string",
|
||||
"title": "UIGF version; follow the regular expression pattern",
|
||||
"pattern": "v\\d+\\.\\d+"
|
||||
},
|
||||
"region_time_zone": {
|
||||
"type": "number",
|
||||
"title": "Region timezone offset"
|
||||
}
|
||||
},
|
||||
"required": ["uid", "uigf_version"],
|
||||
"title": "UIGF Export Information"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uigf_gacha_type": {
|
||||
"type": "string",
|
||||
"title": "UIGF gacha type",
|
||||
"description": "Used to differentiate different gacha types with the same pity calculation for items"
|
||||
},
|
||||
"gacha_type": {
|
||||
"type": "string",
|
||||
"title": "Gacha type"
|
||||
},
|
||||
"item_id": {
|
||||
"type": "string",
|
||||
"title": "Internal ID of the item"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"title": "Count, usually 1"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"title": "Time when the item was obtained"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Item name"
|
||||
},
|
||||
"item_type": {
|
||||
"type": "string",
|
||||
"title": "Item type"
|
||||
},
|
||||
"rank_type": {
|
||||
"type": "string",
|
||||
"title": "Item rank"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "Internal ID of the record"
|
||||
}
|
||||
},
|
||||
"required": ["uigf_gacha_type", "gacha_type", "id", "item_id", "time"],
|
||||
"title": "UIGF Item"
|
||||
},
|
||||
"title": "Item List"
|
||||
}
|
||||
},
|
||||
"required": ["info", "list"],
|
||||
"title": "UIGF Root Object"
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* @file src/data/index.ts
|
||||
* @description 数据文件入口
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import type { SchemaType } from "ajv/lib/types/index.js";
|
||||
|
||||
// 应用数据
|
||||
import achievements from "./app/achievements.json";
|
||||
import achievementSeries from "./app/achievementSeries.json";
|
||||
@@ -12,6 +14,7 @@ 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";
|
||||
@@ -30,6 +33,7 @@ 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;
|
||||
export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar;
|
||||
export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw;
|
||||
export const ArcBirRole: TGApp.Archive.Birth.RoleItem[] = arcBirRole;
|
||||
|
||||
@@ -304,13 +304,7 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
return;
|
||||
}
|
||||
const check = await verifyUigfData(<string>selectedFile);
|
||||
if (!check) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "读取 UIGF 文件失败,请检查文件是否符合规范",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!check) return;
|
||||
const remoteData = await readUigfData(<string>selectedFile);
|
||||
const res = await showConfirm({
|
||||
title: "是否导入祈愿数据?",
|
||||
@@ -342,9 +336,9 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
await TGLogger.Info(
|
||||
`[UserGacha][handleImportBtn] 成功导入 ${remoteData.info.uid} 的 ${remoteData.list.length} 条祈愿数据`,
|
||||
);
|
||||
// setTimeout(() => {
|
||||
// window.location.reload();
|
||||
// }, 1000);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 导出按钮点击事件
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
/**
|
||||
* @file utils/UIGF.ts
|
||||
* @description UIGF工具类
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app, fs, path } from "@tauri-apps/api";
|
||||
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 { timestampToDate } from "./toolFunc";
|
||||
|
||||
@@ -65,16 +70,30 @@ export function convertDataToUigf(
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测是否存在 UIGF 数据
|
||||
* @description 粗略检测,不保证数据完整性
|
||||
* @since Alpha v0.2.3
|
||||
* @description 检测是否存在 UIGF 数据,采用 ajv 验证 schema
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} path - UIGF 数据路径
|
||||
* @returns {Promise<boolean>} 是否存在 UIGF 数据
|
||||
*/
|
||||
export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const UigfData: TGApp.Plugins.UIGF.Export = JSON.parse(fileData)?.info;
|
||||
return UigfData?.uigf_version !== undefined;
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(AppUigfSchema);
|
||||
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",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user