Files
TeyvatGuide/src/pages/common/PageHome.vue
2026-04-10 14:05:14 +08:00

339 lines
9.5 KiB
Vue

<!-- 首页 -->
<template>
<v-app-bar>
<div class="home-top-nav">
<div v-if="isLogin" class="home-tools">
<v-select
v-model="curGid"
:hide-details="true"
:items="games"
class="home-tool-select"
density="compact"
item-value="gid"
label="小工具(右侧)分区"
variant="outlined"
>
<template #selection="{ item }">
<div class="select-item main">
<img
v-if="item.icon"
:alt="item.title"
:src="item.icon"
:title="item.title"
class="icon"
/>
<span>{{ item.title }}</span>
</div>
</template>
<template #item="{ props, item }">
<div
:class="item.gid === curGid ? 'selected' : ''"
class="select-item sub"
v-bind="props"
>
<img
v-if="item.icon"
:alt="item.title"
:src="item.icon"
:title="item.title"
class="icon"
/>
<span>{{ item.title }}</span>
</div>
</template>
</v-select>
<TGameNav :gid="curGid" :mini="true" />
</div>
<div class="home-select">
<v-select
v-model="oldItems"
:chips="true"
:hide-details="true"
:items="showItemsAll"
:multiple="true"
density="compact"
label="首页组件显示"
variant="outlined"
width="440px"
/>
<v-btn :rounded="true" class="select-btn" variant="elevated" @click="submitHome">
确定
</v-btn>
</div>
</div>
</v-app-bar>
<div class="home-container">
<component :is="item" v-for="item in components" :key="item" @success="loadEnd(item)" />
</div>
</template>
<script lang="ts" setup>
import TGameNav from "@comp/app/t-gameNav.vue";
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import PhCompCalendar from "@comp/pageHome/ph-comp-calendar.vue";
import PhCompDailyNote from "@comp/pageHome/ph-comp-daily-note.vue";
import PhCompPool from "@comp/pageHome/ph-comp-pool.vue";
import PhCompPosition from "@comp/pageHome/ph-comp-position.vue";
import PhCompSign from "@comp/pageHome/ph-comp-sign.vue";
import TSUserAccount from "@Sqlm/userAccount.js";
import useAppStore from "@store/app.js";
import useBBSStore from "@store/bbs.js";
import useHomeStore from "@store/home.js";
import { getVersion } from "@tauri-apps/api/app";
import { invoke } from "@tauri-apps/api/core";
import { openUrl } from "@tauri-apps/plugin-opener";
import getLrv from "@utils/Github.js";
import TGLogger from "@utils/TGLogger.js";
import { storeToRefs } from "pinia";
import { defineComponent, onMounted, ref, shallowRef, watch } from "vue";
/**
* 单文件组件类型
*/
type SFComp = ReturnType<typeof defineComponent>;
/**
* 选项类型
*/
type SelectItem = {
/** 图标 */
icon: string;
/** 标题 */
title: string;
/** 分区ID */
gid: number;
};
const homeStore = useHomeStore();
const bbsStore = useBBSStore();
const { devMode, isLogin, lastUcts } = storeToRefs(useAppStore());
const { gameList } = storeToRefs(bbsStore);
const curGid = ref<number>(2);
const games = shallowRef<Array<SelectItem>>();
const loadItems = shallowRef<Array<string>>([]);
const components = shallowRef<Array<SFComp>>([]);
const showItems = shallowRef<Array<string>>([]);
const showItemsAll = shallowRef<Array<string>>(["素材日历", "限时祈愿", "近期活动"]);
const oldItems = shallowRef<Array<string>>([]);
onMounted(async () => {
await bbsStore.refreshGameList();
await bbsStore.refreshGameUidCards();
// @ts-expect-error-next-line The import.meta meta-property is not allowed in files which will build into CommonJS output.
const isProdEnv = import.meta.env.MODE === "production";
if (isProdEnv && devMode.value) devMode.value = false;
if (isLogin.value) {
await TSUserAccount.account.updateCk();
await showLoading.start("正在加载首页小部件");
games.value = gameList.value.map((i) => ({ icon: i.app_icon, title: i.name, gid: i.id }));
showItems.value = homeStore.getShowItems();
showItemsAll.value = ["游戏签到", "实时便笺", "素材日历", "限时祈愿", "近期活动"];
} else {
showItems.value = homeStore.getShowItems().filter((i) => i !== "游戏签到");
showItemsAll.value = ["素材日历", "限时祈愿", "近期活动"];
}
oldItems.value = showItems.value;
await loadComp();
await checkAppUpdate();
});
watch(
() => components.value,
async (cur, old) => {
const newComp = cur.filter((i) => !old.includes(i));
if (newComp.length === 0) await showLoading.end();
},
);
async function loadComp(): Promise<void> {
const temp: Array<SFComp> = [];
for (const item of showItems.value) {
switch (item) {
case "游戏签到":
if (isLogin.value) {
temp.push(PhCompSign);
} else {
showSnackbar.warn("未登录不可设置游戏签到组件");
}
break;
case "实时便笺":
if (isLogin.value) {
temp.push(PhCompDailyNote);
} else {
showSnackbar.warn("未登录不可设置实时便笺组件");
}
break;
case "限时祈愿":
temp.push(PhCompPool);
break;
case "近期活动":
temp.push(PhCompPosition);
break;
case "素材日历":
temp.push(PhCompCalendar);
break;
}
}
await showLoading.start(`正在加载:${showItems.value.join("、")}`);
components.value = temp;
await TGLogger.Info(`[Home][loadComp] 打开首页,当前显示:${showItems.value.join("、")}`);
}
async function submitHome(): Promise<void> {
if (oldItems.value.length === 0) {
showSnackbar.warn("请至少选择一个!");
oldItems.value = showItems.value;
return;
}
showItems.value = oldItems.value;
homeStore.setShowItems(showItems.value);
showSnackbar.success("设置成功!");
await TGLogger.Info("[Home][submitHome] 首页设置成功,当前显示:" + showItems.value.join("、"));
loadItems.value = showItems.value.filter((i) => loadItems.value.includes(i));
await loadComp();
}
function getName(name: string): string | undefined {
switch (name) {
case "ph-comp-sign":
return "游戏签到";
case "ph-comp-daily-note":
return "实时便笺";
case "ph-comp-pool":
return "限时祈愿";
case "ph-comp-position":
return "近期活动";
case "ph-comp-calendar":
return "素材日历";
default:
return undefined;
}
}
async function loadEnd(item: ReturnType<typeof defineComponent>): Promise<void> {
const compName = getName(item.__name ?? "");
if (!compName) return;
await TGLogger.Info(`[Home][loadEnd] ${compName} 加载完成`);
await showLoading.update(`${compName} 加载完成`);
if (!loadItems.value.includes(compName)) loadItems.value.push(compName);
else showSnackbar.warn(`${compName} 已加载`);
if (loadItems.value.length === components.value.length) await showLoading.end();
}
async function checkAppUpdate(): Promise<void> {
const nowTs = Math.floor(Date.now() / 1000);
const diffTime = nowTs - lastUcts.value;
if (diffTime < 60 * 60 * 24) return;
await TGLogger.Info("[Home][CheckAppUpdate]检测版本更新");
const versionApp = await getVersion();
const versionCheck = await getLrv();
if (versionCheck === "0") return;
if (versionCheck === versionApp) {
await TGLogger.Info(`[Home][CheckAppUpdate]版本号一致:${versionCheck}`);
lastUcts.value = nowTs;
return;
}
await TGLogger.Info(`[Home][CheckAppUpdate]检测到新版本:${versionCheck}`);
const check = await showDialog.checkF({
title: "检测到新版本",
text: `${versionApp}${versionCheck}`,
otcancel: false,
confirmLabel: "前往更新",
cancelLabel: "稍后提醒",
});
lastUcts.value = nowTs;
if (!check) return;
const isMsix = await invoke<boolean>("is_msix");
if (isMsix) {
await openUrl("ms-windows-store://pdp/?ProductId=9nlbnnnbnsjn");
return;
}
await openUrl("https://github.com/BTMuli/TeyvatGuide/releases/latest");
}
</script>
<style lang="scss" scoped>
.home-top-nav {
position: relative;
display: flex;
width: 100%;
max-width: 100%;
height: 100%;
align-items: center;
justify-content: space-between;
overflow-x: auto;
}
.home-container {
position: relative;
display: flex;
flex-direction: column;
gap: 12px;
}
.home-tools {
display: flex;
align-items: center;
justify-content: flex-start;
}
.home-tool-select {
width: 220px;
max-width: 250px;
margin-left: 16px;
}
.home-select {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
margin-left: auto;
gap: 8px;
}
.select-btn {
width: 100px;
height: 40px;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
.select-item {
position: relative;
display: flex;
align-items: center;
column-gap: 4px;
&.main {
position: relative;
height: 24px;
font-family: var(--font-title);
font-size: 16px;
}
&.sub {
padding: 8px;
font-family: var(--font-title);
font-size: 16px;
&:hover {
background: var(--common-shadow-2);
}
&.selected:not(:hover) {
background: var(--common-shadow-1);
}
}
.icon {
width: 28px;
height: 28px;
border-radius: 4px;
}
}
</style>