♻️ 重构公告时间计算逻辑,降低界面加载耗时

This commit is contained in:
BTMuli
2025-05-29 20:52:35 +08:00
parent 36f2adf3f7
commit b30d96c3d3
9 changed files with 118 additions and 106 deletions

View File

@@ -1,25 +1,25 @@
/*
* @file assets/fonts/index.scss
* @description 字体样式文件
* @since v0.2.0-Alpha
* @since v0.7.7-beta
*/
@font-face {
font-family: "Genshin";
src: url("./汉仪文黑-85W.ttf") format("truetype");
src: url("@/assets/fonts/汉仪文黑-85W.ttf") format("truetype");
}
@font-face {
font-family: "Genshin-Light";
src: url("./汉仪文黑-55W.ttf") format("truetype");
src: url("@/assets/fonts/汉仪文黑-55W.ttf") format("truetype");
}
@font-face {
font-family: "JetBrians mono";
src: url("./JetBrainsMono-Regular.ttf") format("truetype");
src: url("@/assets/fonts/JetBrainsMono-Regular.ttf") format("truetype");
}
@font-face {
font-family: "JetBrians mono Bold";
src: url("./JetBrainsMono-Bold.ttf") format("truetype");
src: url("@/assets/fonts/JetBrainsMono-Bold.ttf") format("truetype");
}

View File

