mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-14 09:38:13 +08:00
♻️ 路径优化
This commit is contained in:
448
src/pages/common/Achievements.vue
Normal file
448
src/pages/common/Achievements.vue
Normal file
@@ -0,0 +1,448 @@
|
||||
<template>
|
||||
<!-- 顶部操作栏 -->
|
||||
<v-app-bar style="background: rgb(0 0 0 / 50%); color: #f4d8a8; font-family: Genshin, serif">
|
||||
<template #prepend>
|
||||
<span style="font-size: 30px">{{ title }}</span>
|
||||
</template>
|
||||
<v-spacer />
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
hide-details
|
||||
@click:append="searchCard"
|
||||
@keyup.enter="searchCard"
|
||||
/>
|
||||
<template #append>
|
||||
<v-btn prepend-icon="mdi-import" class="ms-2 top-btn" @click="importJson"> 导入 </v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="ms-2 top-btn" @click="exportJson"> 导出 </v-btn>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<ToLoading v-model="loading" :title="loadingTitle" />
|
||||
<div class="wrap">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="left-wrap">
|
||||
<v-list
|
||||
v-for="series in seriesList"
|
||||
:key="series.id"
|
||||
class="card-left"
|
||||
@click="selectSeries(series.id)"
|
||||
>
|
||||
<div class="version-icon-series">v{{ series.version }}</div>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-img
|
||||
width="40px"
|
||||
style="margin-right: 10px"
|
||||
:src="`/source/achievement/${series.id}.webp`"
|
||||
/>
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ series.name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ series.finCount }} / {{ series.totalCount }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
<!-- 右侧内容-->
|
||||
<div class="right-wrap" @scroll="handleScroll">
|
||||
<v-list
|
||||
v-if="selectedSeries !== 0 && selectedSeries !== 17 && selectedSeries !== -1"
|
||||
:style="{
|
||||
backgroundImage: 'url(' + getCardImg.bg || null + ')',
|
||||
backgroundPosition: 'right',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
margin: '10px',
|
||||
borderRadius: '10px 50px 50px 10px',
|
||||
color: '#485466',
|
||||
fontFamily: 'Genshin,serif',
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
}"
|
||||
@click="openImg()"
|
||||
>
|
||||
<v-list-item :title="getCardInfo.name" :subtitle="getCardInfo.desc">
|
||||
<template #prepend>
|
||||
<v-img width="80px" style="margin-right: 10px" :src="getCardImg.icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<div class="list-empty" :style="{ height: `${emptyHeight}px` }">
|
||||
<v-list
|
||||
v-for="achievement in renderAchievement"
|
||||
:key="achievement.id"
|
||||
class="card-right"
|
||||
:style="{ Transform: `translateY(${translateY})` }"
|
||||
>
|
||||
<div v-if="achievement.progress !== 0" class="achievement-progress">
|
||||
{{ achievement.progress }}
|
||||
</div>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon :color="achievement.isCompleted ? '#fec90b' : '#485466'">
|
||||
<!-- todo 图标替换 -->
|
||||
{{ achievement.isCompleted ? "mdi-check-circle" : "mdi-circle" }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ achievement.name }}
|
||||
<span class="version-icon-single">v{{ achievement.version }}</span>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ achievement.description }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
<span v-show="achievement.isCompleted" class="right-time">{{
|
||||
achievement.completedTime
|
||||
}}</span>
|
||||
<v-card class="reward-card">
|
||||
<v-img src="/icon/material/201.webp" sizes="32" />
|
||||
<div class="reward-num">
|
||||
<span>{{ achievement.reward }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 弹窗提示 -->
|
||||
<v-snackbar v-model="snackbar" timeout="1500" :color="snackbarColor" top>
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref, onBeforeMount, computed } from "vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
// tauri
|
||||
import { dialog, fs } from "@tauri-apps/api";
|
||||
// Store
|
||||
import { useAchievementsStore } from "../../store/modules/achievements";
|
||||
// Utils
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
import { getUiafHeader, readUiafData, verifyUiafData } from "../../utils/UIAF";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
|
||||
// Store
|
||||
const achievementsStore = useAchievementsStore();
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
const loadingTitle = ref<string>("正在加载数据");
|
||||
|
||||
// data
|
||||
const title = ref(achievementsStore.title);
|
||||
const getCardInfo = ref<TGApp.Sqlite.NameCard.SingleTable>(<TGApp.Sqlite.NameCard.SingleTable>{});
|
||||
const getCardImg = computed(() => {
|
||||
return {
|
||||
profile: `/source/nameCard/profile/${getCardInfo.value.name}.webp`,
|
||||
bg: `/source/nameCard/bg/${getCardInfo.value.name}.webp`,
|
||||
icon: `/source/nameCard/icon/${getCardInfo.value.name}.webp`,
|
||||
};
|
||||
});
|
||||
// series
|
||||
const seriesList = ref<TGApp.Sqlite.Achievement.SeriesTable[]>([]);
|
||||
const selectedSeries = ref<number>(-1);
|
||||
const selectedAchievement = ref<TGApp.Sqlite.Achievement.SingleTable[]>([]);
|
||||
const renderAchievement = computed(() => {
|
||||
return selectedAchievement.value.slice(start.value, start.value + itemCount.value + 1);
|
||||
});
|
||||
// virtual list
|
||||
const start = ref<number>(0);
|
||||
const itemCount = computed(() => {
|
||||
return Math.ceil((window.innerHeight - 100) / 76);
|
||||
});
|
||||
const emptyHeight = computed(() => {
|
||||
return selectedAchievement.value.length * 76;
|
||||
});
|
||||
const translateY = ref<string>("0px");
|
||||
// render
|
||||
const search = ref<string>("");
|
||||
const snackbar = ref<boolean>(false);
|
||||
const snackbarText = ref<string>("");
|
||||
const snackbarColor = ref<string>("#F5810A");
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const { total, fin } = await TGSqlite.getAchievementsOverview();
|
||||
achievementsStore.flushData(total, fin);
|
||||
title.value = achievementsStore.title;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在获取成就系列数据";
|
||||
seriesList.value = await TGSqlite.getAchievementSeries();
|
||||
loadingTitle.value = "正在获取成就数据";
|
||||
selectedAchievement.value = await TGSqlite.getAchievements();
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function handleScroll(e: Event): void {
|
||||
const target: HTMLElement = <HTMLElement>e.target;
|
||||
// 如果 scrollTop 到底部了
|
||||
if (target.scrollTop + target.offsetHeight >= target.scrollHeight) {
|
||||
// 如果 selectedAchievement 的长度小于 itemCount,不进行偏移
|
||||
if (selectedAchievement.value.length <= itemCount.value) {
|
||||
window.requestAnimationFrame(() => {
|
||||
start.value = 0;
|
||||
translateY.value = "0px";
|
||||
});
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
start.value = selectedAchievement.value.length - itemCount.value;
|
||||
translateY.value = `${(selectedAchievement.value.length - itemCount.value) * 76}px`;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const scrollTop = target.scrollTop;
|
||||
if (selectedSeries.value !== 0 && selectedSeries.value !== 17 && selectedSeries.value !== -1) {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (scrollTop < 86.8) {
|
||||
start.value = 0;
|
||||
translateY.value = "0px";
|
||||
} else {
|
||||
start.value = Math.floor((scrollTop - 86.8) / 76);
|
||||
translateY.value = `${scrollTop - 86.8}px`;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.requestAnimationFrame(() => {
|
||||
start.value = Math.floor(scrollTop / 76);
|
||||
translateY.value = `${scrollTop}px`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染选中的成就系列
|
||||
async function selectSeries(index: number): Promise<void> {
|
||||
// 如果选中的是已经选中的系列,则不进行操作
|
||||
if (selectedSeries.value === index) {
|
||||
snackbarText.value = "已经选中该系列";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在获取对应的成就数据";
|
||||
selectedSeries.value = index;
|
||||
selectedAchievement.value = await TGSqlite.getAchievements(index);
|
||||
loadingTitle.value = "正在查找对应的成就名片";
|
||||
if (selectedSeries.value !== 0 && selectedSeries.value !== 17) {
|
||||
getCardInfo.value = await TGSqlite.getNameCard(index);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 打开图片
|
||||
function openImg(): void {
|
||||
createTGWindow(getCardImg.value.profile, "nameCard", getCardInfo.value.name, 840, 400, false);
|
||||
}
|
||||
|
||||
async function searchCard(): Promise<void> {
|
||||
if (search.value === "") {
|
||||
snackbarColor.value = "#F5810A";
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
selectedSeries.value = -1;
|
||||
loadingTitle.value = "正在搜索";
|
||||
loading.value = true;
|
||||
selectedAchievement.value = await TGSqlite.searchAchievements(search.value);
|
||||
if (selectedAchievement.value.length === 0) {
|
||||
snackbarColor.value = "#F5810A";
|
||||
snackbarText.value = "没有找到对应的成就";
|
||||
snackbar.value = true;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
// 导入 UIAF 数据,进行数据合并、刷新
|
||||
async function importJson(): Promise<void> {
|
||||
const selectedFile = await dialog.open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (selectedFile && (await verifyUiafData(<string>selectedFile))) {
|
||||
const remoteRaw: string | false = await readUiafData(<string>selectedFile);
|
||||
if (remoteRaw === false) {
|
||||
snackbarText.value = "读取 UIAF 数据失败,请检查文件是否符合规范";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在解析数据";
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在合并成就数据";
|
||||
await TGSqlite.mergeUIAF(JSON.parse(remoteRaw).list);
|
||||
loadingTitle.value = "即将刷新页面";
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
async function exportJson(): Promise<void> {
|
||||
// 判断是否有数据
|
||||
if (achievementsStore.finAchievements === 0) {
|
||||
snackbarColor.value = "#F5810A";
|
||||
snackbarText.value = "没有可导出的数据";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
// 获取本地数据
|
||||
const UiafData = {
|
||||
info: await getUiafHeader(),
|
||||
list: await TGSqlite.getUIAF(),
|
||||
};
|
||||
const isSave = await dialog.save({
|
||||
// TODO: 设置保存文件名
|
||||
filters: [
|
||||
{
|
||||
name: "uiaf",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (isSave) {
|
||||
await fs.writeTextFile(isSave, JSON.stringify(UiafData));
|
||||
snackbarColor.value = "#00BFA5";
|
||||
snackbarText.value = "导出成功";
|
||||
snackbar.value = true;
|
||||
} else {
|
||||
snackbarColor.value = "#F5810A";
|
||||
snackbarText.value = "导出已取消";
|
||||
snackbar.value = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
/* 顶部按钮 */
|
||||
.top-btn {
|
||||
background: #393b40;
|
||||
color: #faf7e8 !important;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* 左侧系列 */
|
||||
.left-wrap {
|
||||
width: 400px;
|
||||
height: calc(100vh - 100px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* 右侧成就 */
|
||||
.right-wrap {
|
||||
width: calc(100% - 410px);
|
||||
height: calc(100vh - 100px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.list-empty {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 版本信息 */
|
||||
.version-icon-series {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 80px;
|
||||
border-radius: 10px 0 0;
|
||||
border-top: #fff 2px solid;
|
||||
border-left: #fff 2px solid;
|
||||
background: #546d8b;
|
||||
color: #fec90b;
|
||||
font-family: Genshin, serif;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.version-icon-single {
|
||||
border-radius: 5px;
|
||||
color: #ff6d6d;
|
||||
font-family: Genshin, serif;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-left {
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
background: #485466;
|
||||
color: #fec90b;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 成就卡片 */
|
||||
.card-right {
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
background: #546d8b;
|
||||
color: #faf7e8;
|
||||
}
|
||||
|
||||
/* 成就进度 */
|
||||
.achievement-progress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 65px;
|
||||
border-right: #fff 2px solid;
|
||||
border-bottom: #fff 2px solid;
|
||||
background: #8ba5c5;
|
||||
border-bottom-right-radius: 20px;
|
||||
color: #485466;
|
||||
font-family: Genshin, serif;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 成就完成时间 */
|
||||
.right-time {
|
||||
margin-right: 10px;
|
||||
color: #faf7e8;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
/* 成就奖励 */
|
||||
.reward-card {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 5px;
|
||||
background-image: url("/icon/bg/5-Star.webp");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.reward-num {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgb(0 0 0 / 50%);
|
||||
color: #faf7e8;
|
||||
font-size: 8px;
|
||||
}
|
||||
</style>
|
||||
248
src/pages/common/Announcements.vue
Normal file
248
src/pages/common/Announcements.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<ToLoading v-model="loading" :title="loadingTitle" />
|
||||
<v-tabs v-model="tab" align-tabs="start" class="anno-tab">
|
||||
<v-tab value="activity"> 活动公告 </v-tab>
|
||||
<v-tab value="game"> 游戏公告 </v-tab>
|
||||
<v-spacer />
|
||||
<v-btn class="anno-switch-btn" @click="switchNews">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-bullhorn</v-icon>
|
||||
</template>
|
||||
切换米游社咨讯
|
||||
</v-btn>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="activity">
|
||||
<div class="anno-grid">
|
||||
<v-card v-for="item in annoCards.activity" :key="item.id" class="anno-card">
|
||||
<div class="anno-cover" @click="toPost(item)">
|
||||
<img :src="item.banner" alt="cover" />
|
||||
</div>
|
||||
<v-card-title class="anno-card-title">
|
||||
{{ item.title }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn class="anno-btn" @click="toPost(item)">
|
||||
<img :src="item.tagIcon || '../assets/icons/arrow-right.svg'" alt="right" />
|
||||
<span>查看</span>
|
||||
</v-btn>
|
||||
<v-card-subtitle v-show="!appStore.devMode">
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
{{ item.startTime.split(" ")[0] }} -
|
||||
{{ item.endTime.split(" ")[0] }}
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle v-show="appStore.devMode"> id: {{ item.id }} </v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="anno-dev-btn" @click="toJson(item)">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
<span>查看数据</span>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="game">
|
||||
<div class="anno-grid">
|
||||
<v-card v-for="item in annoCards.game" :key="item.id" class="anno-card">
|
||||
<div class="anno-cover" @click="toPost(item)">
|
||||
<img :src="item.banner" alt="cover" />
|
||||
</div>
|
||||
<v-card-title class="anno-card-title">{{ item.title }}</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn class="anno-btn" @click="toPost(item)">
|
||||
<img :src="item.tagIcon || '../assets/icons/arrow-right.svg'" alt="right" />
|
||||
<span>查看</span>
|
||||
</v-btn>
|
||||
<v-card-subtitle v-show="!appStore.devMode">
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
{{ item.startTime.split(" ")[0] }} -
|
||||
{{ item.endTime.split(" ")[0] }}
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle v-show="appStore.devMode"> id: {{ item.id }} </v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="anno-dev-btn" @click="toJson(item)">
|
||||
<img src="../../assets/icons/arrow-right.svg" alt="right" />
|
||||
<span>查看数据</span>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
// store
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
// utils
|
||||
import TGRequest from "../../web/request/TGRequest";
|
||||
import TGUtils from "../../web/utils/TGUtils";
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
|
||||
// store
|
||||
const appStore = useAppStore();
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
const loadingTitle = ref<string>("正在加载");
|
||||
// 路由
|
||||
const router = useRouter();
|
||||
|
||||
// 数据
|
||||
const tab = ref<string>("");
|
||||
const annoCards = ref({
|
||||
activity: <TGApp.App.Announcement.ListCard[]>[],
|
||||
game: <TGApp.App.Announcement.ListCard[]>[],
|
||||
});
|
||||
const annoData = ref<TGApp.BBS.Announcement.ListData>(<TGApp.BBS.Announcement.ListData>{});
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = "正在获取公告数据";
|
||||
annoData.value = await TGRequest.Anno.getList();
|
||||
loadingTitle.value = "正在转换公告数据";
|
||||
const listCards: TGApp.App.Announcement.ListCard[] = TGUtils.Anno.getCard(annoData.value);
|
||||
const activityCard: TGApp.App.Announcement.ListCard[] = listCards.filter(
|
||||
(item) => item.typeLabel === "活动公告",
|
||||
);
|
||||
const newsCard: TGApp.App.Announcement.ListCard[] = listCards.filter(
|
||||
(item) => item.typeLabel === "游戏公告",
|
||||
);
|
||||
annoCards.value = {
|
||||
activity: activityCard,
|
||||
game: newsCard,
|
||||
};
|
||||
tab.value = "activity";
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
async function switchNews(): Promise<void> {
|
||||
await router.push("/news/2");
|
||||
}
|
||||
|
||||
async function toPost(item: TGApp.App.Announcement.ListCard): Promise<void> {
|
||||
const path = router.resolve({
|
||||
name: "游戏内公告",
|
||||
params: {
|
||||
anno_id: item.id,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "游戏内公告", item.title, 960, 720, false, false);
|
||||
}
|
||||
|
||||
async function toJson(item: TGApp.App.Announcement.ListCard): Promise<void> {
|
||||
const path = router.resolve({
|
||||
name: "游戏内公告(JSON)",
|
||||
params: {
|
||||
anno_id: item.id,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "游戏内公告-JSON", item.title, 960, 720, false, false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.anno-tab {
|
||||
margin-bottom: 10px;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.anno-switch-btn {
|
||||
height: 40px;
|
||||
background: var(--common-btn-bg-1);
|
||||
color: var(--common-btn-bgt-1);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.anno-grid {
|
||||
display: grid;
|
||||
padding: 5px;
|
||||
font-family: var(--font-title);
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
}
|
||||
|
||||
.anno-card {
|
||||
border-radius: 5px;
|
||||
background: var(--common-bg-1);
|
||||
color: var(--common-bgt-1);
|
||||
}
|
||||
|
||||
.anno-cover {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.anno-cover img {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
.anno-cover :hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.1);
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
.anno-card-title {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
transition: padding-top 0.3s linear, padding-bottom 0.3s linear, background 0.3s linear,
|
||||
font-size 0.3s linear, line-height 0.3s linear, white-space 0.3s linear;
|
||||
}
|
||||
|
||||
.anno-card-title:hover {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
padding: 5px;
|
||||
background: var(--common-shadow-2);
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.anno-btn {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
background: var(--common-btn-bg-2);
|
||||
color: var(--common-btn-bgt-2);
|
||||
}
|
||||
|
||||
.anno-btn img {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 5px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.anno-dev-btn {
|
||||
background: var(--common-btn-bg-2);
|
||||
color: var(--common-btn-bgt-2);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.anno-dev-btn img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
background: var(--common-shadow-2);
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
700
src/pages/common/Config.vue
Normal file
700
src/pages/common/Config.vue
Normal file
@@ -0,0 +1,700 @@
|
||||
<template>
|
||||
<ToLoading v-model="loading" :title="loadingTitle" />
|
||||
<v-list class="config-list">
|
||||
<v-list-subheader :inset="true" class="config-header"> 应用信息 </v-list-subheader>
|
||||
<v-divider :inset="true" class="border-opacity-75" />
|
||||
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="/platforms/tauri.webp" alt="Tauri" />
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="/icon.webp" alt="App" />
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
应用版本
|
||||
<v-btn
|
||||
class="card-btn"
|
||||
size="small"
|
||||
@click="toOuter('https://github.com/BTMuli/Tauri.Genshin/releases/latest')"
|
||||
>
|
||||
Alpha
|
||||
</v-btn>
|
||||
</v-list-item-title>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ versionApp }}.{{ buildTime }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="成就版本">
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="../../assets/icons/achievements.svg" alt="Achievements" />
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ achievementsStore.lastVersion }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="登录信息">
|
||||
<v-list-item-subtitle v-show="userInfo.nickname !== '未登录'">
|
||||
{{ userInfo.nickname }} uid:{{ userInfo.uid }}
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle v-show="userInfo.nickname === '未登录'">
|
||||
未登录,请输入 Cookie 登录!
|
||||
</v-list-item-subtitle>
|
||||
<template #prepend>
|
||||
<img class="config-icon" :src="userInfo.avatar" alt="Login" />
|
||||
</template>
|
||||
<template #append>
|
||||
<v-btn class="card-btn" @click="tryConfirm('refreshUser')">
|
||||
<template #prepend>
|
||||
<img src="../../assets/icons/circle-check.svg" alt="check" />
|
||||
刷新数据
|
||||
</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader :inset="true" class="config-header"> 系统信息 </v-list-subheader>
|
||||
<v-divider :inset="true" class="border-opacity-75" />
|
||||
<v-list-item title="系统平台">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-desktop-classic</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ osPlatform }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="系统版本">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-desktop-classic</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ osVersion }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="数据库更新时间" prepend-icon="mdi-database">
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{
|
||||
dbInfo.find((item) => item.key === "dataUpdated")?.value
|
||||
}}</v-list-item-subtitle>
|
||||
</template>
|
||||
<v-list-item-subtitle
|
||||
>更新于
|
||||
{{ dbInfo.find((item) => item.key === "dataUpdated")?.updated }}</v-list-item-subtitle
|
||||
>
|
||||
</v-list-item>
|
||||
<v-list-item title="数据库版本" prepend-icon="mdi-database">
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{
|
||||
dbInfo.find((item) => item.key === "appVersion")?.value
|
||||
}}</v-list-item-subtitle>
|
||||
</template>
|
||||
<v-list-item-subtitle
|
||||
>更新于
|
||||
{{ dbInfo.find((item) => item.key === "appVersion")?.updated }}</v-list-item-subtitle
|
||||
>
|
||||
</v-list-item>
|
||||
<v-list-subheader :inset="true" class="config-header"> 设置 </v-list-subheader>
|
||||
<v-divider :inset="true" class="border-opacity-75" />
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>mdi-view-dashboard</v-icon>
|
||||
</template>
|
||||
<v-select
|
||||
v-model="showHome"
|
||||
:items="homeStore.getShowItems()"
|
||||
label="首页显示组件"
|
||||
:multiple="true"
|
||||
:chips="true"
|
||||
/>
|
||||
<template #append>
|
||||
<v-btn class="card-btn" @click="submitHome">
|
||||
<template #prepend>
|
||||
<img src="../../assets/icons/circle-check.svg" alt="check" />
|
||||
确定
|
||||
</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-content-save" title="数据备份" @click="tryConfirm('backup')" />
|
||||
<v-list-item prepend-icon="mdi-content-save" title="数据恢复" @click="tryConfirm('restore')" />
|
||||
<v-list-item prepend-icon="mdi-delete" title="清除用户缓存" @click="tryConfirm('delUser')" />
|
||||
<v-list-item prepend-icon="mdi-delete" title="清除临时数据" @click="tryConfirm('delTemp')" />
|
||||
<v-list-item prepend-icon="mdi-cog" title="恢复默认设置" @click="tryConfirm('delApp')" />
|
||||
<v-list-subheader :inset="true" class="config-header"> 调试 </v-list-subheader>
|
||||
<v-divider :inset="true" class="border-opacity-75" />
|
||||
<v-list-item v-if="appStore.devEnv" title="调试模式" subtitle="开启后将显示调试信息">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-bug</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-switch
|
||||
v-model="appStore.devMode"
|
||||
:label="appStore.devMode ? '开启' : '关闭'"
|
||||
:inset="true"
|
||||
color="#FAC51E"
|
||||
@click="submitDevMode"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>mdi-cookie</v-icon>
|
||||
</template>
|
||||
<template #title>
|
||||
<span style="cursor: pointer" @click="tryConfirm('inputCookie')">手动输入 Cookie</span>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-icon
|
||||
style="cursor: pointer"
|
||||
@click="toOuter('https://github.com/BTMuli/Tauri.Genshin/issues/18')"
|
||||
>
|
||||
mdi-help-circle-outline
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="重置数据库" prepend-icon="mdi-delete" @click="tryConfirm('resetDB')" />
|
||||
<v-list-item
|
||||
title="检测 SQLite 数据库完整性"
|
||||
prepend-icon="mdi-database-check"
|
||||
@click="tryConfirm('checkDB')"
|
||||
/>
|
||||
<v-list-subheader :inset="true" class="config-header"> 路径 </v-list-subheader>
|
||||
<v-divider :inset="true" class="border-opacity-75" />
|
||||
<v-list-item prepend-icon="mdi-database">
|
||||
<v-list-item-title>本地数据库路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.dbDataPath }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-folder">
|
||||
<v-list-item-title>本地临时数据路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.tempDataDir }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-folder">
|
||||
<v-list-item-title>本地用户数据路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.userDataDir }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<!-- 弹窗提示条 -->
|
||||
<v-snackbar v-model="snackbar" timeout="1500" :color="snackbarColor">
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
<!-- 确认弹窗 -->
|
||||
<ToConfirm
|
||||
v-model="confirmShow"
|
||||
:model-input="confirmInput"
|
||||
:title="confirmText"
|
||||
:subtitle="confirmSub"
|
||||
:is-input="isConfirmInput"
|
||||
@confirm="doConfirm(confirmOper)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import ToConfirm from "../../components/overlay/to-confirm.vue";
|
||||
// tauri
|
||||
import { app, fs, os } from "@tauri-apps/api";
|
||||
// store
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import { useHomeStore } from "../../store/modules/home";
|
||||
import { useAchievementsStore } from "../../store/modules/achievements";
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
// utils
|
||||
import { backupUiafData, restoreUiafData } from "../../utils/UIAF";
|
||||
import { backupAbyssData, backupCookieData } from "../../web/utils/backupData";
|
||||
import { restoreAbyssData, restoreCookieData } from "../../web/utils/restoreData";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TGRequest from "../../web/request/TGRequest";
|
||||
|
||||
// Store
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const homeStore = useHomeStore();
|
||||
const achievementsStore = useAchievementsStore();
|
||||
|
||||
// About App
|
||||
const versionApp = ref<string>("");
|
||||
const versionTauri = ref<string>("");
|
||||
const buildTime = computed(() => appStore.buildTime);
|
||||
|
||||
// About OS
|
||||
const osPlatform = ref<string>("");
|
||||
const osVersion = ref<string>("");
|
||||
const dbInfo = ref<Array<{ key: string; value: string; updated: string }>>([]);
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
const loadingTitle = ref<string>("正在加载...");
|
||||
|
||||
// data
|
||||
const showHome = ref<string[]>(homeStore.getShowValue());
|
||||
const userInfo = computed(() => {
|
||||
const info = userStore.getBriefInfo();
|
||||
return {
|
||||
nickname: info.nickname || "未登录",
|
||||
uid: info.uid || "-1",
|
||||
desc: info.desc || "未登录",
|
||||
avatar: info.avatar || "/source/UI/defaultUser.webp",
|
||||
};
|
||||
});
|
||||
|
||||
// snackbar
|
||||
const snackbar = ref<boolean>(false);
|
||||
const snackbarText = ref<string>("");
|
||||
const snackbarColor = ref<string>("success");
|
||||
|
||||
// confirm
|
||||
const confirmText = ref<string>("");
|
||||
const isConfirmInput = ref<boolean>(false);
|
||||
const confirmInput = ref<string>("");
|
||||
const confirmSub = ref<string>("");
|
||||
const confirmOper = ref<string>("");
|
||||
const confirmShow = ref<boolean>(false);
|
||||
|
||||
// load version
|
||||
onMounted(async () => {
|
||||
versionApp.value = await app.getVersion();
|
||||
versionTauri.value = await app.getTauriVersion();
|
||||
osPlatform.value = `${await os.platform()}`;
|
||||
osVersion.value = await os.version();
|
||||
try {
|
||||
dbInfo.value = await TGSqlite.getAppData();
|
||||
const ck = dbInfo.value.find((v) => v.key === "cookie");
|
||||
if (ck) {
|
||||
userStore.cookie = JSON.parse(ck.value);
|
||||
}
|
||||
} catch (e) {
|
||||
snackbarText.value = "读取数据库失败!";
|
||||
snackbarColor.value = "warn";
|
||||
snackbar.value = true;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
// 打开外部链接
|
||||
function toOuter(url: string): void {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
// open confirm
|
||||
function tryConfirm(oper: string): void {
|
||||
confirmSub.value = "";
|
||||
isConfirmInput.value = false;
|
||||
switch (oper) {
|
||||
case "backup":
|
||||
confirmText.value = "确认备份数据吗?";
|
||||
confirmSub.value = "若已备份将会被覆盖";
|
||||
confirmOper.value = "backup";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "restore":
|
||||
confirmText.value = "确认恢复数据吗?";
|
||||
confirmSub.value = "请确保存在备份数据";
|
||||
confirmOper.value = "restore";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delTemp":
|
||||
confirmText.value = "确认清除临时数据吗?";
|
||||
confirmOper.value = "delTemp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delUser":
|
||||
confirmText.value = "确认清除用户缓存吗?";
|
||||
confirmSub.value = "备份数据也将被清除";
|
||||
confirmOper.value = "delUser";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delApp":
|
||||
confirmText.value = "确认恢复默认设置吗?";
|
||||
confirmOper.value = "delApp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "inputCookie":
|
||||
isConfirmInput.value = true;
|
||||
confirmText.value = "请输入 Cookie";
|
||||
confirmSub.value = "Cookie 用于获取用户信息";
|
||||
confirmOper.value = "inputCookie";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "checkDB":
|
||||
confirmText.value = "将检测数据库表单完整性";
|
||||
confirmSub.value = "数据库版本与更新时间也会进行检测";
|
||||
confirmOper.value = "checkDB";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "resetDB":
|
||||
confirmText.value = "确认重置数据库吗?";
|
||||
confirmSub.value = "请确认已经备份关键数据";
|
||||
confirmOper.value = "resetDB";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "refreshUser":
|
||||
confirmText.value = "确认刷新用户信息吗?";
|
||||
confirmSub.value = "将会重新获取用户信息";
|
||||
confirmOper.value = "refreshUser";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// transfer confirm oper
|
||||
async function doConfirm(oper: string): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
switch (oper) {
|
||||
case "backup":
|
||||
await backupData();
|
||||
break;
|
||||
case "restore":
|
||||
await restoreData();
|
||||
break;
|
||||
case "delTemp":
|
||||
await delTempData();
|
||||
break;
|
||||
case "delUser":
|
||||
await delUserData();
|
||||
break;
|
||||
case "delApp":
|
||||
initAppData();
|
||||
break;
|
||||
case "inputCookie":
|
||||
await inputCookie();
|
||||
break;
|
||||
case "checkDB":
|
||||
await checkDB();
|
||||
break;
|
||||
case "resetDB":
|
||||
await resetDB();
|
||||
break;
|
||||
case "updateDB":
|
||||
await updateDB();
|
||||
break;
|
||||
case "refreshUser":
|
||||
await refreshUser();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// confirmOper
|
||||
async function backupData(): Promise<void> {
|
||||
loadingTitle.value = "正在备份数据...";
|
||||
loading.value = true;
|
||||
const achievements = await TGSqlite.getUIAF();
|
||||
await backupUiafData(achievements);
|
||||
const cookie = await TGSqlite.getCookie();
|
||||
await backupCookieData(cookie);
|
||||
const abyss = await TGSqlite.getAbyss();
|
||||
await backupAbyssData(abyss);
|
||||
loading.value = false;
|
||||
snackbarText.value = "数据已备份!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
async function restoreData(): Promise<void> {
|
||||
loadingTitle.value = "正在恢复数据...";
|
||||
loading.value = true;
|
||||
const fail = [];
|
||||
let res = await restoreUiafData();
|
||||
if (!res) {
|
||||
fail.push("成就数据");
|
||||
}
|
||||
res = await restoreCookieData();
|
||||
if (!res) {
|
||||
fail.push("Cookie");
|
||||
}
|
||||
res = await restoreAbyssData();
|
||||
if (!res) {
|
||||
fail.push("深渊数据");
|
||||
}
|
||||
if (fail.length > 0) {
|
||||
snackbarText.value = `${fail.join("、")} 恢复失败!`;
|
||||
snackbarColor.value = "error";
|
||||
} else {
|
||||
snackbarText.value = "数据已恢复!";
|
||||
snackbarColor.value = "success";
|
||||
}
|
||||
const cookie = await TGSqlite.getCookie();
|
||||
userStore.initCookie(cookie);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
async function delTempData(): Promise<void> {
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
snackbarText.value = "临时数据已删除!";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
async function delUserData(): Promise<void> {
|
||||
await fs.removeDir("userData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
snackbarText.value = "用户数据已删除!";
|
||||
snackbar.value = true;
|
||||
achievementsStore.init();
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
}
|
||||
|
||||
// 恢复默认配置
|
||||
function initAppData(): void {
|
||||
appStore.init();
|
||||
homeStore.init();
|
||||
achievementsStore.init();
|
||||
snackbarText.value = "已恢复默认配置!即将刷新页面...";
|
||||
snackbar.value = true;
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// 开启 dev 模式
|
||||
function submitDevMode(): void {
|
||||
appStore.devMode
|
||||
? (snackbarText.value = "已关闭 dev 模式!")
|
||||
: (snackbarText.value = "已开启 dev 模式!");
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
// 修改首页显示
|
||||
function submitHome(): void {
|
||||
// 获取已选
|
||||
const show = showHome.value;
|
||||
if (show.length < 1) {
|
||||
snackbarText.value = "请至少选择一个!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
// 设置
|
||||
homeStore.setShowValue(show);
|
||||
snackbarText.value = "已修改!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
// 刷新用户数据
|
||||
async function refreshUser(): Promise<void> {
|
||||
const ck = userStore.cookie;
|
||||
// ck = {}
|
||||
if (Object.keys(ck).length < 1) {
|
||||
snackbarText.value = "请先输入 Cookie!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
let failCount = 0;
|
||||
loadingTitle.value = "正在验证 ltoken...";
|
||||
loading.value = true;
|
||||
const verifyLTokenRes = await TGRequest.User.byLToken.verify(ck.ltoken, ck.ltuid);
|
||||
if (typeof verifyLTokenRes === "string") {
|
||||
loadingTitle.value = "验证成功!正在刷新 cookie_token";
|
||||
} else {
|
||||
console.error(verifyLTokenRes);
|
||||
loadingTitle.value = "验证失败!正在重新获取 ltoken";
|
||||
const ltokenRes = await TGRequest.User.bySToken.getLToken(ck.stuid, ck.stoken);
|
||||
if (typeof ltokenRes === "string") {
|
||||
ck.ltoken = ltokenRes;
|
||||
await TGSqlite.saveAppData("cookie", JSON.stringify(ck));
|
||||
userStore.initCookie(ck);
|
||||
loadingTitle.value = "刷新成功!正在获取用户头像、昵称信息";
|
||||
} else {
|
||||
console.error(ltokenRes);
|
||||
loadingTitle.value = "刷新失败!正在获取用户头像、昵称信息";
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
const cookieTokenRes = await TGRequest.User.bySToken.getCookieToken(ck.stuid, ck.stoken);
|
||||
if (typeof cookieTokenRes === "string") {
|
||||
ck.cookie_token = cookieTokenRes;
|
||||
await TGSqlite.saveAppData("cookie", JSON.stringify(ck));
|
||||
userStore.initCookie(ck);
|
||||
console.log(JSON.stringify(ck));
|
||||
loadingTitle.value = "刷新成功!正在获取用户头像、昵称信息";
|
||||
} else {
|
||||
console.error(cookieTokenRes);
|
||||
loadingTitle.value = "刷新失败!正在获取用户头像、昵称信息";
|
||||
failCount++;
|
||||
}
|
||||
const infoRes = await TGRequest.User.byCookie.getUserInfo(ck.cookie_token, ck.account_id);
|
||||
if ("nickname" in infoRes) {
|
||||
userStore.setBriefInfo(infoRes);
|
||||
loadingTitle.value = "获取成功!正在获取用户游戏账号信息";
|
||||
} else {
|
||||
console.error(infoRes);
|
||||
loadingTitle.value = "获取失败!正在获取用户游戏账号信息";
|
||||
failCount++;
|
||||
}
|
||||
const accountRes = await TGRequest.User.byCookie.getAccounts(ck.cookie_token, ck.account_id);
|
||||
if (Array.isArray(accountRes)) {
|
||||
loadingTitle.value = "获取成功!正在保存到数据库!";
|
||||
await TGSqlite.saveAccount(accountRes);
|
||||
} else {
|
||||
console.error(accountRes);
|
||||
loadingTitle.value = "获取失败!";
|
||||
failCount++;
|
||||
}
|
||||
if (failCount > 0) {
|
||||
snackbarText.value = "刷新失败!请重新输入 cookie!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
} else {
|
||||
snackbarText.value = "刷新成功!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 输入 Cookie
|
||||
async function inputCookie(): Promise<void> {
|
||||
const cookie = confirmInput.value;
|
||||
if (cookie === "") {
|
||||
snackbarText.value = "Cookie 为空!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在获取 tokens...";
|
||||
const cookieObj = cookie
|
||||
.trim()
|
||||
.split(";")
|
||||
.map((item) => item.trim().split("="));
|
||||
const ticket = cookieObj.find((item) => item[0] === "login_ticket")?.[1];
|
||||
const uid = cookieObj.find((item) => item[0] === "login_uid")?.[1];
|
||||
// 如果两者不存在
|
||||
if (!ticket || !uid) {
|
||||
snackbarText.value = "Cookie 无效!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await TGRequest.User.init(ticket, uid);
|
||||
const ck = await TGSqlite.getCookie();
|
||||
userStore.initCookie(ck);
|
||||
loadingTitle.value = "正在获取用户信息...";
|
||||
const cookie_token = userStore.getCookieItem("cookie_token");
|
||||
const resUser = await TGRequest.User.byCookie.getUserInfo(cookie_token, uid);
|
||||
if ("nickname" in resUser) {
|
||||
userStore.setBriefInfo(resUser);
|
||||
appStore.isLogin = true;
|
||||
}
|
||||
const resAccounts = await TGRequest.User.byCookie.getAccounts(cookie_token, uid);
|
||||
if (Array.isArray(resAccounts)) {
|
||||
await TGSqlite.saveAccount(resAccounts);
|
||||
}
|
||||
loading.value = false;
|
||||
snackbarText.value = "Cookie 已保存!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
} catch (err) {
|
||||
loading.value = false;
|
||||
snackbarText.value = "Cookie 无效!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 SQLite 数据库
|
||||
async function checkDB(): Promise<void> {
|
||||
loadingTitle.value = "正在检查数据库表单完整性...";
|
||||
loading.value = true;
|
||||
const res = await TGSqlite.check();
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
if (!res) {
|
||||
confirmOper.value = "resetDB";
|
||||
confirmText.value = "数据库表单不完整,是否重置数据库?";
|
||||
loading.value = false;
|
||||
confirmShow.value = true;
|
||||
} else {
|
||||
const appVersion = await app.getVersion();
|
||||
const dbVersion = dbInfo.value.find((item) => item.key === "appVersion")?.value;
|
||||
const dbUpdatedTime = dbInfo.value.find((item) => item.key === "dataUpdated")?.value;
|
||||
if (!dbVersion || dbVersion < appVersion) {
|
||||
confirmOper.value = "updateDB";
|
||||
confirmText.value = "数据库版本过低,是否更新数据库?";
|
||||
loading.value = false;
|
||||
confirmShow.value = true;
|
||||
return;
|
||||
} else if (!buildTime.value.startsWith("dev")) {
|
||||
if (!dbUpdatedTime || dbUpdatedTime.startsWith("dev") || dbUpdatedTime < buildTime.value) {
|
||||
confirmOper.value = "updateDB";
|
||||
confirmText.value = "数据库可能过时,是否更新数据库?";
|
||||
loading.value = false;
|
||||
confirmShow.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
snackbarText.value = "数据库已是最新!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 SQLite 数据库
|
||||
async function resetDB(): Promise<void> {
|
||||
loadingTitle.value = "正在重置数据库...";
|
||||
loading.value = true;
|
||||
await TGSqlite.reset();
|
||||
loading.value = false;
|
||||
snackbarText.value = "数据库已重置!请进行再次检查。";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
// 刷新
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// 更新 SQLite 数据库
|
||||
async function updateDB(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
loadingTitle.value = "正在更新数据库...";
|
||||
loading.value = true;
|
||||
await TGSqlite.update();
|
||||
achievementsStore.lastVersion = await TGSqlite.getLatestAchievementVersion();
|
||||
loading.value = false;
|
||||
snackbarText.value = "数据库已是最新!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
// 刷新
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.config-list {
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
background: var(--content-bg-2);
|
||||
color: var(--content-text-3);
|
||||
font-family: Genshin-Light, serif;
|
||||
}
|
||||
|
||||
.config-header {
|
||||
margin-top: 10px;
|
||||
background: var(--content-bg-2);
|
||||
color: #fec90b;
|
||||
font-family: Genshin, serif;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.config-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
border-radius: 10px;
|
||||
margin-right: 15px;
|
||||
background: var(--content-bg-3);
|
||||
}
|
||||
</style>
|
||||
98
src/pages/common/Home.vue
Normal file
98
src/pages/common/Home.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSubtitle" />
|
||||
<component :is="item" v-for="item in components" :key="item" :ref="setItemRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { markRaw, onBeforeMount, onMounted, onUnmounted, onUpdated, ref } from "vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import TPool from "../../components/main/t-pool.vue";
|
||||
import TPosition from "../../components/main/t-position.vue";
|
||||
import TCalendar from "../../components/main/t-calendar.vue";
|
||||
// store
|
||||
import { useHomeStore } from "../../store/modules/home";
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
// utils
|
||||
import { getBuildTime } from "../../utils/TGBuild";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
|
||||
// store
|
||||
const appStore = useAppStore();
|
||||
const homeStore = useHomeStore();
|
||||
|
||||
// loading
|
||||
const loading = ref<boolean>(true);
|
||||
const loadingTitle = ref<string>("正在加载首页");
|
||||
const loadingSubtitle = ref<string>("");
|
||||
|
||||
// data
|
||||
const components = ref<any[]>([]);
|
||||
const itemRefs = ref<any[]>([]);
|
||||
|
||||
// 定时器
|
||||
const timer = ref<any>(null);
|
||||
|
||||
function readLoading(): void {
|
||||
if (!loading.value) return;
|
||||
const loadingMap = itemRefs.value.map((item) => {
|
||||
return item.loading ? item.name : null;
|
||||
});
|
||||
loadingSubtitle.value = "正在加载 " + loadingMap.filter((item) => item)?.join("、");
|
||||
if (loadingMap.every((item) => !item)) {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
// 获取当前用户
|
||||
const user = await TGSqlite.getCurAccount();
|
||||
// 存储当前用户
|
||||
if (user) {
|
||||
useUserStore().setCurAccount(user);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = "正在加载首页";
|
||||
loading.value = true;
|
||||
// 获取当前环境
|
||||
const timeGet = getBuildTime();
|
||||
appStore.devEnv = timeGet.startsWith("dev");
|
||||
if (!appStore.devEnv && appStore.devMode) {
|
||||
appStore.devMode = false;
|
||||
}
|
||||
appStore.buildTime = getBuildTime();
|
||||
const showItems = homeStore.getShowValue();
|
||||
await Promise.allSettled(
|
||||
showItems.map((item) => {
|
||||
switch (item) {
|
||||
case "限时祈愿":
|
||||
return components.value.push(markRaw(TPool));
|
||||
case "近期活动":
|
||||
return components.value.push(markRaw(TPosition));
|
||||
case "素材日历":
|
||||
return components.value.push(markRaw(TCalendar));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
timer.value = setInterval(readLoading, 100);
|
||||
});
|
||||
|
||||
function setItemRef(item: any): void {
|
||||
if (itemRefs.value.includes(item)) return;
|
||||
itemRefs.value.push(item);
|
||||
}
|
||||
|
||||
// 监听定时器
|
||||
onUpdated(() => {
|
||||
if (!loading.value) clearInterval(timer.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
itemRefs.value = [];
|
||||
});
|
||||
</script>
|
||||
3
src/pages/common/Test.vue
Normal file
3
src/pages/common/Test.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<h1>测试页</h1>
|
||||
</template>
|
||||
Reference in New Issue
Block a user