Merge pull request #19 from Aues6uen11Z/feat/api-url

feat: 添加用于小程序的API URL复制
This commit is contained in:
Aues6uen11Z
2026-01-09 22:09:13 +08:00
committed by GitHub
parent 176a7de220
commit 0e2a345371
6 changed files with 389 additions and 203 deletions

View File

@@ -13,7 +13,8 @@
"ui.button.startProxy": "Proxy mode", "ui.button.startProxy": "Proxy mode",
"ui.button.solution": "Solution", "ui.button.solution": "Solution",
"ui.button.cacheFolder": "Open cache folder", "ui.button.cacheFolder": "Open cache folder",
"ui.button.copyUrl": "Copy URL", "ui.button.copyUrl": "Copy Web URL",
"ui.button.copyApiUrl": "Copy API URL",
"ui.select.newAccount": "New account", "ui.select.newAccount": "New account",
"ui.hint.newAccount": "Export data from other accounts", "ui.hint.newAccount": "Export data from other accounts",
"ui.hint.init": "Please open your warp history inside the game client before clicking on the 'Load data' button", "ui.hint.init": "Please open your warp history inside the game client before clicking on the 'Load data' button",
@@ -104,6 +105,7 @@
"uigf.fileType": "Uniformed Interchangeable GachaLog Format v4.0, v4.1", "uigf.fileType": "Uniformed Interchangeable GachaLog Format v4.0, v4.1",
"ui.extra.cacheClean": "1. Confirm whether the search history in the game has been opened, and if the error \"User authentication expired\" still appears, try the following steps \n2. Close the game window of Zenless Zone Zero \n3. Click the \"Open Web Cache Folder\" button above to open the \"Cache\" folder \n4. Delete the \"Cache_Data\" folder \n5. Start the Zenless Zone Zero game and open the search history page in the game \n6. Close this dialog and click the \"Update Data\" button", "ui.extra.cacheClean": "1. Confirm whether the search history in the game has been opened, and if the error \"User authentication expired\" still appears, try the following steps \n2. Close the game window of Zenless Zone Zero \n3. Click the \"Open Web Cache Folder\" button above to open the \"Cache\" folder \n4. Delete the \"Cache_Data\" folder \n5. Start the Zenless Zone Zero game and open the search history page in the game \n6. Close this dialog and click the \"Update Data\" button",
"ui.extra.findCacheFolder": "If the \"Open cache folder\" button does not respond, you can manually find the game's web cache folder. The directory is \"Your game installation path/ZenlessZoneZero_Data/webCaches/Cache/\"", "ui.extra.findCacheFolder": "If the \"Open cache folder\" button does not respond, you can manually find the game's web cache folder. The directory is \"Your game installation path/ZenlessZoneZero_Data/webCaches/Cache/\"",
"ui.extra.urlCopied": "URL Copied", "ui.extra.urlCopied": "Web URL Copied",
"ui.extra.apiUrlCopied": "API URL Copied",
"ui.uigf.title": "Please select the UID(s) you want to export" "ui.uigf.title": "Please select the UID(s) you want to export"
} }

View File