@@ -1,12 +1,12 @@
/*
* @file assets/index.css
* @description 全局样式文件
* @since Beta v0.7.2
* @since Beta v0.7.7
*/
@import "fonts/index.scss";
@import "themes/default.scss";
@import "themes/dark.scss";
@use "fonts/index.scss";
@use "themes/default.scss";
@use "themes/dark.scss";
:root {
/* font */

View File

@@ -1,41 +1,55 @@
<template>
<div :id="`anno_card_${props.modelValue.id}`" class="anno-card">
<div :title="props.modelValue.title" class="anno-cover" @click="createAnno">
<TMiImg
v-if="props.modelValue.banner !== ''"
:ori="true"
:src="props.modelValue.banner"
alt="cover"
/>
<div :id="`anno_card_${model.id}`" class="anno-card">
<div :title="model.title" class="anno-cover" @click="createAnno">
<TMiImg v-if="model.banner !== ''" :ori="true" :src="model.banner" alt="cover" />
<img v-else alt="cover" src="/source/UI/defaultCover.webp" />
<div class="anno-info">
<div class="anno-time">
<v-icon>mdi-clock-time-four-outline</v-icon>
<span>{{ props.modelValue.timeStr }}</span>
<span>{{ timeStr }}</span>
</div>
</div>
</div>
<div :title="props.modelValue.title" class="anno-title" @click="shareAnno">
{{ parseTitle(props.modelValue.subtitle) }}
<div :title="model.title" class="anno-title" @click="shareAnno">
{{ parseTitle(model.subtitle) }}
</div>
<div :title="`标签:${props.modelValue.tagLabel}`" class="anno-label">
<img :src="props.modelValue.tagIcon" alt="tag" />
<span>{{ props.modelValue.tagLabel }}</span>
<div :title="`标签:${model.tagLabel}`" class="anno-label">
<img :src="model.tagIcon" alt="tag" />
<span>{{ model.tagLabel }}</span>
</div>
<div class="anno-id">{{ props.modelValue.id }}</div>
<div class="anno-id">{{ model.id }}</div>
</div>
</template>
<script lang="ts" setup>
import TMiImg from "@comp/app/t-mi-img.vue";
import showSnackbar from "@comp/func/snackbar.js";
import hk4eReq from "@req/hk4eReq.js";
import useAppStore from "@store/app.js";
import TGLogger from "@utils/TGLogger.js";
import { generateShareImg } from "@utils/TGShare.js";
import { createTGWindow } from "@utils/TGWindow.js";
import { decodeRegExp } from "@utils/toolFunc.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, watch } from "vue";
import type { AnnoCard } from "@/pages/common/PageAnno.vue";
type TAnnoCardProps = { region: string; modelValue: AnnoCard; lang: string };
const { server } = storeToRefs(useAppStore());
type TAnnoCardProps = { region: string; lang: string };
const props = defineProps<TAnnoCardProps>();
const model = defineModel<AnnoCard>({ required: true });
const timeStr = ref<string>(model.value.timeStr);
onMounted(async () => await refreshAnnoTime());
watch(
() => model.value,
async (newVal, oldVal) => {
if (newVal.id !== oldVal.id) {
timeStr.value = newVal.timeStr;
await refreshAnnoTime();
}
},
);
function parseTitle(title: string): string {
const dom = new DOMParser().parseFromString(title, "text/html");
@@ -43,21 +57,82 @@ function parseTitle(title: string): string {
}
async function createAnno(): Promise<void> {
const annoPath = `/anno_detail/${props.region}/${props.modelValue.id}/${props.lang}`;
const annoTitle = `Anno_${props.modelValue.id} ${props.modelValue.title}`;
await TGLogger.Info(`[Announcements][createAnno][${props.modelValue.id}] 打开公告窗口`);
const annoPath = `/anno_detail/${props.region}/${model.value.id}/${props.lang}`;
const annoTitle = `Anno_${model.value.id} ${model.value.title}`;
await TGLogger.Info(`[Announcements][createAnno][${model.value.id}] 打开公告窗口`);
await createTGWindow(annoPath, "Sub_window", annoTitle, 960, 720, false, false);
}
async function shareAnno(): Promise<void> {
const fileName = `AnnoCard_${props.modelValue.id}_${props.modelValue.subtitle}`;
const element = document.querySelector<HTMLElement>(`#anno_card_${props.modelValue.id}`);
const fileName = `AnnoCard_${model.value.id}_${model.value.subtitle}`;
const element = document.querySelector<HTMLElement>(`#anno_card_${model.value.id}`);
if (element === null) {
showSnackbar.error("分享失败,未找到分享元素");
return;
}
await generateShareImg(fileName, element, 2.5);
}
async function refreshAnnoTime(): Promise<void> {
const detail = await hk4eReq.anno.content(model.value.id, server.value, "zh-cn");
const strGet = getAnnoTime(detail.content);
if (strGet !== false) timeStr.value = strGet;
}
function getAnnoTime(content: string): string | false {
const regexes = [
/〓活动时间〓.*?\d\.\d版本期间持续开放/,
/(?:|).*?(?:(\d\.\d)(?:|)|&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt; *?)/s,
/(?:||||).*?(\d\.\d).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/s,
/(?:(?:|)||).*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/s,
// /〓活动时间〓.*?(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}).*?(\d\.\d版本结束)/
/.*?(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}).*?(\d\.\d)/s,
];
if (content.match(regexes[0])) {
const res = content.match(regexes[0]);
return res?.[0].replace(/.*?(\d\.\d版本期间持续开放)/, "$1") ?? false;
}
if (content.match(regexes[1])) {
const res = content.match(regexes[1]);
if (res === null) return false;
const regex2 = /\d\.\d版本更新(?:完成|)后永久开放/;
const regex3 = /\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}/;
const res2 = res[0].match(regex2);
if (res2 !== null) return res2[0];
const res3 = res[0].match(regex3);
return res3 === null ? false : `${res3[0]} 后永久开放`;
}
if (content.match(regexes[2])) {
const res = content.match(regexes[2]);
if (res?.[1]?.match(/\d\.\d/)) {
const parser = new DOMParser().parseFromString(decodeRegExp(res[2]), "text/html");
return `${res?.[1]}版本更新后 ~ ${parser.body.innerText}`;
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[3])) {
const res = content.match(regexes[3]);
try {
const span1 = document.createElement("span");
span1.innerHTML = res?.[1] ?? "";
const span2 = document.createElement("span");
span2.innerHTML = res?.[2] ?? "";
return `${span1.innerText} ~ ${span2.innerText}`;
} catch (e) {
console.error(e);
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[4])) {
const res = content.match(regexes[4]);
if (res !== null) {
const cnt = res[0].match(/〓/g);
if (cnt && cnt.length > 2) return false;
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
return false;
}
</script>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;

View File

@@ -67,7 +67,7 @@ import GroHistory from "@comp/userGacha/gro-history.vue";
import GroOverview from "@comp/userGacha/gro-overview.vue";
import GroTable from "@comp/userGacha/gro-table.vue";
import UgoUid from "@comp/userGacha/ugo-uid.vue";
import Hk4eApi from "@req/hk4eReq.js";
import hk4eReq from "@req/hk4eReq.js";
import takumiReq from "@req/takumiReq.js";
import TSUserGacha from "@Sqlm/userGacha.js";
import useUserStore from "@store/user.js";
@@ -190,7 +190,7 @@ async function refreshGachaPool(
if (!force) endId = (await TSUserGacha.getGachaCheck(account.value.gameUid, type)) ?? "0";
while (true) {
page++;
const gachaRes = await Hk4eApi.gacha(authkey.value, type, reqId);
const gachaRes = await hk4eReq.gacha(authkey.value, type, reqId);
if (!Array.isArray(gachaRes)) {
showSnackbar.error(`[${type}][${gachaRes.retcode}] ${gachaRes.message}`);
await TGLogger.Error(

View File

@@ -56,10 +56,9 @@
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TaCard from "@comp/pageAnno/ta-card.vue";
import Hk4eApi, { type AnnoLang, type AnnoServer } from "@req/hk4eReq.js";
import hk4eReq, { type AnnoLang, type AnnoServer } from "@req/hk4eReq.js";
import useAppStore from "@store/app.js";
import TGLogger from "@utils/TGLogger.js";
import { decodeRegExp } from "@utils/toolFunc.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
@@ -133,16 +132,8 @@ async function loadData(): Promise<void> {
"正在获取公告数据",
`服务器:${getRegionName(server.value)},语言:${getLangName(lang.value)}`,
);
const annoData = await Hk4eApi.anno.list(server.value, lang.value);
const annoData = await hk4eReq.anno.list(server.value, lang.value);
const listCards = annoData.list.map((list) => list.list.map((anno) => getAnnoCard(anno))).flat();
await showLoading.update("", { title: "正在解析游戏内公告时间" });
for (const item of listCards) {
if (item.typeLabel === "game") continue;
const detail = await Hk4eApi.anno.content(item.id, server.value, "zh-cn");
const timeStr = getAnnoTime(detail.content);
if (timeStr !== false) item.timeStr = timeStr;
await showLoading.update(`[${item.id}]${item.subtitle}:${item.timeStr}`);
}
annoCards.value = {
activity: listCards.filter((item) => item.typeLabel === "activity"),
game: listCards.filter((item) => item.typeLabel === "game"),
@@ -191,61 +182,6 @@ function getLangName(value: AnnoLang): string {
return annoLangList.find((item) => item.value === value)?.text ?? annoLangList[0].text;
}
function getAnnoTime(content: string): string | false {
const regexes = [
/〓活动时间〓.*?\d\.\d版本期间持续开放/,
/(?:|).*?(?:(\d\.\d)(?:|)|&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt; *?)/s,
/(?:||||).*?(\d\.\d).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/s,
/(?:(?:|)||).*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/s,
// /〓活动时间〓.*?(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}).*?(\d\.\d版本结束)/
/.*?(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}).*?(\d\.\d)/s,
];
if (content.match(regexes[0])) {
const res = content.match(regexes[0]);
return res?.[0].replace(/.*?(\d\.\d版本期间持续开放)/, "$1") ?? false;
}
if (content.match(regexes[1])) {
const res = content.match(regexes[1]);
if (res === null) return false;
const regex2 = /\d\.\d版本更新(?:完成|)后永久开放/;
const regex3 = /\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}/;
const res2 = res[0].match(regex2);
if (res2 !== null) return res2[0];
const res3 = res[0].match(regex3);
return res3 === null ? false : `${res3[0]} 后永久开放`;
}
if (content.match(regexes[2])) {
const res = content.match(regexes[2]);
if (res?.[1]?.match(/\d\.\d/)) {
const parser = new DOMParser().parseFromString(decodeRegExp(res[2]), "text/html");
return `${res?.[1]}版本更新后 ~ ${parser.body.innerText}`;
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[3])) {
const res = content.match(regexes[3]);
try {
const span1 = document.createElement("span");
span1.innerHTML = res?.[1] ?? "";
const span2 = document.createElement("span");
span2.innerHTML = res?.[2] ?? "";
return `${span1.innerText} ~ ${span2.innerText}`;
} catch (e) {
console.error(e);
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[4])) {
const res = content.match(regexes[4]);
if (res !== null) {
const cnt = res[0].match(/〓/g);
if (cnt && cnt.length > 2) return false;
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
return false;
}
async function switchNews(): Promise<void> {
await TGLogger.Info("[Announcements][switchNews] 切换米游社咨讯");
await router.push("/news/2");

View File

@@ -1,7 +1,7 @@
/**
* @file request/hk4eReq.ts
* @description Hk4eApi 请求模块
* @since Beta v0.7.2
* @since Beta v0.7.7
*/
import TGHttp from "@utils/TGHttp.js";
@@ -169,10 +169,10 @@ async function queryPandaQr(
return resp.data;
}
const Hk4eApi = {
const hk4eReq = {
anno: { list: getAnnoList, content: getAnnoContent },
gacha: getGachaLog,
loginQr: { create: fetchPandaQr, state: queryPandaQr },
};
export default Hk4eApi;
export default hk4eReq;

View File

@@ -73,7 +73,7 @@
import TPinWin from "@comp/app/t-pinWin.vue";
import TSwitchTheme from "@comp/app/t-switchTheme.vue";
import showLoading from "@comp/func/loading.js";
import Hk4eApi, { type AnnoLang, AnnoServer } from "@req/hk4eReq.js";
import hk4eReq, { type AnnoLang, AnnoServer } from "@req/hk4eReq.js";
import useAppStore from "@store/app.js";
import parseAnnoContent from "@utils/annoParser.js";
import { storeToRefs } from "pinia";
@@ -100,7 +100,7 @@ onMounted(async () => {
return;
}
await showLoading.update(`公告ID: ${annoId}`);
const listData = await Hk4eApi.anno.list(region, lang);
const listData = await hk4eReq.anno.list(region, lang);
for (const listItem of listData.list) {
for (const single of listItem.list) {
if (single.ann_id === annoId) {
@@ -109,7 +109,7 @@ onMounted(async () => {
}
}
}
jsonContent.value = await Hk4eApi.anno.content(annoId, region, lang);
jsonContent.value = await hk4eReq.anno.content(annoId, region, lang);
parsedJson.value = parseAnnoContent(jsonContent.value);
await showLoading.end();
});

View File

@@ -19,7 +19,7 @@ import TShareBtn from "@comp/app/t-shareBtn.vue";
import TSwitchTheme from "@comp/app/t-switchTheme.vue";
import showLoading from "@comp/func/loading.js";
import TaParser from "@comp/pageAnno/ta-parser.vue";
import Hk4eApi, { type AnnoLang, AnnoServer } from "@req/hk4eReq.js";
import hk4eReq, { type AnnoLang, AnnoServer } from "@req/hk4eReq.js";
import useAppStore from "@store/app.js";
import { app, webviewWindow } from "@tauri-apps/api";
import TGLogger from "@utils/TGLogger.js";
@@ -44,7 +44,7 @@ onMounted(async () => {
}
await showLoading.update("正在获取数据");
try {
annoData.value = await Hk4eApi.anno.content(annoId, region, lang);
annoData.value = await hk4eReq.anno.content(annoId, region, lang);
await showLoading.update("正在渲染数据");
await webviewWindow
.getCurrentWebviewWindow()

View File

@@ -40,6 +40,7 @@
"src/**/*.d.ts",
"src/**/*.ts",
"src/**/*.vue",
"src/**/*.scss",
"src/App.vue",
"src/data/**/*.json",
"scripts/*.ts",