mirror of
https://github.com/earthjasonlin/zzz-signal-search-export.git
synced 2025-04-21 16:00:17 +08:00
Departure commit
This commit is contained in:
268
src/renderer/App.vue
Normal file
268
src/renderer/App.vue
Normal file
@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div v-if="ui" class="relative">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<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 icon="folder-opened" @click="saveExcel" class="focus:outline-none" :disabled="!gachaData" type="success" plain>{{ui.button.excel}}</el-button>
|
||||
<el-tooltip 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 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>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<el-select v-if="state.status !== 'loading' && state.dataMap && (state.dataMap.size > 1 || (state.dataMap.size === 1 && state.current === 0))" class="w-44" @change="changeCurrent" v-model="uidSelectText">
|
||||
<el-option
|
||||
v-for="item of state.dataMap"
|
||||
:key="item[0]"
|
||||
:label="maskUid(item[0])"
|
||||
:value="item[0]">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-dropdown @command="optionCommand" >
|
||||
<el-button @click="showSetting(true)" class="focus:outline-none" plain type="info" icon="more" >{{ui.button.option}}</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="setting" icon="setting">{{ui.button.setting}}</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!allowClick() || state.status === 'loading'" command="url" icon="link">{{ui.button.url}}</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!allowClick() || state.status === 'loading'" command="proxy" icon="position">{{ui.button.startProxy}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</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>
|
||||
<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="{hidden: state.config.hideNovice && item[0] === '2'}">
|
||||
<p class="text-center text-gray-600 my-2">{{typeMap.get(item[0])}}</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>
|
||||
<Setting v-show="state.showSetting" :i18n="state.i18n" @changeLang="getI18nData()" @close="showSetting(false)"></Setting>
|
||||
|
||||
<el-dialog :title="ui.urlDialog.title" v-model="state.showUrlDlg" width="90%" custom-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>
|
||||
<span class="dialog-footer">
|
||||
<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>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="ui.button.solution" v-model="state.showCacheCleanDlg" width="90%" custom-class="max-w-md">
|
||||
<el-button plain icon="folder" type="primary" @click="openCacheFolder">{{ui.button.cacheFolder}}</el-button>
|
||||
<p class="my-4 leading-2 text-gray-600 text-sm whitespace-pre-line">{{ui.extra.cacheClean}}</p>
|
||||
<p class="my-2 text-gray-400 text-xs">{{ui.extra.findCacheFolder}}</p>
|
||||
<template #footer>
|
||||
<div class="dialog-footer text-center">
|
||||
<el-button type="primary" @click="state.showCacheCleanDlg = false" class="focus:outline-none">{{ui.common.ok}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { ipcRenderer } = require('electron')
|
||||
import { reactive, computed, watch, onMounted } from 'vue'
|
||||
import PieChart from './components/PieChart.vue'
|
||||
import GachaDetail from './components/GachaDetail.vue'
|
||||
import Setting from './components/Setting.vue'
|
||||
import gachaDetail from './gachaDetail'
|
||||
import { version } from '../../package.json'
|
||||
|
||||
const state = reactive({
|
||||
status: 'init',
|
||||
log: '',
|
||||
data: null,
|
||||
dataMap: new Map(),
|
||||
current: 0,
|
||||
showSetting: false,
|
||||
i18n: null,
|
||||
showUrlDlg: false,
|
||||
showCacheCleanDlg: false,
|
||||
urlInput: '',
|
||||
authkeyTimeout: false,
|
||||
config: {}
|
||||
})
|
||||
|
||||
const ui = computed(() => {
|
||||
if (state.i18n) {
|
||||
return state.i18n.ui
|
||||
}
|
||||
})
|
||||
|
||||
const gachaData = computed(() => {
|
||||
return state.dataMap.get(state.current)
|
||||
})
|
||||
|
||||
const uidSelectText = computed(() => {
|
||||
if (state.current === 0) {
|
||||
return state.i18n.ui.select.newAccount
|
||||
} else {
|
||||
return state.current
|
||||
}
|
||||
})
|
||||
|
||||
const allowClick = () => {
|
||||
const data = state.dataMap.get(state.current)
|
||||
if (!data) return true
|
||||
if (Date.now() - data.time < 1000 * 60) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const hint = computed(() => {
|
||||
const data = state.dataMap.get(state.current)
|
||||
if (!state.i18n) {
|
||||
return 'Loading...'
|
||||
}
|
||||
const { hint } = state.i18n.ui
|
||||
const { colon } = state.i18n.symbol
|
||||
if (state.status === 'init') {
|
||||
return hint.init
|
||||
} else if (state.status === 'loaded') {
|
||||
return `${hint.lastUpdate}${colon}${new Date(data.time).toLocaleString()}`
|
||||
} else if (state.status === 'loading') {
|
||||
return state.log || 'Loading...'
|
||||
} else if (state.status === 'updated') {
|
||||
return state.log
|
||||
} else if (state.status === 'failed') {
|
||||
return state.log + ` - ${hint.failed}`
|
||||
}
|
||||
return ' '
|
||||
})
|
||||
|
||||
const detail = computed(() => {
|
||||
const data = state.dataMap.get(state.current)
|
||||
if (data) {
|
||||
return gachaDetail(data.result)
|
||||
}
|
||||
})
|
||||
|
||||
const typeMap = computed(() => {
|
||||
const data = state.dataMap.get(state.current)
|
||||
return data.typeMap
|
||||
})
|
||||
|
||||
const fetchData = async (url) => {
|
||||
state.status = 'loading'
|
||||
const data = await ipcRenderer.invoke('FETCH_DATA', url)
|
||||
if (data) {
|
||||
state.dataMap = data.dataMap
|
||||
state.current = data.current
|
||||
state.status = 'loaded'
|
||||
} else {
|
||||
state.status = 'failed'
|
||||
}
|
||||
}
|
||||
|
||||
const readData = async () => {
|
||||
const data = await ipcRenderer.invoke('READ_DATA')
|
||||
if (data) {
|
||||
state.dataMap = data.dataMap
|
||||
state.current = data.current
|
||||
if (data.dataMap.get(data.current)) {
|
||||
state.status = 'loaded'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getI18nData = async () => {
|
||||
const data = await ipcRenderer.invoke('I18N_DATA')
|
||||
if (data) {
|
||||
state.i18n = data
|
||||
setTitle()
|
||||
}
|
||||
}
|
||||
|
||||
const saveExcel = async () => {
|
||||
await ipcRenderer.invoke('SAVE_EXCEL')
|
||||
}
|
||||
|
||||
const openCacheFolder = async () => {
|
||||
await ipcRenderer.invoke('OPEN_CACHE_FOLDER')
|
||||
}
|
||||
|
||||
const changeCurrent = async (uid) => {
|
||||
if (uid === 0) {
|
||||
state.status = 'init'
|
||||
} else {
|
||||
state.status = 'loaded'
|
||||
}
|
||||
state.current = uid
|
||||
await ipcRenderer.invoke('CHANGE_UID', uid)
|
||||
}
|
||||
|
||||
const newUser = async () => {
|
||||
await changeCurrent(0)
|
||||
}
|
||||
|
||||
const relaunch = async () => {
|
||||
await ipcRenderer.invoke('RELAUNCH')
|
||||
}
|
||||
|
||||
const maskUid = (uid) => {
|
||||
return `${uid}`.replace(/(.{3})(.+)(.{3})$/, '$1***$3')
|
||||
}
|
||||
|
||||
const showSetting = (show) => {
|
||||
if (show) {
|
||||
state.showSetting = true
|
||||
} else {
|
||||
state.showSetting = false
|
||||
updateConfig()
|
||||
}
|
||||
}
|
||||
|
||||
const optionCommand = (type) => {
|
||||
if (type === 'setting') {
|
||||
showSetting(true)
|
||||
} else if (type === 'url') {
|
||||
state.urlInput = ''
|
||||
state.showUrlDlg = true
|
||||
} else if (type === 'proxy') {
|
||||
fetchData('proxy')
|
||||
}
|
||||
}
|
||||
|
||||
const setTitle = () => {
|
||||
document.title = `${state.i18n.ui.win.title} - v${version}`
|
||||
}
|
||||
|
||||
const updateConfig = async () => {
|
||||
state.config = await ipcRenderer.invoke('GET_CONFIG')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await readData()
|
||||
await getI18nData()
|
||||
|
||||
ipcRenderer.on('LOAD_DATA_STATUS', (event, message) => {
|
||||
state.log = message
|
||||
})
|
||||
|
||||
ipcRenderer.on('ERROR', (event, err) => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
ipcRenderer.on('UPDATE_HINT', (event, message) => {
|
||||
state.log = message
|
||||
state.status = 'updated'
|
||||
})
|
||||
|
||||
ipcRenderer.on('AUTHKEY_TIMEOUT', (event, message) => {
|
||||
state.authkeyTimeout = message
|
||||
})
|
||||
|
||||
await updateConfig()
|
||||
})
|
||||
</script>
|
91
src/renderer/components/GachaDetail.vue
Normal file
91
src/renderer/components/GachaDetail.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<p class="text-gray-500 text-xs mb-2 text-center whitespace-nowrap">
|
||||
<span class="mx-2" :title="new Date(detail.date[0]).toLocaleString()">{{new Date(detail.date[0]).toLocaleDateString()}}</span>
|
||||
-
|
||||
<span class="mx-2" :title="new Date(detail.date[1]).toLocaleString()">{{new Date(detail.date[1]).toLocaleDateString()}}</span>
|
||||
</p>
|
||||
<p class="text-gray-600 text-xs mb-1">
|
||||
<span class="mr-1">{{text.total}}
|
||||
<span class="text-blue-600">{{detail.total}}</span> {{text.times}}
|
||||
</span>
|
||||
<span v-if="type !== '100'">{{text.sum}}<span class="mx-1 text-green-600">{{detail.countMio}}</span>{{text.no5star}}</span>
|
||||
</p>
|
||||
<p class="text-gray-600 text-xs mb-1">
|
||||
<span :title="`${text.character}${colon}${detail.count5c}\n${text.weapon}${colon}${detail.count5w}`" class="mr-3 whitespace-pre cursor-help text-yellow-500">
|
||||
<span class="min-w-10 inline-block">{{text.star5}}{{colon}}{{detail.count5}}</span>
|
||||
[{{percent(detail.count5, detail.total)}}]
|
||||
</span>
|
||||
<br><span :title="`${text.character}${colon}${detail.count4c}\n${text.weapon}${colon}${detail.count4w}`" class="mr-3 whitespace-pre cursor-help text-purple-600">
|
||||
<span class="min-w-10 inline-block">{{text.star4}}{{colon}}{{detail.count4}}</span>
|
||||
[{{percent(detail.count4, detail.total)}}]
|
||||
</span>
|
||||
<br><span class="text-blue-500 whitespace-pre">
|
||||
<span class="min-w-10 inline-block">{{text.star3}}{{colon}}{{detail.count3}}</span>
|
||||
[{{percent(detail.count3, detail.total)}}]
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p class="text-gray-600 text-xs mb-1" v-if="detail.ssrPos.length">
|
||||
{{text.history}}{{colon}}
|
||||
<span :title="`${item[2]}${item[3] === '400' ? '\n' + props.i18n.excel.wish2 : ''}`" :class="{wish2: item[3] === '400'}" class="cursor-help mr-1" :style="`color:${colorList[index]}`"
|
||||
v-for="(item, index) of detail.ssrPos" :key="item"
|
||||
>
|
||||
{{item[0]}}[{{item[1]}}]
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="detail.ssrPos.length" class="text-gray-600 text-xs">{{text.average}}{{colon}}<span class="text-green-600">{{avg5(detail.ssrPos)}}</span></p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
typeMap: Map,
|
||||
i18n: Object
|
||||
})
|
||||
|
||||
const type = computed(() => props.data[0])
|
||||
const detail = computed(() => props.data[1])
|
||||
const text = computed(() => props.i18n.ui.data)
|
||||
const colon = computed(() => props.i18n.symbol.colon)
|
||||
|
||||
const avg5 = (list) => {
|
||||
let n = 0
|
||||
list.forEach(item => {
|
||||
n += item[1]
|
||||
})
|
||||
return parseInt((n / list.length) * 100) / 100
|
||||
}
|
||||
|
||||
const percent = (num, total) => {
|
||||
return `${Math.round(num / total * 10000) / 100}%`
|
||||
}
|
||||
|
||||
const colors = [
|
||||
'#5470c6', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#2ab7ca',
|
||||
'#005b96', '#ff8b94', '#72a007','#b60d1b', '#16570d'
|
||||
]
|
||||
|
||||
const colorList = computed(() => {
|
||||
let colorsTemp = [...colors]
|
||||
const result = []
|
||||
const map = new Map()
|
||||
props.data[1].ssrPos.forEach(item => {
|
||||
if (map.has(item[0])) {
|
||||
return result.push(map.get(item[0]))
|
||||
}
|
||||
const num = Math.abs(hashCode(`${Math.floor(Date.now() / (1000 * 60 * 10))}-${item[0]}`))
|
||||
if (!colorsTemp.length) colorsTemp = [...colors]
|
||||
const color = colorsTemp.splice(num % colorsTemp.length, 1)[0]
|
||||
map.set(item[0], color)
|
||||
result.push(color)
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
function hashCode(str) {
|
||||
return Array.from(str)
|
||||
.reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0)
|
||||
}
|
||||
</script>
|
125
src/renderer/components/PieChart.vue
Normal file
125
src/renderer/components/PieChart.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="chart mb-2 relative h-48 lg:h-56 xl:h-64 2xl:h-72">
|
||||
<div ref="chart" class="absolute inset-0"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed, ref, onMounted, onUpdated } from "vue";
|
||||
import { use, init } from "echarts/core";
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
} from "echarts/components";
|
||||
import { PieChart } from "echarts/charts";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import throttle from "lodash-es/throttle";
|
||||
|
||||
use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
typeMap: Map,
|
||||
i18n: Object,
|
||||
});
|
||||
|
||||
const chart = ref(null);
|
||||
|
||||
const colors = ["#fac858", "#ee6666", "#5470c6", "#91cc75", "#73c0de"];
|
||||
|
||||
const parseData = (detail, type) => {
|
||||
const text = props.i18n.ui.data;
|
||||
const keys = [
|
||||
[text.chara5, "count5c"],
|
||||
[text.weapon5, "count5w"],
|
||||
[text.chara4, "count4c"],
|
||||
[text.weapon4, "count4w"],
|
||||
[text.weapon3, "count3w"],
|
||||
];
|
||||
const result = [];
|
||||
const color = [];
|
||||
const selected = {
|
||||
[text.weapon3]: false,
|
||||
};
|
||||
keys.forEach((key, index) => {
|
||||
if (!detail[key[1]]) return;
|
||||
result.push({
|
||||
value: detail[key[1]],
|
||||
name: key[0],
|
||||
});
|
||||
color.push(colors[index]);
|
||||
});
|
||||
if (
|
||||
type === "100" ||
|
||||
result.findIndex((item) => item.name.includes("5")) === -1
|
||||
) {
|
||||
selected[text.weapon3] = true;
|
||||
}
|
||||
return [result, color, selected];
|
||||
};
|
||||
|
||||
let pieChart = null;
|
||||
const updateChart = throttle(() => {
|
||||
if (!pieChart) {
|
||||
pieChart = init(chart.value);
|
||||
}
|
||||
|
||||
const colon = props.i18n.symbol.colon;
|
||||
const result = parseData(props.data[1], props.data[0]);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: `{b0}${colon}{c0}`,
|
||||
padding: 4,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: "2%",
|
||||
left: "center",
|
||||
selected: result[2],
|
||||
},
|
||||
selectedMode: "single",
|
||||
color: result[1],
|
||||
series: [
|
||||
{
|
||||
name: props.typeMap.get(props.data[0]),
|
||||
type: "pie",
|
||||
top: 50,
|
||||
startAngle: 70,
|
||||
radius: ["0%", "90%"],
|
||||
// avoidLabelOverlap: false,
|
||||
labelLine: {
|
||||
length: 0,
|
||||
length2: 10,
|
||||
},
|
||||
label: {
|
||||
overflow: "break",
|
||||
},
|
||||
data: result[0],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
pieChart.setOption(option);
|
||||
pieChart.resize();
|
||||
}, 1000);
|
||||
|
||||
onUpdated(() => {
|
||||
updateChart();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
updateChart();
|
||||
window.addEventListener("resize", updateChart);
|
||||
});
|
||||
</script>
|
129
src/renderer/components/Setting.vue
Normal file
129
src/renderer/components/Setting.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="bg-white pt-2 pb-4 px-6 w-full h-full absolute inset-0">
|
||||
<div class="flex content-center items-center mb-4 justify-between">
|
||||
<h3 class="text-lg">{{text.title}}</h3>
|
||||
<el-button icon="close" @click="closeSetting" plain circle type="default" class="w-8 h-8 relative -right-4 -top-2 shadow-md focus:shadow-none focus:outline-none"></el-button>
|
||||
</div>
|
||||
<el-form :model="settingForm" label-width="120px">
|
||||
<el-form-item :label="text.language">
|
||||
<el-select @change="saveLang" v-model="settingForm.lang">
|
||||
<el-option v-for="item of data.langMap" :key="item[0]" :label="item[1]" :value="item[0]"></el-option>
|
||||
</el-select>
|
||||
<p class="text-gray-400 text-xs m-1.5">{{text.languageHint}}</p>
|
||||
</el-form-item>
|
||||
<el-form-item :label="text.logType">
|
||||
<el-radio-group @change="saveSetting" v-model.number="settingForm.logType">
|
||||
<el-radio-button :label="0">{{text.auto}}</el-radio-button>
|
||||
<el-radio-button :label="1">{{text.cnServer}}</el-radio-button>
|
||||
<el-radio-button :label="2">{{text.seaServer}}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<p class="text-gray-400 text-xs m-1.5">{{text.logTypeHint}}</p>
|
||||
</el-form-item>
|
||||
<el-form-item :label="text.autoUpdate">
|
||||
<el-switch
|
||||
@change="saveSetting"
|
||||
v-model="settingForm.autoUpdate">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="text.hideNovice">
|
||||
<el-switch
|
||||
@change="saveSetting"
|
||||
v-model="settingForm.hideNovice">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="text.fetchFullHistory">
|
||||
<el-switch
|
||||
@change="saveSetting"
|
||||
v-model="settingForm.fetchFullHistory">
|
||||
</el-switch>
|
||||
<p class="text-gray-400 text-xs m-1.5">{{text.fetchFullHistoryHint}}</p>
|
||||
</el-form-item>
|
||||
<el-form-item :label="text.proxyMode">
|
||||
<el-switch
|
||||
@change="saveSetting"
|
||||
v-model="settingForm.proxyMode">
|
||||
</el-switch>
|
||||
<p class="text-gray-400 text-xs m-1.5">{{text.proxyModeHint}}</p>
|
||||
<el-button class="focus:outline-none" @click="disableProxy">{{text.closeProxy}}</el-button>
|
||||
<p class="text-gray-400 text-xs m-1.5">{{text.closeProxyHint}}</p>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<h3 class="text-lg my-4">{{about.title}}</h3>
|
||||
<p class="text-gray-600 text-xs mt-1">{{about.license}}</p>
|
||||
<p class="text-gray-600 text-xs mt-1 pb-6">Github: <a @click="openGithub" class="cursor-pointer text-blue-400">https://github.com/biuuu/star-rail-warp-export</a></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { ipcRenderer, shell } = require('electron')
|
||||
import { reactive, onMounted, computed } from 'vue'
|
||||
|
||||
const emit = defineEmits(['close', 'changeLang'])
|
||||
|
||||
const props = defineProps({
|
||||
i18n: Object
|
||||
})
|
||||
|
||||
const data = reactive({
|
||||
langMap: new Map()
|
||||
})
|
||||
|
||||
const settingForm = reactive({
|
||||
lang: 'zh-cn',
|
||||
logType: 1,
|
||||
proxyMode: true,
|
||||
autoUpdate: true,
|
||||
fetchFullHistory: false,
|
||||
hideNovice: true
|
||||
})
|
||||
|
||||
const text = computed(() => props.i18n.ui.setting)
|
||||
const about = computed(() => props.i18n.ui.about)
|
||||
|
||||
const saveSetting = async () => {
|
||||
const keys = ['lang', 'logType', 'proxyMode', 'autoUpdate', 'fetchFullHistory', 'hideNovice']
|
||||
for (let key of keys) {
|
||||
await ipcRenderer.invoke('SAVE_CONFIG', [key, settingForm[key]])
|
||||
}
|
||||
}
|
||||
|
||||
const saveLang = async () => {
|
||||
await saveSetting()
|
||||
emit('changeLang')
|
||||
}
|
||||
|
||||
const closeSetting = () => emit('close')
|
||||
|
||||
const disableProxy = async () => {
|
||||
await ipcRenderer.invoke('DISABLE_PROXY')
|
||||
}
|
||||
|
||||
const openGithub = () => shell.openExternal('https://github.com/biuuu/star-rail-warp-export')
|
||||
const openLink = (link) => shell.openExternal(link)
|
||||
|
||||
const exportUIGFJSON = () => {
|
||||
ipcRenderer.invoke('EXPORT_UIGF_JSON')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
data.langMap = await ipcRenderer.invoke('LANG_MAP')
|
||||
const config = await ipcRenderer.invoke('GET_CONFIG')
|
||||
Object.assign(settingForm, config)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-form-item__label {
|
||||
line-height: normal !important;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
}
|
||||
.el-form-item__content {
|
||||
flex-direction: column;
|
||||
align-items: start !important;
|
||||
}
|
||||
.el-form-item--default {
|
||||
margin-bottom: 14px !important;
|
||||
}
|
||||
</style>
|
71
src/renderer/gachaDetail.js
Normal file
71
src/renderer/gachaDetail.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { isWeapon, isCharacter } from './utils'
|
||||
|
||||
const itemCount = (map, name) => {
|
||||
if (!map.has(name)) {
|
||||
map.set(name, 1)
|
||||
} else {
|
||||
map.set(name, map.get(name) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const gachaDetail = (data) => {
|
||||
const detailMap = new Map()
|
||||
for (let [key, value] of data) {
|
||||
let detail = {
|
||||
count3: 0, count4: 0, count5: 0,
|
||||
count3w: 0, count4w: 0, count5w: 0, count4c: 0, count5c: 0,
|
||||
weapon3: new Map(), weapon4: new Map(), weapon5: new Map(),
|
||||
char4: new Map(), char5: new Map(),
|
||||
date: [],
|
||||
ssrPos: [], countMio: 0, total: value.length,
|
||||
}
|
||||
let lastSSR = 0
|
||||
let dateMin = 0
|
||||
let dateMax = 0
|
||||
value.forEach((item, index) => {
|
||||
const { time, name, item_type: type, rank_type: rank } = item
|
||||
const timestamp = new Date(time).getTime()
|
||||
if (!dateMin) dateMin = timestamp
|
||||
if (!dateMax) dateMax = timestamp
|
||||
if (dateMin > timestamp) dateMin = timestamp
|
||||
if (dateMax < timestamp) dateMax = timestamp
|
||||
if (rank === '3') {
|
||||
detail.count3++
|
||||
detail.countMio++
|
||||
if (isWeapon(type)) {
|
||||
detail.count3w++
|
||||
itemCount(detail.weapon3, name)
|
||||
}
|
||||
} else if (rank === '4') {
|
||||
detail.count4++
|
||||
detail.countMio++
|
||||
if (isWeapon(type)) {
|
||||
detail.count4w++
|
||||
itemCount(detail.weapon4, name)
|
||||
} else if (isCharacter(type)) {
|
||||
detail.count4c++
|
||||
itemCount(detail.char4, name)
|
||||
}
|
||||
} else if (rank === '5') {
|
||||
detail.ssrPos.push([name, index + 1 - lastSSR, time, key])
|
||||
lastSSR = index + 1
|
||||
detail.count5++
|
||||
detail.countMio = 0
|
||||
if (isWeapon(type)) {
|
||||
detail.count5w++
|
||||
itemCount(detail.weapon5, name)
|
||||
} else if (isCharacter(type)) {
|
||||
detail.count5c++
|
||||
itemCount(detail.char5, name)
|
||||
}
|
||||
}
|
||||
})
|
||||
detail.date = [dateMin, dateMax]
|
||||
if (detail.total) {
|
||||
detailMap.set(key, detail)
|
||||
}
|
||||
}
|
||||
return detailMap
|
||||
}
|
||||
|
||||
export default gachaDetail
|
16
src/renderer/index.css
Normal file
16
src/renderer/index.css
Normal file
@ -0,0 +1,16 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--el-font-size-base: 12px !important;
|
||||
}
|
||||
body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
body::-webkit-scrollbar-thumb {
|
||||
@apply rounded-full bg-gray-300;
|
||||
}
|
||||
}
|
12
src/renderer/index.html
Normal file
12
src/renderer/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="pt-4 px-6"></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
11
src/renderer/main.js
Normal file
11
src/renderer/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import './index.css'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import { IconInstaller } from './utils'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus)
|
||||
IconInstaller(app)
|
||||
app.mount('#app')
|
24
src/renderer/utils.js
Normal file
24
src/renderer/utils.js
Normal file
@ -0,0 +1,24 @@
|
||||
import * as IconComponents from '@element-plus/icons-vue'
|
||||
|
||||
const weaponTypeNames = new Set([
|
||||
'光锥', 'Light Cone', '光錐', 'Lichtkegel', 'Conos de luz', 'cônes de lumière', '光円錐', '광추', 'Cones de Luz', 'Световые конусы', 'Nón Ánh Sáng'
|
||||
])
|
||||
|
||||
const characterTypeNames = new Set([
|
||||
'角色', 'Character', '캐릭터', 'キャラクター', 'Personaje', 'Personnage', 'Персонажи', 'ตัวละคร', 'Nhân Vật', 'Figur', 'Karakter', 'Personagem'
|
||||
])
|
||||
|
||||
const isCharacter = (name) => characterTypeNames.has(name)
|
||||
const isWeapon = (name) => weaponTypeNames.has(name)
|
||||
|
||||
const IconInstaller = (app) => {
|
||||
Object.values(IconComponents).forEach(component => {
|
||||
app.component(component.name, component)
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
isWeapon,
|
||||
isCharacter,
|
||||
IconInstaller,
|
||||
}
|
Reference in New Issue
Block a user