Files
TeyvatGuide/src/components/userGacha/ugo-uid.vue

449 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- UIGF4导入导出组件 -->
<template>
<TOverlay v-model="visible" blur-val="5px">
<div class="ugo-box">
<div class="ugo-top">
<div class="ugo-title">{{ title }}</div>
<div class="ugo-fp" title="点击选择文件路径" @click="selectFile()">文件路径:{{ fp }}</div>
</div>
<div v-if="props.mode === 'import' && importRaw" class="ugo-header">
<div class="ugo-header-item">
<div>应用信息</div>
<div>
{{ importRaw.data.info.export_app }} {{ importRaw.data.info.export_app_version }}
</div>
</div>
<div class="ugo-header-item">
<div>文件UIGF版本</div>
<div>
{{ importRaw.isV4 ? importRaw.data.info.version : importRaw.data.info.uigf_version }}
</div>
</div>
<div class="ugo-header-item">
<div>导出时间</div>
<div>{{ timestampToDate(Number(importRaw.data.info.export_timestamp) * 1000) }}</div>
</div>
</div>
<v-item-group v-model="selectedData" class="ugo-content" multiple>
<v-item
v-for="(item, index) in data"
:key="index"
v-slot="{ isSelected, toggle }"
:value="item"
>
<div class="ugo-item" @click="toggle">
<div class="ugo-item-left">
<div class="ugo-item-title">
<span>{{ item.uid }} - {{ item.length }}</span>
<span>{{ item.isUgc ? "颂愿记录" : "祈愿记录" }}</span>
</div>
<div class="ugo-item-sub">{{ item.time }}</div>
</div>
<div class="ugo-item-right">
<v-btn :class="{ active: isSelected }" class="ugo-item-btn">
<v-icon>{{ isSelected ? "mdi-check" : "mdi-checkbox-blank-outline" }}</v-icon>
</v-btn>
</div>
</div>
</v-item>
</v-item-group>
<div class="ugo-bottom">
<v-btn :rounded="true" class="ugo-item-btn" @click="visible = false">取消</v-btn>
<v-btn :rounded="true" class="ugo-item-btn" @click="handleSelected()">确定</v-btn>
</div>
</div>
</TOverlay>
</template>
<script lang="ts" setup>
import TOverlay from "@comp/app/t-overlay.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TSUserGacha from "@Sqlm/userGacha.js";
import TSUserGachaB from "@Sqlm/userGachaB.js";
import { path } from "@tauri-apps/api";
import { open } from "@tauri-apps/plugin-dialog";
import TGLogger from "@utils/TGLogger.js";
import { timestampToDate } from "@utils/toolFunc.js";
import { checkUigfData, exportUigf4Data, readUigf4Data, readUigfData } from "@utils/UIGF.js";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
type UgoUidProps = {
/** 导入/导出 */
mode: "import" | "export";
/** filePathImport导入路径 */
fpi?: string;
/** filePathExport导出路径 */
fpe?: string;
};
/**
* UID项
*/
type UgoUidItem = {
/** UID */
uid: string;
/** 数据条数 */
length: number;
/** 数据时间段 */
time: string;
/** 是否是颂愿数据 */
isUgc: boolean;
};
/** 兼容不同版本的导入 */
type UgoUidImportRaw =
| { isV4: true; data: TGApp.Plugins.UIGF.Schema4 }
| { isV4: false; data: TGApp.Plugins.UIGF.Schema };
const fpEmptyText = "点击选择文件路径";
const props = defineProps<UgoUidProps>();
const visible = defineModel<boolean>();
const fp = ref<string>(fpEmptyText);
const importRaw = shallowRef<UgoUidImportRaw>();
const dataRaw = shallowRef<TGApp.Plugins.UIGF.Schema4>();
const data = shallowRef<Array<UgoUidItem>>([]);
const selectedData = shallowRef<Array<UgoUidItem>>([]);
const title = computed<string>(() => (props.mode === "import" ? "导入" : "导出"));
onMounted(async () => {
if (props.mode === "export") fp.value = await getDefaultSavePath();
});
watch(
() => [visible.value, props.mode, props.fpi, props.fpe],
async () => {
if (visible.value) await refreshData();
},
);
async function getDefaultSavePath(): Promise<string> {
const tsNow = Math.floor(Date.now() / 1000);
return `${await path.downloadDir()}${path.sep()}UIGFv4.2_${tsNow}.json`;
}
async function refreshData(): Promise<void> {
selectedData.value = [];
data.value = [];
dataRaw.value = undefined;
importRaw.value = undefined;
if (props.mode === "import") {
fp.value = props.fpi ?? fpEmptyText;
await refreshImport();
} else {
fp.value = props.fpe ?? (await getDefaultSavePath());
await refreshExport();
}
}
async function selectFile(): Promise<void> {
const defaultPath =
props.mode === "import" ? await getDefaultSavePath() : await path.downloadDir();
const file = await open({
multiple: false,
title: "选择文件",
filters: [{ name: "UIGF JSON", extensions: ["json"] }],
defaultPath,
directory: false,
});
if (file === null) {
showSnackbar.cancel("已取消文件选择");
return;
}
fp.value = file;
if (props.mode === "import") await refreshImport();
}
async function refreshImport(): Promise<void> {
if (fp.value === fpEmptyText) return;
try {
await showLoading.start("正在导入数据...", "正在验证数据...");
const isV4 = await checkUigfData(fp.value);
console.info(isV4);
if (isV4 === null) {
await showLoading.end();
return;
}
await showLoading.update("数据验证成功,正在读取数据...");
if (isV4) {
const read = await readUigf4Data(fp.value);
importRaw.value = { isV4: true, data: read };
const hk4eUids = read.hk4e?.map(parseData4) ?? [];
const ugcUids = read.hk4e_ugc?.map(parseUgc) ?? [];
data.value = [...hk4eUids, ...ugcUids];
} else {
const read = await readUigfData(fp.value);
console.log(read.list.length);
importRaw.value = { isV4: false, data: read };
data.value = [parseData(read)];
}
await showLoading.end();
} catch (e) {
if (e instanceof Error) {
showSnackbar.error(`[${e.name}] ${e.message}`);
await TGLogger.Error(`[UgoUid][handleImportData] ${e.name} ${e.message}`);
} else {
showSnackbar.error(`[${e}]`);
await TGLogger.Error(`[UgoUid][handleImportData] ${e}`);
}
}
}
function parseData(data: TGApp.Plugins.UIGF.Schema): UgoUidItem {
const timeList = data.list.map((item) => new Date(item.time).getTime());
return {
uid: data.info.uid,
length: data.list.length,
time: `${timestampToDate(Math.min(...timeList))} ~ ${timestampToDate(Math.max(...timeList))}`,
isUgc: false,
};
}
function parseData4(data: TGApp.Plugins.UIGF.GachaHk4e): UgoUidItem {
const timeList = data.list.map((item) => new Date(item.time).getTime());
return {
uid: data.uid.toString(),
length: data.list.length,
time: `${timestampToDate(Math.min(...timeList))} ~ ${timestampToDate(Math.max(...timeList))}`,
isUgc: false,
};
}
function parseUgc(data: TGApp.Plugins.UIGF.GachaUgc): UgoUidItem {
const timeList = data.list.map((item) => new Date(item.time).getTime());
return {
uid: data.uid.toString(),
length: data.list.length,
time: `${timestampToDate(Math.min(...timeList))} ~ ${timestampToDate(Math.max(...timeList))}`,
isUgc: true,
};
}
async function refreshExport(): Promise<void> {
const uidHk4e = await TSUserGacha.getUidList();
const uidUgc = await TSUserGachaB.getUidList();
const tmpData: Array<UgoUidItem> = [];
for (const uid of uidHk4e) {
const dataRaw = await TSUserGacha.record.all(uid);
tmpData.push(parseLocalHk4e(dataRaw));
}
for (const uid of uidUgc) {
const dataRaw = await TSUserGachaB.getGachaRecords(uid);
tmpData.push(parseLocalUgc(dataRaw));
}
data.value = tmpData;
}
function parseLocalHk4e(data: Array<TGApp.Sqlite.Gacha.Gacha>): UgoUidItem {
const timeList = data.map((item) => new Date(item.time).getTime());
return {
uid: data[0].uid,
length: data.length,
time: `${timestampToDate(Math.min(...timeList))} ~ ${timestampToDate(Math.max(...timeList))}`,
isUgc: false,
};
}
function parseLocalUgc(data: Array<TGApp.Sqlite.Gacha.GachaB>): UgoUidItem {
const timeList = data.map((item) => new Date(item.time).getTime());
return {
uid: data[0].uid,
length: data.length,
time: `${timestampToDate(Math.min(...timeList))} ~ ${timestampToDate(Math.max(...timeList))}`,
isUgc: true,
};
}
async function handleSelected(): Promise<void> {
if (props.mode === "import") return await handleImport();
return await handleExport();
}
async function handleImport(): Promise<void> {
if (!importRaw.value) {
showSnackbar.error("未获取到数据!");
fp.value = fpEmptyText;
return;
}
if (selectedData.value.length === 0) {
showSnackbar.warn("请至少选择一个!");
return;
}
await showLoading.start("正在导入数据...");
if (importRaw.value.isV4) {
for (const item of selectedData.value) {
await showLoading.update(`正在导入UID: ${item.uid}`);
if (!item.isUgc) {
const dataFind = importRaw.value.data.hk4e?.find((i) => i.uid.toString() === item.uid);
if (!dataFind) {
showSnackbar.error(`未找到UID: ${item.uid}`);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
continue;
}
await TSUserGacha.mergeUIGF4(dataFind, true);
} else {
const dataFind = importRaw.value.data.hk4e_ugc?.find((i) => i.uid.toString() === item.uid);
if (!dataFind) {
showSnackbar.error(`未找到UID: ${item.uid}`);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
continue;
}
await TSUserGachaB.mergeUIGF4(dataFind, true);
}
}
} else {
const iUid = selectedData.value[0].uid;
await showLoading.update(`正在导入UID:${iUid}`);
await TSUserGacha.mergeUIGF(iUid, importRaw.value.data.list, true);
}
await showLoading.end();
showSnackbar.success("导入成功!即将刷新页面...");
window.location.reload();
}
async function handleExport(): Promise<void> {
if (selectedData.value.length === 0) {
showSnackbar.warn("请至少选择一个!");
return;
}
const totalCnt = selectedData.value.map((s) => s.length).reduce((a, b) => a + b, 0);
await showLoading.start(
"正在导出数据...",
`${selectedData.value.length}条UID - ${totalCnt}条记录`,
);
await exportUigf4Data(
selectedData.value.filter((i) => !i.isUgc).map((s) => s.uid.toString()),
selectedData.value.filter((i) => i.isUgc).map((s) => s.uid.toString()),
fp.value,
);
await showLoading.end();
showSnackbar.success(`导出成功! 文件路径: ${fp.value}`);
fp.value = await getDefaultSavePath();
}
</script>
<style lang="scss" scoped>
.ugo-box {
position: relative;
width: 600px;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
background: var(--app-page-bg);
}
.ugo-top {
position: relative;
display: flex;
width: 100%;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
column-gap: 10px;
}
.ugo-title {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.ugo-fp {
color: var(--tgc-od-white);
cursor: pointer;
font-size: 12px;
word-break: break-all;
}
.ugo-header {
position: relative;
display: flex;
width: 100%;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.ugo-header-item {
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;
font-size: 16px;
gap: 5px;
:first-child {
color: var(--box-text-7);
font-family: var(--font-title);
}
:last-child {
color: var(--box-text-5);
}
}
.ugo-content {
position: relative;
display: flex;
width: 100%;
max-height: 300px;
flex-direction: column;
align-items: center;
justify-content: flex-start;
margin-top: 10px;
margin-bottom: 10px;
gap: 10px;
overflow-y: auto;
}
.ugo-item {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
padding: 5px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
background: var(--box-bg-1);
color: var(--box-text-1);
cursor: pointer;
}
.ugo-item-title {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
column-gap: 8px;
font-family: var(--font-title);
font-size: 16px;
:last-child {
color: var(--tgc-od-red);
}
}
.ugo-item-sub {
font-size: 12px;
}
.ugo-item-btn {
height: 40px;
border: 1px solid var(--common-shadow-2);
background: var(--tgc-btn-1);
color: var(--btn-text);
font-family: var(--font-title);
&.active {
color: var(--tgc-od-green);
}
}
.ugo-bottom {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
</style>