@@ -13,7 +13,8 @@
"ui.button.startProxy": "代理模式", "ui.button.startProxy": "代理模式",
"ui.button.solution": "解决办法", "ui.button.solution": "解决办法",
"ui.button.cacheFolder": "打开网页缓存文件夹", "ui.button.cacheFolder": "打开网页缓存文件夹",
"ui.button.copyUrl": "复制URL", "ui.button.copyUrl": "复制网页链接",
"ui.button.copyApiUrl": "复制API链接",
"ui.select.newAccount": "新账号", "ui.select.newAccount": "新账号",
"ui.hint.newAccount": "从其它账号导出数据", "ui.hint.newAccount": "从其它账号导出数据",
"ui.hint.init": "请先在游戏里打开任意一个抽卡记录后再点击“加载数据”按钮", "ui.hint.init": "请先在游戏里打开任意一个抽卡记录后再点击“加载数据”按钮",
@@ -103,6 +104,7 @@
"uigf.fileType":"统一可交换抽卡记录标准 v4.0, v4.1", "uigf.fileType":"统一可交换抽卡记录标准 v4.0, v4.1",
"ui.extra.cacheClean": "1. 确认是否已经打开游戏内的抽卡历史记录,如果仍然出现“身份认证已过期”的错误,再尝试下面的步骤\n2. 关闭绝区零的游戏窗口\n3. 点击上方的“打开缓存文件夹”按钮打开Cache文件夹\n4. 删除Cache_Data文件夹\n5. 启动绝区零游戏,打开游戏内抽卡历史记录页面\n6. 关闭这个对话框,再点击“更新数据”按钮", "ui.extra.cacheClean": "1. 确认是否已经打开游戏内的抽卡历史记录,如果仍然出现“身份认证已过期”的错误,再尝试下面的步骤\n2. 关闭绝区零的游戏窗口\n3. 点击上方的“打开缓存文件夹”按钮打开Cache文件夹\n4. 删除Cache_Data文件夹\n5. 启动绝区零游戏,打开游戏内抽卡历史记录页面\n6. 关闭这个对话框,再点击“更新数据”按钮",
"ui.extra.findCacheFolder": "如果点“打开缓存文件夹”按钮没有反应,可以手动找到游戏的网页缓存文件夹,目录为“你的游戏安装路径/ZenlessZoneZero_Data/webCaches/Cache/”", "ui.extra.findCacheFolder": "如果点“打开缓存文件夹”按钮没有反应,可以手动找到游戏的网页缓存文件夹,目录为“你的游戏安装路径/ZenlessZoneZero_Data/webCaches/Cache/”",
"ui.extra.urlCopied": "URL已复制", "ui.extra.urlCopied": "网页链接已复制",
"ui.extra.apiUrlCopied": "API链接已复制",
"ui.uigf.title": "请选择要导出的UID" "ui.uigf.title": "请选择要导出的UID"
} }

View File

@@ -13,7 +13,8 @@
"ui.button.startProxy": "代理模式", "ui.button.startProxy": "代理模式",
"ui.button.solution": "解決辦法", "ui.button.solution": "解決辦法",
"ui.button.cacheFolder": "打開網頁緩存文件夾", "ui.button.cacheFolder": "打開網頁緩存文件夾",
"ui.button.copyUrl": "復製URL", "ui.button.copyUrl": "復製網頁鏈接",
"ui.button.copyApiUrl": "復製API鏈接",
"ui.select.newAccount": "新賬號", "ui.select.newAccount": "新賬號",
"ui.hint.newAccount": "從其它賬號導出數據", "ui.hint.newAccount": "從其它賬號導出數據",
"ui.hint.init": "請先在遊戲裏打開任意一個抽卡記錄後再點擊「加載數據」按鈕", "ui.hint.init": "請先在遊戲裏打開任意一個抽卡記錄後再點擊「加載數據」按鈕",
@@ -102,6 +103,7 @@
"uigf.fileType":"統一可交換抽卡記錄標準 v4.0, v4.1", "uigf.fileType":"統一可交換抽卡記錄標準 v4.0, v4.1",
"ui.extra.cacheClean": "1. 確認是否已經打開遊戲內的抽卡歷史記錄,如果仍然出現「身份認證已過期」的錯誤,再嘗試下面的步驟\n2. 關閉絕區零的遊戲窗口\n3. 點擊上方的「打開緩存文件夾」按鈕打開Cache文件夾\n4. 刪除Cache_Data文件夾\n5. 啟動絕區零遊戲,打開遊戲內抽卡歷史記錄頁面\n6. 關閉這個對話框,再點擊「更新數據」按鈕", "ui.extra.cacheClean": "1. 確認是否已經打開遊戲內的抽卡歷史記錄,如果仍然出現「身份認證已過期」的錯誤,再嘗試下面的步驟\n2. 關閉絕區零的遊戲窗口\n3. 點擊上方的「打開緩存文件夾」按鈕打開Cache文件夾\n4. 刪除Cache_Data文件夾\n5. 啟動絕區零遊戲,打開遊戲內抽卡歷史記錄頁面\n6. 關閉這個對話框,再點擊「更新數據」按鈕",
"ui.extra.findCacheFolder": "如果點「打開緩存文件夾」按鈕沒有反應,可以手動找到遊戲的網頁緩存文件夾,目錄為「你的遊戲安裝路徑/ZenlessZoneZero_Data/webCaches/Cache/」", "ui.extra.findCacheFolder": "如果點「打開緩存文件夾」按鈕沒有反應,可以手動找到遊戲的網頁緩存文件夾,目錄為「你的遊戲安裝路徑/ZenlessZoneZero_Data/webCaches/Cache/」",
"ui.extra.urlCopied": "URL已復製", "ui.extra.urlCopied": "網頁鏈接已復製",
"ui.extra.apiUrlCopied": "API鏈接已復製",
"ui.uigf.title": "請選擇要導出的UID" "ui.uigf.title": "請選擇要導出的UID"
} }

