-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
381 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,340 @@ | ||
<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 class="ugo-header" v-if="props.mode === 'import' && dataRaw"> | ||
<div class="ugo-header-item"> | ||
<div>应用信息:</div> | ||
<div>{{ dataRaw.info.export_app }} {{ dataRaw.info.export_app_version }}</div> | ||
</div> | ||
<div class="ugo-header-item"> | ||
<div>文件UIGF版本:</div> | ||
<div>{{ dataRaw.info.version }}</div> | ||
</div> | ||
<div class="ugo-header-item"> | ||
<div>导出时间:</div> | ||
<div>{{ timestampToDate(Number(dataRaw.info.export_timestamp) * 1000) }}</div> | ||
</div> | ||
</div> | ||
<v-item-group multiple v-model="selectedData" class="ugo-content"> | ||
<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">{{ item.uid }} - {{ item.length }}条记录</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 class="ugo-item-btn" @click="visible = false" :rounded="true">取消</v-btn> | ||
<v-btn class="ugo-item-btn" @click="handleSelected()" :rounded="true">确定</v-btn> | ||
</div> | ||
</div> | ||
</TOverlay> | ||
</template> | ||
<script lang="ts" setup> | ||
import { path } from "@tauri-apps/api"; | ||
import { open } from "@tauri-apps/plugin-dialog"; | ||
import { writeTextFile } from "@tauri-apps/plugin-fs"; | ||
import { computed, onMounted, ref, shallowRef, watch } from "vue"; | ||
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js"; | ||
import TGLogger from "../../utils/TGLogger.js"; | ||
import { timestampToDate } from "../../utils/toolFunc.js"; | ||
import { getUigf4Header, getUigf4Item, readUigf4Data, verifyUigfData } from "../../utils/UIGF.js"; | ||
import TOverlay from "../app/t-overlay.vue"; | ||
import showLoading from "../func/loading.js"; | ||
import showSnackbar from "../func/snackbar.js"; | ||
type UgoUidProps = | ||
| { modelValue: boolean; mode: "export" } | ||
| { modelValue: boolean; mode: "import" }; | ||
type UgoUidEmits = (e: "update:modelValue", v: boolean) => void; | ||
type UgoUidItem = { uid: string; length: number; time: string }; | ||
const props = defineProps<UgoUidProps>(); | ||
const emits = defineEmits<UgoUidEmits>(); | ||
const dataRaw = shallowRef<TGApp.Plugins.UIGF.Schema4>(); | ||
const data = shallowRef<UgoUidItem[]>([]); | ||
const selectedData = shallowRef<UgoUidItem[]>([]); | ||
const title = computed<string>(() => (props.mode === "import" ? "导入" : "导出")); | ||
const fp = ref<string>("未选择"); | ||
const visible = computed<boolean>({ | ||
get: () => props.modelValue, | ||
set: (v) => emits("update:modelValue", v), | ||
}); | ||
onMounted(async () => { | ||
if (props.mode === "export") fp.value = await getDefaultSavePath(); | ||
}); | ||
watch( | ||
() => props.modelValue, | ||
async (v) => (v ? await refreshData() : undefined), | ||
{ immediate: true }, | ||
); | ||
async function getDefaultSavePath(): Promise<string> { | ||
const tsNow = new Date().getTime(); | ||
return `${await path.downloadDir()}${path.sep()}UIGF4_${tsNow}.json`; | ||
} | ||
async function refreshData(): Promise<void> { | ||
selectedData.value = []; | ||
data.value = []; | ||
dataRaw.value = undefined; | ||
if (props.mode === "import") { | ||
fp.value = "未选择"; | ||
await handleImportData(); | ||
} else { | ||
fp.value = await getDefaultSavePath(); | ||
await handleExportData(); | ||
} | ||
} | ||
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 handleImportData(); | ||
} | ||
async function handleImportData(): Promise<void> { | ||
if (fp.value === "未选择") return; | ||
try { | ||
showLoading.start("正在导入数据...", "正在验证数据..."); | ||
const check = await verifyUigfData(fp.value, true); | ||
if (!check) { | ||
showLoading.end(); | ||
return; | ||
} | ||
showLoading.update("正在导入数据...", "正在读取数据..."); | ||
const uigfData = await readUigf4Data(fp.value); | ||
dataRaw.value = uigfData; | ||
data.value = uigfData.hk4e.map(parseData); | ||
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.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))}`, | ||
}; | ||
} | ||
async function handleExportData(): Promise<void> { | ||
const uidList = await TSUserGacha.getUidList(); | ||
const tmpData: UgoUidItem[] = []; | ||
for (const uid of uidList) { | ||
const dataRaw = await TSUserGacha.getGachaRecords(uid); | ||
tmpData.push(parseDataRaw(dataRaw)); | ||
} | ||
data.value = tmpData; | ||
} | ||
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.SingleTable[]): 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))}`, | ||
}; | ||
} | ||
async function handleSelected(): Promise<void> { | ||
if (props.mode === "import") { | ||
if (!dataRaw.value) { | ||
showSnackbar.error("未获取到数据!"); | ||
fp.value = "未选择"; | ||
return; | ||
} | ||
if (selectedData.value.length === 0) { | ||
showSnackbar.warn("请至少选择一个!"); | ||
return; | ||
} | ||
for (const item of selectedData.value) { | ||
showLoading.start("正在导入数据...", `正在导入UID: ${item.uid}`); | ||
const dataFind = dataRaw.value.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); | ||
} | ||
showLoading.end(); | ||
showSnackbar.success("导入成功!"); | ||
return; | ||
} | ||
if (selectedData.value.length === 0) { | ||
showSnackbar.warn("请至少选择一个!"); | ||
return; | ||
} | ||
showLoading.start("正在导出数据...", "正在生成文件头"); | ||
const header = await getUigf4Header(); | ||
const data: TGApp.Plugins.UIGF.GachaHk4e[] = []; | ||
for (const item of selectedData.value) { | ||
showLoading.update("正在导出数据...", `正在导出UID: ${item.uid}`); | ||
const dataItem = await getUigf4Item(item.uid); | ||
data.push(dataItem); | ||
} | ||
showLoading.update("正在导出数据...", "正在生成文件..."); | ||
await writeTextFile(fp.value, JSON.stringify({ info: header, hk4e: data })); | ||
showLoading.end(); | ||
showSnackbar.success(`导出成功! 文件路径: ${fp.value}`); | ||
fp.value = await getDefaultSavePath(); | ||
} | ||
</script> | ||
<style lang="css" 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%; | ||
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; | ||
} | ||
.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 { | ||
font-family: var(--font-title); | ||
font-size: 16px; | ||
} | ||
.ugo-item-sub { | ||
font-size: 12px; | ||
} | ||
.ugo-item-btn { | ||
height: 40px; | ||
border: 1px solid var(--common-shadow-2); | ||
background: var(--btn-bg-1); | ||
color: var(--btn-text-1); | ||
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.