View File

@@ -1,5 +1,5 @@
const { clipboard, ipcMain } = require('electron') const { clipboard, ipcMain } = require('electron')
const { getUrl, deleteData } = require('./getData') const { getUrl, deleteData, getApiUrl } = require('./getData')
ipcMain.handle('COPY_URL', async () => { ipcMain.handle('COPY_URL', async () => {
const url = await getUrl() const url = await getUrl()
@@ -10,6 +10,15 @@ ipcMain.handle('COPY_URL', async () => {
return false return false
}) })
ipcMain.handle('COPY_API_URL', async () => {
const apiUrl = await getApiUrl()
if (apiUrl) {
clipboard.writeText(apiUrl)
return true
}
return false
})
ipcMain.handle('DELETE_DATA', async (event, uid, action) => { ipcMain.handle('DELETE_DATA', async (event, uid, action) => {
await deleteData(uid, action) await deleteData(uid, action)
}) })

View File

@@ -408,6 +408,17 @@ const getUrl = async () => {
return url return url
} }
const getApiUrl = async () => {
const url = await getUrl()
if (!url) return null
const searchParams = getQuerystring(url)
if (!searchParams) return null
const apiUrl = `${apiDomain}/common/gacha_record/api/getGachaLog?${searchParams.toString()}`
return apiUrl
}
const fetchData = async (urlOverride) => { const fetchData = async (urlOverride) => {
const text = i18n.log const text = i18n.log
await readData() await readData()
@@ -559,3 +570,4 @@ exports.deleteData = deleteData
exports.saveData = saveData exports.saveData = saveData
exports.changeCurrent = changeCurrent exports.changeCurrent = changeCurrent
exports.convertTimeZone = convertTimeZone exports.convertTimeZone = convertTimeZone
exports.getApiUrl = getApiUrl

View File

@@ -2,81 +2,221 @@
<div v-if="ui" class="relative"> <div v-if="ui" class="relative">
<div class="flex justify-between"> <div class="flex justify-between">
<div class="space-x-3"> <div class="space-x-3">
<el-button type="primary" :icon="state.status === 'init' ? 'milk-tea': 'refresh-right'" class="focus:outline-none" :disabled="!allowClick()" plain @click="fetchData()" :loading="state.status === 'loading'">{{state.status === 'init' ? ui.button.load: ui.button.update}}</el-button> <el-button
type="primary"
:icon="state.status === 'init' ? 'milk-tea' : 'refresh-right'"
class="focus:outline-none"
:disabled="!allowClick()"
plain
@click="fetchData()"
:loading="state.status === 'loading'"
>{{
state.status === "init" ? ui.button.load : ui.button.update
}}</el-button
>
<el-dropdown :disabled="!gachaData" @command="exportCommand"> <el-dropdown :disabled="!gachaData" @command="exportCommand">
<el-button :disabled="!gachaData" icon="folder-opened" class="focus:outline-none" type="success" plain> <el-button
{{ui.button.files}} :disabled="!gachaData"
icon="folder-opened"
class="focus:outline-none"
type="success"
plain
>
{{ ui.button.files }}
<el-icon class="el-icon--right"><arrow-down /></el-icon> <el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="excel">{{ui.button.excel}}</el-dropdown-item> <el-dropdown-item command="excel">{{
<el-dropdown-item command="uigf-json">{{ui.button.uigf}}</el-dropdown-item> ui.button.excel
<el-dropdown-item command="import-json" divided>{{ui.button.import}}</el-dropdown-item> }}</el-dropdown-item>
<el-dropdown-item command="uigf-json">{{
ui.button.uigf
}}</el-dropdown-item>
<el-dropdown-item command="import-json" divided>{{
ui.button.import
}}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<el-tooltip v-if="detail && state.status !== 'loading'" :content="ui.hint.newAccount" placement="bottom"> <el-tooltip
<el-button @click="newUser()" plain icon="plus" class="focus:outline-none"></el-button> v-if="detail && state.status !== 'loading'"
:content="ui.hint.newAccount"
placement="bottom"
>
<el-button
@click="newUser()"
plain
icon="plus"
class="focus:outline-none"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip v-if="state.status === 'updated'" :content="ui.hint.relaunchHint" placement="bottom"> <el-tooltip
<el-button @click="relaunch()" type="success" icon="refresh" class="focus:outline-none" style="margin-left: 48px">{{ui.button.directUpdate}}</el-button> v-if="state.status === 'updated'"
:content="ui.hint.relaunchHint"
placement="bottom"
>
<el-button
@click="relaunch()"
type="success"
icon="refresh"
class="focus:outline-none"
style="margin-left: 48px"
>{{ ui.button.directUpdate }}</el-button
>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<el-select v-if="state.status !== 'loading' && dataMap && (dataMap.size > 1 || (dataMap.size === 1 && state.current === 0))" class="w-44" @change="changeCurrent" v-model="uidSelectText"> <el-select
v-if="
state.status !== 'loading' &&
dataMap &&
(dataMap.size > 1 || (dataMap.size === 1 && state.current === 0))
"
class="w-44"
@change="changeCurrent"
v-model="uidSelectText"
>
<el-option <el-option
v-for="item of dataMap" v-for="item of dataMap"
:key="item[0]" :key="item[0]"
:label="maskUid(item[0])" :label="maskUid(item[0])"
:value="item[0]"> :value="item[0]"
>
</el-option> </el-option>
</el-select> </el-select>
<el-dropdown @command="optionCommand"> <el-dropdown @command="optionCommand">
<el-button @click="showSetting(true)" class="focus:outline-none" plain type="info" icon="more" >{{ui.button.option}}</el-button> <el-button
@click="showSetting(true)"
class="focus:outline-none"
plain
type="info"
icon="more"
>{{ ui.button.option }}</el-button
>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="setting" icon="setting">{{ui.button.setting}}</el-dropdown-item> <el-dropdown-item command="setting" icon="setting">{{
<el-dropdown-item :disabled="!allowClick() || state.status === 'loading'" command="url" icon="link">{{ui.button.url}}</el-dropdown-item> ui.button.setting
<el-dropdown-item command="copyUrl" icon="DocumentCopy">{{ui.button.copyUrl}}</el-dropdown-item> }}</el-dropdown-item>
<el-dropdown-item :disabled="!allowClick() || state.status === 'loading'" command="proxy" icon="position">{{ui.button.startProxy}}</el-dropdown-item> <el-dropdown-item
:disabled="!allowClick() || state.status === 'loading'"
command="url"
icon="link"
>{{ ui.button.url }}</el-dropdown-item
>
<el-dropdown-item command="copyUrl" icon="DocumentCopy">{{
ui.button.copyUrl
}}</el-dropdown-item>
<el-dropdown-item command="copyApiUrl" icon="Link">{{
ui.button.copyApiUrl
}}</el-dropdown-item>
<el-dropdown-item
:disabled="!allowClick() || state.status === 'loading'"
command="proxy"
icon="position"
>{{ ui.button.startProxy }}</el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</div> </div>
</div> </div>
<p class="text-gray-400 my-2 text-xs">{{hint}}<el-button @click="(state.showCacheCleanDlg=true)" v-if="state.authkeyTimeout" style="margin-left: 8px;" size="small" plain round>{{ui.button.solution}}</el-button></p> <p class="text-gray-400 my-2 text-xs">
<div v-if="detail" class="gap-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 2xl:grid-cols-4"> {{ hint
}}<el-button
@click="state.showCacheCleanDlg = true"
v-if="state.authkeyTimeout"
style="margin-left: 8px"
size="small"
plain
round
>{{ ui.button.solution }}</el-button
>
</p>
<div
v-if="detail"
class="gap-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 2xl:grid-cols-4"
>
<div class="mb-4" v-for="(item, i) of detail" :key="i"> <div class="mb-4" v-for="(item, i) of detail" :key="i">
<div :class="{hidden: state.config.hideNovice && item[0] === '2'}"> <div :class="{ hidden: state.config.hideNovice && item[0] === '2' }">
<p class="text-center text-gray-600 my-2">{{typeMap.get(item[0])}}</p> <p class="text-center text-gray-600 my-2">
<pie-chart :data="item" :i18n="state.i18n" :typeMap="typeMap"></pie-chart> {{ typeMap.get(item[0]) }}
<gacha-detail :i18n="state.i18n" :data="item" :typeMap="typeMap"></gacha-detail> </p>
<pie-chart
:data="item"
:i18n="state.i18n"
:typeMap="typeMap"
></pie-chart>
<gacha-detail
:i18n="state.i18n"
:data="item"
:typeMap="typeMap"
></gacha-detail>
</div> </div>
</div> </div>
</div> </div>
<Setting v-show="state.showSetting" :i18n="state.i18n" :gacha-data-info="dataInfo" @refreshData="readData()" @changeLang="getI18nData()" @close="showSetting(false)"></Setting> <Setting
v-show="state.showSetting"
:i18n="state.i18n"
:gacha-data-info="dataInfo"
@refreshData="readData()"
@changeLang="getI18nData()"
@close="showSetting(false)"
></Setting>
<el-dialog :title="ui.urlDialog.title" v-model="state.showUrlDlg" width="90%" class="max-w-md"> <el-dialog
<p class="mb-4 text-gray-500">{{ui.urlDialog.hint}}</p> :title="ui.urlDialog.title"
<el-input type="textarea" :autosize="{minRows: 4, maxRows: 6}" :placeholder="ui.urlDialog.placeholder" v-model="state.urlInput" spellcheck="false"></el-input> v-model="state.showUrlDlg"
width="90%"
class="max-w-md"
>
<p class="mb-4 text-gray-500">{{ ui.urlDialog.hint }}</p>
<el-input
type="textarea"
:autosize="{ minRows: 4, maxRows: 6 }"
:placeholder="ui.urlDialog.placeholder"
v-model="state.urlInput"
spellcheck="false"
></el-input>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="state.showUrlDlg = false" class="focus:outline-none">{{ui.common.cancel}}</el-button> <el-button
<el-button type="primary" @click="state.showUrlDlg = false, fetchData(state.urlInput)" class="focus:outline-none">{{ui.common.ok}}</el-button> @click="state.showUrlDlg = false"
class="focus:outline-none"
>{{ ui.common.cancel }}</el-button
>
<el-button
type="primary"
@click="(state.showUrlDlg = false), fetchData(state.urlInput)"
class="focus:outline-none"
>{{ ui.common.ok }}</el-button
>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog :title="ui.button.solution" v-model="state.showCacheCleanDlg" width="90%" class="max-w-md cache-clean-dialog"> <el-dialog
<el-button plain icon="folder" type="success" @click="openCacheFolder">{{ui.button.cacheFolder}}</el-button> :title="ui.button.solution"
v-model="state.showCacheCleanDlg"
width="90%"
class="max-w-md cache-clean-dialog"
>
<el-button plain icon="folder" type="success" @click="openCacheFolder">{{
ui.button.cacheFolder
}}</el-button>
<p class="my-2 flex flex-col text-teal-800 text-[13px]"> <p class="my-2 flex flex-col text-teal-800 text-[13px]">
<span class="my-1" v-for="txt of cacheCleanTextList">{{ txt }}</span> <span class="my-1" v-for="txt of cacheCleanTextList">{{ txt }}</span>
</p> </p>
<p class="my-2 text-gray-500 text-xs">{{ui.extra.findCacheFolder}}</p> <p class="my-2 text-gray-500 text-xs">{{ ui.extra.findCacheFolder }}</p>
<template #footer> <template #footer>
<div class="dialog-footer text-center"> <div class="dialog-footer text-center">
<el-button type="primary" @click="state.showCacheCleanDlg = false" class="focus:outline-none">{{ui.common.ok}}</el-button> <el-button
type="primary"
@click="state.showCacheCleanDlg = false"
class="focus:outline-none"
>{{ ui.common.ok }}</el-button
>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -84,19 +224,19 @@
</template> </template>
<script setup> <script setup>
const { ipcRenderer } = require('electron') const { ipcRenderer } = require("electron");
import { reactive, computed, watch, onMounted } from 'vue' import { reactive, computed, watch, onMounted } from "vue";
import PieChart from './components/PieChart.vue' import PieChart from "./components/PieChart.vue";
import GachaDetail from './components/GachaDetail.vue' import GachaDetail from "./components/GachaDetail.vue";
import Setting from './components/Setting.vue' import Setting from "./components/Setting.vue";
import gachaDetail from './gachaDetail' import gachaDetail from "./gachaDetail";
import { version } from '../../package.json' import { version } from "../../package.json";
import gachaType from '../gachaType.json' import gachaType from "../gachaType.json";
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from "element-plus";
const state = reactive({ const state = reactive({
status: 'init', status: "init",
log: '', log: "",
data: null, data: null,
dataMap: new Map(), dataMap: new Map(),
current: 0, current: 0,
@@ -104,157 +244,163 @@ const state = reactive({
i18n: null, i18n: null,
showUrlDlg: false, showUrlDlg: false,
showCacheCleanDlg: false, showCacheCleanDlg: false,
urlInput: '', urlInput: "",
authkeyTimeout: false, authkeyTimeout: false,
config: {} config: {},
}) });
const dataMap = computed(() => { const dataMap = computed(() => {
const result = new Map() const result = new Map();
for (let [uid, data] of state.dataMap) { for (let [uid, data] of state.dataMap) {
if (!data.deleted) { if (!data.deleted) {
result.set(uid, data) result.set(uid, data);
} }
} }
return result return result;
}) });
const dataInfo = computed(() => { const dataInfo = computed(() => {
const result = [] const result = [];
for (let [uid, data] of state.dataMap) { for (let [uid, data] of state.dataMap) {
result.push({ result.push({
uid, time: data.time, deleted: data.deleted uid,
}) time: data.time,
deleted: data.deleted,
});
} }
return result return result;
}) });
const ui = computed(() => { const ui = computed(() => {
if (state.i18n) { if (state.i18n) {
return state.i18n.ui return state.i18n.ui;
} }
}) });
const cacheCleanTextList = computed(() => { const cacheCleanTextList = computed(() => {
if (ui.value) { if (ui.value) {
return ui.value.extra?.cacheClean?.split('\n') return ui.value.extra?.cacheClean?.split("\n");
} }
return [] return [];
}) });
const gachaData = computed(() => { const gachaData = computed(() => {
return state.dataMap.get(state.current) return state.dataMap.get(state.current);
}) });
const uidSelectText = computed(() => { const uidSelectText = computed(() => {
if (state.current === 0) { if (state.current === 0) {
return state.i18n.ui.select.newAccount return state.i18n.ui.select.newAccount;
} else { } else {
return state.current return state.current;
} }
}) });
const allowClick = () => { const allowClick = () => {
const data = state.dataMap.get(state.current) const data = state.dataMap.get(state.current);
if (!data) return true if (!data) return true;
if (Date.now() - data.time < 1000 * 10) { if (Date.now() - data.time < 1000 * 10) {
return false return false;
} }
return true return true;
} };
const hint = computed(() => { const hint = computed(() => {
const data = state.dataMap.get(state.current) const data = state.dataMap.get(state.current);
if (!state.i18n) { if (!state.i18n) {
return 'Loading...' return "Loading...";
} }
const { hint } = state.i18n.ui const { hint } = state.i18n.ui;
const { colon } = state.i18n.symbol const { colon } = state.i18n.symbol;
if (state.status === 'init') { if (state.status === "init") {
return hint.init return hint.init;
} else if (state.status === 'loaded') { } else if (state.status === "loaded") {
return `${hint.lastUpdate}${colon}${new Date(data.time).toLocaleString()}` return `${hint.lastUpdate}${colon}${new Date(data.time).toLocaleString()}`;
} else if (state.status === 'loading') { } else if (state.status === "loading") {
return state.log || 'Loading...' return state.log || "Loading...";
} else if (state.status === 'updated') { } else if (state.status === "updated") {
return state.log return state.log;
} else if (state.status === 'failed') { } else if (state.status === "failed") {
return state.log + ` - ${hint.failed}` return state.log + ` - ${hint.failed}`;
} }
return ' ' return " ";
}) });
const detail = computed(() => { const detail = computed(() => {
const data = dataMap.value.get(state.current) const data = dataMap.value.get(state.current);
if (data) { if (data) {
return gachaDetail(data.result) return gachaDetail(data.result);
} }
}) });
const typeMap = computed(() => { const typeMap = computed(() => {
const gachaTypeMap = new Map(gachaType) const gachaTypeMap = new Map(gachaType);
const type = gachaTypeMap.get(state.config.lang) const type = gachaTypeMap.get(state.config.lang);
const result = new Map() const result = new Map();
if (type) { if (type) {
for (let { key, name } of type) { for (let { key, name } of type) {
result.set(key, name) result.set(key, name);
} }
} }
return result return result;
}) });
const fetchData = async (url) => { const fetchData = async (url) => {
state.log = '' state.log = "";
state.status = 'loading' state.status = "loading";
const data = await ipcRenderer.invoke('FETCH_DATA', url) const data = await ipcRenderer.invoke("FETCH_DATA", url);
if (data) { if (data) {
state.dataMap = data.dataMap state.dataMap = data.dataMap;
state.current = data.current state.current = data.current;
state.status = 'loaded' state.status = "loaded";
} else { } else {
state.status = 'failed' state.status = "failed";
} }
} };
const readData = async () => { const readData = async () => {
const data = await ipcRenderer.invoke('READ_DATA') const data = await ipcRenderer.invoke("READ_DATA");
if (data) { if (data) {
state.dataMap = data.dataMap state.dataMap = data.dataMap;
state.current = data.current state.current = data.current;
if (data.dataMap.get(data.current)) { if (data.dataMap.get(data.current)) {
state.status = 'loaded' state.status = "loaded";
} }
} }
} };
const getI18nData = async () => { const getI18nData = async () => {
const data = await ipcRenderer.invoke('I18N_DATA') const data = await ipcRenderer.invoke("I18N_DATA");
if (data) { if (data) {
state.i18n = data state.i18n = data;
setTitle() setTitle();
} }
} };
const saveExcel = async () => { const saveExcel = async () => {
await ipcRenderer.invoke('SAVE_EXCEL') await ipcRenderer.invoke("SAVE_EXCEL");
} };
const exportUIGFJSON = () => { const exportUIGFJSON = () => {
let uidList = [] let uidList = [];
dataMap.value.forEach(item => { dataMap.value.forEach((item) => {
uidList.push(item.uid) uidList.push(item.uid);
}) });
ElMessageBox({ ElMessageBox({
title: state.i18n.ui.uigf.title, title: state.i18n.ui.uigf.title,
message: ` message: `
<div> <div>
${uidList.map(uid => ` ${uidList
.map(
(uid) => `
<div> <div>
<input type="checkbox" id="${uid}" value="${uid}" /> <input type="checkbox" id="${uid}" value="${uid}" />
<label for="${uid}">${uid}</label> <label for="${uid}">${uid}</label>
</div> </div>
`).join('')} `
)
.join("")}
</div> </div>
`, `,
dangerouslyUseHTMLString: true, dangerouslyUseHTMLString: true,
@@ -262,124 +408,137 @@ const exportUIGFJSON = () => {
confirmButtonText: state.i18n.ui.common.ok, confirmButtonText: state.i18n.ui.common.ok,
cancelButtonText: state.i18n.ui.common.cancel, cancelButtonText: state.i18n.ui.common.cancel,
beforeClose: (action, instance, done) => { beforeClose: (action, instance, done) => {
if (action === 'confirm') { if (action === "confirm") {
const selected_uids = uidList.filter(uid => document.getElementById(uid).checked); const selected_uids = uidList.filter(
ipcRenderer.invoke('EXPORT_UIGF_JSON', selected_uids); (uid) => document.getElementById(uid).checked
);
ipcRenderer.invoke("EXPORT_UIGF_JSON", selected_uids);
} }
done(); done();
} },
}).then(() => { })
}).catch(() => { .then(() => {})
}); .catch(() => {});
} };
const importData = async () => { const importData = async () => {
state.status = 'loading' state.status = "loading";
const data = await ipcRenderer.invoke('IMPORT_UIGF_JSON') const data = await ipcRenderer.invoke("IMPORT_UIGF_JSON");
if (data) { if (data) {
state.dataMap = data.dataMap state.dataMap = data.dataMap;
state.current = data.current state.current = data.current;
state.status = 'loaded' state.status = "loaded";
} else { } else {
state.status = 'failed' state.status = "failed";
} }
} };
const exportCommand = (type) => { const exportCommand = (type) => {
if (type === 'excel') { if (type === "excel") {
saveExcel() saveExcel();
} else if (type === 'uigf-json') { } else if (type === "uigf-json") {
exportUIGFJSON() exportUIGFJSON();
} else if (type === 'import-json') { } else if (type === "import-json") {
importData() importData();
} }
} };
const openCacheFolder = async () => { const openCacheFolder = async () => {
await ipcRenderer.invoke('OPEN_CACHE_FOLDER') await ipcRenderer.invoke("OPEN_CACHE_FOLDER");
} };
const changeCurrent = async (uid) => { const changeCurrent = async (uid) => {
if (uid === 0) { if (uid === 0) {
state.status = 'init' state.status = "init";
} else { } else {
state.status = 'loaded' state.status = "loaded";
} }
state.current = uid state.current = uid;
await ipcRenderer.invoke('CHANGE_UID', uid) await ipcRenderer.invoke("CHANGE_UID", uid);
} };
const newUser = async () => { const newUser = async () => {
await changeCurrent(0) await changeCurrent(0);
} };
const relaunch = async () => { const relaunch = async () => {
await ipcRenderer.invoke('RELAUNCH') await ipcRenderer.invoke("RELAUNCH");
} };
const maskUid = (uid) => { const maskUid = (uid) => {
return `${uid}`.replace(/(.{3})(.+)(.{3})$/, '$1***$3') return `${uid}`.replace(/(.{3})(.+)(.{3})$/, "$1***$3");
} };
const showSetting = (show) => { const showSetting = (show) => {
if (show) { if (show) {
state.showSetting = true state.showSetting = true;
} else { } else {
state.showSetting = false state.showSetting = false;
updateConfig() updateConfig();
} }
} };
const optionCommand = (type) => { const optionCommand = (type) => {
if (type === 'setting') { if (type === "setting") {
showSetting(true) showSetting(true);
} else if (type === 'url') { } else if (type === "url") {
state.urlInput = '' state.urlInput = "";
state.showUrlDlg = true state.showUrlDlg = true;
} else if (type === 'proxy') { } else if (type === "proxy") {
fetchData('proxy') fetchData("proxy");
} else if (type === 'copyUrl') { } else if (type === "copyUrl") {
copyUrl() copyUrl();
} else if (type === "copyApiUrl") {
copyApiUrl();
} }
} };
const setTitle = () => { const setTitle = () => {
document.title = `${state.i18n.ui.win.title} - v${version}` document.title = `${state.i18n.ui.win.title} - v${version}`;
} };
const updateConfig = async () => { const updateConfig = async () => {
state.config = await ipcRenderer.invoke('GET_CONFIG') state.config = await ipcRenderer.invoke("GET_CONFIG");
} };
const copyUrl = async () => { const copyUrl = async () => {
const successed = await ipcRenderer.invoke('COPY_URL') const successed = await ipcRenderer.invoke("COPY_URL");
if (successed) { if (successed) {
ElMessage.success(ui.value.extra.urlCopied) ElMessage.success(ui.value.extra.urlCopied);
} else { } else {
ElMessage.error(state.i18n.log.url.notFound) ElMessage.error(state.i18n.log.url.notFound);
} }
} };
const copyApiUrl = async () => {
const successed = await ipcRenderer.invoke("COPY_API_URL");
if (successed) {
ElMessage.success(ui.value.extra.apiUrlCopied);
} else {
ElMessage.error(state.i18n.log.url.notFound);
}
};
onMounted(async () => { onMounted(async () => {
await readData() await readData();
await getI18nData() await getI18nData();
ipcRenderer.on('LOAD_DATA_STATUS', (event, message) => { ipcRenderer.on("LOAD_DATA_STATUS", (event, message) => {
state.log = message state.log = message;
}) });
ipcRenderer.on('ERROR', (event, err) => { ipcRenderer.on("ERROR", (event, err) => {
console.error(err) console.error(err);
}) });
ipcRenderer.on('UPDATE_HINT', (event, message) => { ipcRenderer.on("UPDATE_HINT", (event, message) => {
state.log = message state.log = message;
state.status = 'updated' state.status = "updated";
}) });
ipcRenderer.on('AUTHKEY_TIMEOUT', (event, message) => { ipcRenderer.on("AUTHKEY_TIMEOUT", (event, message) => {
state.authkeyTimeout = message state.authkeyTimeout = message;
}) });
await updateConfig() await updateConfig();
}) });
</script> </script>