限时祈愿增加用户源,与限时活动共享数据

This commit is contained in:
BTMuli
2026-05-20 01:52:20 +08:00
parent 9ad75c77b0
commit c82a1d175c
7 changed files with 835 additions and 56 deletions

View File

@@ -1,25 +1,57 @@
<!-- 首页限时祈愿组件 -->
<template>
<THomeCard :append="false" title="限时祈愿">
<THomeCard :append="isLogin" title="限时祈愿">
<template #title>
<span>限时祈愿</span>
<v-icon class="tp-refresh" size="small" title="刷新" @click.stop="handleRefresh()">
mdi-refresh
</v-icon>
</template>
<template v-if="isLogin" #title-append>
<v-switch v-model="isUserPool" class="tp-switch"></v-switch>
<span>{{ isUserPool ? "用户" : "百科" }}</span>
</template>
<template #default>
<div v-if="pools.length < 3" class="pool-grid">
<PhPoolCard v-for="(pool, idx) in pools" :key="idx" :pool="pool" />
<div v-show="!isUserPool">
<div v-if="obcPools.length < 3" class="pool-grid">
<PhPoolCard v-for="(pool, idx) in obcPools" :key="idx" :pool="pool" />
</div>
<Swiper
v-else
:autoplay="{ delay: 3000, disableOnInteraction: false }"
:centered-slides="true"
:loop="true"
:modules="swiperModules"
:navigation="true"
:slides-per-view="2"
:space-between="12"
class="pool-swiper"
>
<SwiperSlide v-for="(pool, idx) in obcPools" :key="idx">
<PhPoolCard :pool="pool" />
</SwiperSlide>
</Swiper>
</div>
<div v-show="isUserPool">
<div v-if="userPools.length < 3" class="pool-grid">
<PhPoolUser v-for="(pool, idx) in userPools" :key="idx" :pool="pool" />
</div>
<Swiper
v-else
:autoplay="{ delay: 3000, disableOnInteraction: false }"
:centered-slides="true"
:loop="true"
:modules="swiperModules"
:navigation="true"
:slides-per-view="2"
:space-between="12"
class="pool-swiper"
>
<SwiperSlide v-for="(pool, idx) in userPools" :key="idx">
<PhPoolUser :pool="pool" />
</SwiperSlide>
</Swiper>
</div>
<Swiper
v-else
:autoplay="{ delay: 3000, disableOnInteraction: false }"
:centered-slides="true"
:loop="true"
:modules="swiperModules"
:navigation="true"
:slides-per-view="2"
:space-between="12"
class="pool-swiper"
>
<SwiperSlide v-for="(pool, idx) in pools" :key="idx">
<PhPoolCard :pool="pool" />
</SwiperSlide>
</Swiper>
</template>
</THomeCard>
</template>
@@ -28,48 +60,245 @@ import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import PhPoolCard from "@comp/pageHome/ph-pool-card.vue";
import PhPoolUser from "@comp/pageHome/ph-pool-user.vue";
import miscReq from "@req/miscReq.js";
import recordReq from "@req/recordReq.js";
import useAppStore from "@store/app.js";
import useHomeStore from "@store/home.js";
import useUserStore from "@store/user.js";
import takumiReq from "@req/takumiReq.js";
import TGHttps from "@utils/TGHttps.js";
import TGLogger from "@utils/TGLogger.js";
import { A11y, Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import { onMounted, shallowRef } from "vue";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
import THomeCard from "./ph-comp-card.vue";
import gameEnum from "@enum/game.js";
type TPoolEmits = (e: "success") => void;
const { isLogin } = storeToRefs(useAppStore());
const { cookie, account } = storeToRefs(useUserStore());
const homeStore = useHomeStore();
const emits = defineEmits<TPoolEmits>();
const pools = shallowRef<Array<TGApp.BBS.Obc.GachaItem>>([]);
const isInit = ref<boolean>(false);
const isRefreshing = ref<boolean>(false);
const isUserPool = ref<boolean>(isLogin.value);
const obcPools = shallowRef<Array<TGApp.BBS.Obc.GachaItem>>([]);
const userPools = shallowRef<Array<TGApp.Game.ActCalendar.ActPool>>([]);
const swiperModules = [Autoplay, A11y];
watch(
() => isUserPool.value,
async () => {
if (isUserPool.value) await loadUserPool();
else await loadObcPool();
},
);
watch(
() => account.value.gameUid,
async () => {
if (isUserPool.value) {
userPools.value = [];
homeStore.actCalendarData = undefined;
await loadUserPool(true);
}
},
);
onMounted(async () => {
if (isLogin.value) await loadUserPool();
else await loadObcPool();
emits("success");
isInit.value = true;
});
async function handleRefresh(): Promise<void> {
isRefreshing.value = true;
if (isUserPool.value) {
userPools.value = [];
homeStore.actCalendarData = undefined;
await loadUserPool(true);
} else {
obcPools.value = [];
await loadObcPool();
}
}
async function loadUserPool(forceReload: boolean = false): Promise<void> {
if (userPools.value.length > 0 && !forceReload) return;
if (!cookie.value) {
showSnackbar.warn("获取限时祈愿失败:未登录");
isLogin.value = false;
if (isInit.value) await showLoading.end();
return;
}
if (isInit.value) await showLoading.start("正在获取限时祈愿");
let data: TGApp.Game.ActCalendar.ActRes | undefined;
try {
if (homeStore.actCalendarData && !forceReload) {
data = homeStore.actCalendarData;
} else {
const resp = await recordReq.actCalendar(cookie.value, account.value);
if (<number>resp.retcode === 1034) {
await TGLogger.Warn("[PhCompPool][loadUserPool] 触发1034验证");
if (!isInit.value || !isRefreshing.value) {
if (isInit.value) await showLoading.end();
isUserPool.value = false;
if (isRefreshing.value) isRefreshing.value = false;
await loadObcPool();
return;
}
const challenge = await miscReq.challenge({
account_id: cookie.value.account_id,
cookie_token: cookie.value.cookie_token,
});
if (challenge === false) {
if (isInit.value) {
showSnackbar.error("极验验证失败,已切换至百科模式");
await showLoading.end();
}
isUserPool.value = false;
if (isRefreshing.value) isRefreshing.value = false;
await loadObcPool();
return;
}
const resp2 = await recordReq.actCalendar(cookie.value, account.value, challenge);
if (resp2.retcode !== 0) {
if (isInit.value) {
showSnackbar.warn(`[${resp2.retcode}] ${resp2.message}`);
await showLoading.end();
}
if (isRefreshing.value) isRefreshing.value = false;
await TGLogger.Warn(`[PhCompPool][loadUserPool] ${resp2.retcode}-${resp2.message}`);
emits("success");
return;
}
data = resp2.data;
} else if (resp.retcode !== 0) {
if (isInit.value) {
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
await showLoading.end();
}
if (isRefreshing.value) isRefreshing.value = false;
await TGLogger.Warn(`[PhCompPool][loadUserPool] ${resp.retcode}-${resp.message}`);
emits("success");
return;
} else {
data = resp.data;
}
homeStore.actCalendarData = data;
}
} catch (e) {
const errMsg = TGHttps.getErrMsg(e);
if (isInit.value) {
showSnackbar.error(`获取限时祈愿失败:${errMsg}`);
await showLoading.end();
}
if (isRefreshing.value) isRefreshing.value = false;
await TGLogger.Error("[PhCompPool][loadUserPool] 获取限时祈愿异常");
await TGLogger.Error(`[PhCompPool][loadUserPool] ${e}`);
emits("success");
return;
}
const pools = filterPools([
...data.avatar_card_pool_list,
...data.weapon_card_pool_list,
...data.mixed_card_pool_list,
]);
if (pools.length === 0) {
if (isInit.value) {
showSnackbar.warn("用户模式暂无卡池数据,已切换至百科模式");
await showLoading.end();
}
if (isRefreshing.value) isRefreshing.value = false;
isUserPool.value = false;
await loadObcPool();
return;
}
userPools.value = pools;
if (isInit.value) await showLoading.end();
if (isRefreshing.value) {
showSnackbar.success("限时祈愿数据已刷新");
isRefreshing.value = false;
}
}
async function loadObcPool(): Promise<void> {
if (obcPools.value.length > 0) return;
if (isInit.value) await showLoading.start("正在加载限时祈愿");
let resp: TGApp.BBS.Obc.GachaResp | undefined;
try {
resp = await takumiReq.obc.gacha();
if (resp.retcode !== 0) {
showSnackbar.error(`获取限时祈愿失败:[${resp.retcode}] ${resp.message}`);
await TGLogger.Warn(`[PhCompPool] 获取限时祈愿失败:[${resp.retcode}] ${resp.message}`);
if (isInit.value) {
showSnackbar.error(`获取限时祈愿失败:[${resp.retcode}] ${resp.message}`);
await showLoading.end();
}
await TGLogger.Warn(`[PhCompPool][loadObcPool] ${resp.retcode}-${resp.message}`);
emits("success");
return;
}
} catch (e) {
const errMsg = TGHttps.getErrMsg(e);
showSnackbar.error(`获取限时祈愿失败:${errMsg}`);
await TGLogger.Error(`[PhCompPool] 获取限时祈愿异常`);
await TGLogger.Error(`[PhCompPool] ${e}`);
if (isInit.value) {
showSnackbar.error(`获取限时祈愿失败:${errMsg}`);
await showLoading.end();
}
await TGLogger.Error("[PhCompPool][loadObcPool] 获取限时祈愿异常");
await TGLogger.Error(`[PhCompPool][loadObcPool] ${e}`);
emits("success");
return;
}
const list = resp.data.list;
if (list.length < 3) pools.value = list;
else pools.value = [...list, ...list];
emits("success");
});
if (list.length < 3) obcPools.value = list;
else obcPools.value = [...list, ...list];
if (list.length === 0 && isInit.value) showSnackbar.warn("暂无卡池信息");
if (isInit.value) await showLoading.end();
if (isRefreshing.value) {
showSnackbar.success("限时祈愿数据已刷新");
isRefreshing.value = false;
}
}
function filterPools(
pools: Array<TGApp.Game.ActCalendar.ActPool>,
): Array<TGApp.Game.ActCalendar.ActPool> {
if (pools.length === 0) return [];
const ongoing = pools.filter((p) => p.pool_status === gameEnum.actCalendar.poolStatus.Ongoing);
if (ongoing.length > 0) return ongoing;
const notStart = pools.filter((p) => p.pool_status === gameEnum.actCalendar.poolStatus.NotStart);
if (notStart.length > 0) return notStart;
return pools.filter((p) => p.pool_status === gameEnum.actCalendar.poolStatus.Ended);
}
</script>
<style lang="scss" scoped>
.tp-refresh {
margin-left: 8px;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
.tp-switch {
display: flex;
height: 36px;
align-items: center;
justify-content: center;
margin-right: 4px;
}
.pool-grid {
display: grid;
align-items: center;
@@ -77,4 +306,8 @@ onMounted(async () => {
gap: 8px;
grid-template-columns: repeat(2, 0.5fr);
}
.pool-swiper {
width: 100%;
}
</style>

View File

@@ -1,6 +1,12 @@
<!-- 首页近期活动组件 -->
<template>
<THomeCard :append="isLogin" title="近期活动">
<template #title>
<span>近期活动</span>
<v-icon class="tp-refresh" size="small" title="刷新" @click.stop="handleRefresh()">
mdi-refresh
</v-icon>
</template>
<template v-if="isLogin" #title-append>
<v-switch v-model="isUserPos" class="tp-switch"></v-switch>
<span>{{ isUserPos ? "用户" : "百科" }}</span>
@@ -29,10 +35,12 @@ import PboMaterial from "@comp/pageBag/pbo-material.vue";
import ToCalendar from "@comp/pageHome/ph-calendar-overlay.vue";
import PhPosObc from "@comp/pageHome/ph-pos-obc.vue";
import PhPosUser from "@comp/pageHome/ph-pos-user.vue";
import miscReq from "@req/miscReq.js";
import recordReq from "@req/recordReq.js";
import takumiReq from "@req/takumiReq.js";
import TSUserBagMaterial from "@Sqlm/userBagMaterial.js";
import useAppStore from "@store/app.js";
import useHomeStore from "@store/home.js";
import TSUserBagMaterial from "@Sqlm/userBagMaterial.js";
import useUserStore from "@store/user.js";
import { openUrl } from "@tauri-apps/plugin-opener";
import TGHttps from "@utils/TGHttps.js";
@@ -50,10 +58,12 @@ type TPositionEmits = (e: "success") => void;
const { isLogin } = storeToRefs(useAppStore());
const { cookie, account } = storeToRefs(useUserStore());
const homeStore = useHomeStore();
const emits = defineEmits<TPositionEmits>();
const isInit = ref<boolean>(false);
const isRefreshing = ref<boolean>(false);
const isUserPos = ref<boolean>(isLogin.value);
const showMaterial = ref<boolean>(false);
const showCalendar = ref<boolean>(false);
@@ -74,8 +84,9 @@ watch(
watch(
() => account.value.gameUid,
async () => {
if (isUserPos.value && isInit.value) {
if (isUserPos.value) {
userPos.value = [];
homeStore.actCalendarData = undefined;
await loadUserPosition(true);
}
},
@@ -88,6 +99,18 @@ onMounted(async () => {
isInit.value = true;
});
async function handleRefresh(): Promise<void> {
isRefreshing.value = true;
if (isUserPos.value) {
userPos.value = [];
homeStore.actCalendarData = undefined;
await loadUserPosition(true);
} else {
obsPos.value = [];
await loadWikiPosition();
}
}
async function loadUserPosition(forceReload: boolean = false): Promise<void> {
if (userPos.value.length > 0 && !forceReload) return;
if (!cookie.value) {
@@ -96,18 +119,61 @@ async function loadUserPosition(forceReload: boolean = false): Promise<void> {
return;
}
if (isInit.value) await showLoading.start("正在获取近期活动");
// 获取近期活动
let resp: TGApp.Game.ActCalendar.ActResp | undefined;
let data: TGApp.Game.ActCalendar.ActRes | undefined;
try {
resp = await recordReq.actCalendar(cookie.value, account.value);
if (resp.retcode !== 0) {
if (isInit.value) {
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
await showLoading.end();
if (homeStore.actCalendarData && !forceReload) {
data = homeStore.actCalendarData;
} else {
const resp = await recordReq.actCalendar(cookie.value, account.value);
if (<number>resp.retcode === 1034) {
await TGLogger.Warn("[PhCompPosition][loadUserPosition] 触发1034验证");
if (!isInit.value || !isRefreshing.value) {
if (isInit.value) await showLoading.end();
isUserPos.value = false;
if (isRefreshing.value) isRefreshing.value = false;
await loadWikiPosition();
return;
}
const challenge = await miscReq.challenge({
account_id: cookie.value.account_id,
cookie_token: cookie.value.cookie_token,
});
if (challenge === false) {
if (isInit.value) {
showSnackbar.error("极验验证失败,已切换至百科模式");
await showLoading.end();
}
isUserPos.value = false;
if (isRefreshing.value) isRefreshing.value = false;
await loadWikiPosition();
return;
}
const resp2 = await recordReq.actCalendar(cookie.value, account.value, challenge);
if (resp2.retcode !== 0) {
if (isInit.value) {
showSnackbar.warn(`[${resp2.retcode}] ${resp2.message}`);
await showLoading.end();
}
if (isRefreshing.value) isRefreshing.value = false;
await TGLogger.Warn(
`[PhCompPosition][loadUserPosition] ${resp2.retcode}-${resp2.message}`,
);
return;
}
data = resp2.data;
} else if (resp.retcode !== 0) {
if (isInit.value) {
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
await showLoading.end();
}
if (isRefreshing.value) isRefreshing.value = false;
await TGLogger.Warn("[PhCompPosition][loadUserPosition] 获取近期活动异常");
await TGLogger.Warn(`[PhCompPosition][loadUserPosition] ${resp.retcode}-${resp.message}`);
return;
} else {
data = resp.data;
}
await TGLogger.Warn(`[PhCompPosition][loadUserPosition] 获取近期活动异常`);
await TGLogger.Warn(`[PhCompPosition][loadUserPosition] ${resp.retcode}-${resp.message}`);
return;
homeStore.actCalendarData = data;
}
} catch (e) {
const errMsg = TGHttps.getErrMsg(e);
@@ -115,19 +181,20 @@ async function loadUserPosition(forceReload: boolean = false): Promise<void> {
showSnackbar.error(`获取近期活动失败:${errMsg}`);
await showLoading.end();
}
await TGLogger.Error(`[PhCompPosition][loadUserPosition] 获取近期活动异常`);
if (isRefreshing.value) isRefreshing.value = false;
await TGLogger.Error("[PhCompPosition][loadUserPosition] 获取近期活动异常");
await TGLogger.Error(`[PhCompPosition][loadUserPosition] ${e}`);
return;
}
if (!resp) {
if (isInit.value) await showLoading.end();
return;
}
// 处理近期活动数据
userPos.value = [...resp.data.act_list, ...resp.data.fixed_act_list]
userPos.value = [...data.act_list, ...data.fixed_act_list]
.filter((i) => i.start_timestamp !== "0")
.sort((a, b) => Number(a.is_finished) - Number(b.is_finished) || b.id - a.id);
if (isInit.value) await showLoading.end();
if (isRefreshing.value) {
showSnackbar.success("近期活动数据已刷新");
isRefreshing.value = false;
}
}
async function loadWikiPosition(): Promise<void> {
@@ -141,7 +208,7 @@ async function loadWikiPosition(): Promise<void> {
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
await showLoading.end();
}
await TGLogger.Warn(`[PhCompPosition][loadWikiPosition] 获取近期活动异常`);
await TGLogger.Warn("[PhCompPosition][loadWikiPosition] 获取近期活动异常");
await TGLogger.Warn(`[PhCompPosition][loadWikiPosition] ${resp.retcode}-${resp.message}`);
return;
}
@@ -151,7 +218,7 @@ async function loadWikiPosition(): Promise<void> {
showSnackbar.error(`获取近期活动失败:${errMsg}`);
await showLoading.end();
}
await TGLogger.Error(`[PhCompPosition][loadWikiPosition] 获取近期活动异常`);
await TGLogger.Error("[PhCompPosition][loadWikiPosition] 获取近期活动异常");
await TGLogger.Error(`[PhCompPosition][loadWikiPosition] ${e}`);
return;
}
@@ -159,10 +226,14 @@ async function loadWikiPosition(): Promise<void> {
if (isInit.value) await showLoading.end();
return;
}
const data = dfsObc(resp.data.list);
obsPos.value = data;
if (data.length === 0) showSnackbar.warn("暂无近期活动");
const dfsRes = dfsObc(resp.data.list);
obsPos.value = dfsRes;
if (dfsRes.length === 0) showSnackbar.warn("暂无近期活动");
if (isInit.value) await showLoading.end();
if (isRefreshing.value) {
showSnackbar.success("近期活动数据已刷新");
isRefreshing.value = false;
}
}
function dfsObc(
@@ -219,6 +290,15 @@ async function handleMaterial(cur: TGApp.Game.ActCalendar.ActReward): Promise<vo
}
</script>
<style lang="scss" scoped>
.tp-refresh {
margin-left: 8px;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
.tp-switch {
display: flex;
height: 36px;

View File

@@ -265,6 +265,7 @@ async function toPool(): Promise<void> {
backdrop-filter: blur(20px);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
box-shadow: 0 -2px 10px var(--common-shadow-2);
}
.ph-pool-avatars {
@@ -311,6 +312,7 @@ async function toPool(): Promise<void> {
font-size: 12px;
gap: 8px;
text-align: left;
text-shadow: 0 0 4px var(--tgc-dark-1);
}
.ph-pool-time {

View File

@@ -0,0 +1,458 @@
<!-- 限时祈愿卡片组件用户模式 -->
<template>
<div class="ph-pool-user-card">
<div class="ph-pool-type">
<span>{{ poolTypeLabel }}</span>
<span>{{ props.pool.pool_name }}</span>
</div>
<div class="ph-pool-cover" @click="toPool()">
<img v-if="cover" :src="cover" alt="cover" />
<img v-else alt="empty" class="empty" src="/UI/app/empty.webp" />
</div>
<div class="ph-pool-bottom">
<div v-if="totalItems < 5" class="ph-pool-avatars">
<template v-for="item in avatarItems" :key="`av-${item.avatar.id}`">
<div class="ph-pool-icon" @click="toAvatar(item.avatar)">
<TItemBox
v-if="item.info"
:model-value="getAvatarBox(item.info)"
:title="item.info.name"
/>
<img v-else :src="item.avatar.icon" alt="icon" />
</div>
</template>
<template v-for="item in weaponItems" :key="`wp-${item.weapon.id}`">
<div class="ph-pool-icon" @click="toWeapon(item.weapon)">
<TItemBox
v-if="item.info"
:model-value="getWeaponBox(item.info)"
:title="item.info.name"
/>
<img v-else :src="item.weapon.icon" alt="icon" />
</div>
</template>
</div>
<Swiper
v-else
:autoplay="{ delay: 1000, disableOnInteraction: false, stopOnLastSlide: false }"
:centered-slides="false"
:loop="true"
:modules="swiperModules"
:navigation="true"
:slides-per-view="4"
:space-between="8"
class="ph-pool-avatars swiper"
>
<SwiperSlide
v-for="item in avatarItems"
:key="`av-${item.avatar.id}`"
class="ph-pool-icon"
@click="toAvatar(item.avatar)"
>
<TItemBox
v-if="item.info"
:model-value="getAvatarBox(item.info)"
:title="item.info.name"
/>
<img v-else :src="item.avatar.icon" alt="icon" />
</SwiperSlide>
<SwiperSlide
v-for="item in weaponItems"
:key="`wp-${item.weapon.id}`"
class="ph-pool-icon"
@click="toWeapon(item.weapon)"
>
<TItemBox
v-if="item.info"
:model-value="getWeaponBox(item.info)"
:title="item.info.name"
/>
<img v-else :src="item.weapon.icon" alt="icon" />
</SwiperSlide>
</Swiper>
<div class="ph-pool-info">
<div class="ph-pool-time">
<v-icon>mdi-calendar-clock</v-icon>
<span>{{ startTime }} ~ {{ endTime }}</span>
</div>
<v-progress-linear :model-value="percent" :rounded="true" color="var(--tgc-od-green)" />
<div v-if="restTs > durationTs" class="ph-pool-stat">未开始</div>
<div v-else-if="restTs > 0" class="ph-pool-stat">
剩余时间{{ stamp2LastTime(restTs) }}
</div>
<div v-else class="ph-pool-stat">已结束</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import TItemBox, { TItemBoxData } from "@comp/app/t-itemBox.vue";
import showSnackbar from "@comp/func/snackbar.js";
import postReq from "@req/postReq.js";
import useHomeStore from "@store/home.js";
import { parsePost } from "@utils/linkParser.js";
import TGHttps from "@utils/TGHttps.js";
import TGLogger from "@utils/TGLogger.js";
import { createPost } from "@utils/TGWindow.js";
import { getWikiBrief, stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
import { str2Color } from "@utils/colorFunc.js";
import { openUrl } from "@tauri-apps/plugin-opener";
import { storeToRefs } from "pinia";
import { A11y, Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import gameEnum from "@enum/game.js";
type PhPoolUserProps = { pool: TGApp.Game.ActCalendar.ActPool };
type AvatarItem = {
avatar: TGApp.Game.ActCalendar.ActPoolAvatar;
info?: TGApp.App.Character.WikiBriefInfo;
};
type WeaponItem = {
weapon: TGApp.Game.ActCalendar.ActPoolWeapon;
info?: TGApp.App.Weapon.WikiBriefInfo;
};
// eslint-disable-next-line no-undef
let timer: NodeJS.Timeout | null = null;
const swiperModules = [Autoplay, A11y];
const { poolCover } = storeToRefs(useHomeStore());
const router = useRouter();
const props = defineProps<PhPoolUserProps>();
const cover = ref<string>();
const endTs = ref<number>(0);
const restTs = ref<number>(0);
const durationTs = ref<number>(0);
const totalItems = computed<number>(() => props.pool.avatars.length + props.pool.weapons.length);
const avatarItems = computed<Array<AvatarItem>>(() =>
props.pool.avatars.map((av) => {
const info = getAvatarInfo(av.id);
return { avatar: av, info };
}),
);
const weaponItems = computed<Array<WeaponItem>>(() =>
props.pool.weapons.map((wp) => {
const info = getWeaponInfo(wp.id);
return { weapon: wp, info };
}),
);
const percent = computed<number>(() => {
if (restTs.value > durationTs.value) return 100;
if (durationTs.value === 0) return 0;
return (restTs.value * 100) / durationTs.value;
});
const startTime = computed<string>(() =>
timestampToDate(Number(props.pool.start_timestamp) * 1000),
);
const endTime = computed<string>(() => timestampToDate(Number(props.pool.end_timestamp) * 1000));
const poolTypeLabel = computed<string>(() => {
const poolTypeMap: Record<TGApp.Game.ActCalendar.PoolTypeEnum, string> = {
[gameEnum.actCalendar.poolType.Avatar]: "角色活动祈愿",
[gameEnum.actCalendar.poolType.Weapon]: "武器活动祈愿",
[gameEnum.actCalendar.poolType.Mixed]: "集录祈愿",
};
return poolTypeMap[props.pool.pool_type] ?? "未知类型祈愿";
});
const typeBg = computed<string>(() =>
str2Color(`${props.pool.pool_name}${poolTypeLabel.value}`, -60),
);
onMounted(async () => {
await loadCover();
endTs.value = Number(props.pool.end_timestamp) * 1000;
restTs.value = endTs.value - Date.now();
durationTs.value = endTs.value - Number(props.pool.start_timestamp) * 1000;
if (restTs.value > 0) {
if (timer !== null) clearInterval(timer);
timer = setInterval(handlePosition, 1000);
}
});
async function loadCover(): Promise<void> {
const jumpUrl = props.pool.jump_url;
if (!jumpUrl || jumpUrl === "") return;
const postIdStr = await parsePost(jumpUrl);
if (postIdStr === false) return;
const postId = Number(postIdStr);
if (isNaN(postId)) return;
if (poolCover.value && postId in poolCover.value && poolCover.value[postId] !== "") {
cover.value = poolCover.value[postId];
return;
}
let resp: TGApp.BBS.Post.FullResp | undefined;
try {
resp = await postReq.post(postId, {});
if (resp.retcode !== 0) {
showSnackbar.error(`[PhPoolUser][${resp.retcode}] ${resp.message}`);
await TGLogger.Warn(`[PhPoolUser][${resp.retcode}] ${resp.message}`);
return;
}
} catch (e) {
const errMsg = TGHttps.getErrMsg(e);
showSnackbar.error(`获取帖子封面失败:${errMsg}`);
await TGLogger.Error("[PhPoolUser] 获取帖子封面异常");
await TGLogger.Error(`[PhPoolUser] ${typeof e === "object" ? JSON.stringify(e) : e}`);
return;
}
let coverGet: string | undefined;
if (resp.data.post.cover) coverGet = resp.data.post.cover.url;
else if (resp.data.post.post.cover && resp.data.post.post.cover !== "") {
coverGet = resp.data.post.post.cover;
} else if (resp.data.post.post.images.length > 0) {
coverGet = resp.data.post.post.images[0];
} else {
coverGet = "";
}
cover.value = coverGet;
if (!poolCover.value) poolCover.value = { [postId]: coverGet };
else poolCover.value[postId] = coverGet;
}
function handlePosition(): void {
if (restTs.value < 1) {
if (timer !== null) clearInterval(timer);
timer = null;
restTs.value = 0;
return;
}
restTs.value = endTs.value - Date.now();
}
async function toAvatar(avatar: TGApp.Game.ActCalendar.ActPoolAvatar): Promise<void> {
const info = getAvatarInfo(avatar.id);
if (!info) {
showSnackbar.warn(`${avatar.name} 角色图鉴暂未收录`);
return;
}
await router.push({ name: "角色图鉴", params: { id: avatar.id } });
}
async function toWeapon(weapon: TGApp.Game.ActCalendar.ActPoolWeapon): Promise<void> {
const info = getWeaponInfo(weapon.id);
if (!info) {
showSnackbar.warn(`${weapon.name} 武器图鉴暂未收录`);
return;
}
await router.push({ name: "武器图鉴", params: { id: weapon.id } });
}
function getAvatarInfo(id: number): TGApp.App.Character.WikiBriefInfo | undefined {
const info = getWikiBrief(id);
if (info === false) return undefined;
if ("element" in info) return <TGApp.App.Character.WikiBriefInfo>info;
return undefined;
}
function getWeaponInfo(id: number): TGApp.App.Weapon.WikiBriefInfo | undefined {
const info = getWikiBrief(id);
if (info === false) return undefined;
if ("weapon" in info) return <TGApp.App.Weapon.WikiBriefInfo>info;
return undefined;
}
function getAvatarBox(info: TGApp.App.Character.WikiBriefInfo): TItemBoxData {
return {
bg: `/icon/bg/${info.star}-Star.webp`,
icon: `/WIKI/character/${info.id}.webp`,
size: "60px",
height: "60px",
display: "inner",
clickable: true,
lt: `/icon/element/${info.element}元素.webp`,
ltSize: "15px",
innerHeight: 20,
innerIcon: `/icon/weapon/${info.weapon}.webp`,
innerText: info.name,
};
}
function getWeaponBox(info: TGApp.App.Weapon.WikiBriefInfo): TItemBoxData {
return {
bg: `/icon/bg/${info.star}-Star.webp`,
icon: `/WIKI/weapon/${info.id}.webp`,
size: "60px",
height: "60px",
display: "inner",
clickable: true,
lt: `/icon/weapon/${info.weapon}.webp`,
ltSize: "15px",
innerHeight: 20,
innerText: info.name,
};
}
async function toPool(): Promise<void> {
const jumpUrl = props.pool.jump_url;
if (!jumpUrl || jumpUrl === "") {
showSnackbar.warn("暂无跳转链接");
return;
}
const postIdStr = await parsePost(jumpUrl);
if (postIdStr !== false) {
const postId = Number(postIdStr);
if (!isNaN(postId)) {
await createPost(postId, props.pool.pool_name);
return;
}
}
await openUrl(jumpUrl);
}
</script>
<style lang="scss" scoped>
.ph-pool-user-card {
position: relative;
overflow: hidden;
width: 100%;
border-radius: 4px;
aspect-ratio: 69 / 32;
box-shadow: 0 2px 4px var(--common-shadow-2);
}
.ph-pool-type {
position: absolute;
z-index: 10;
top: 0;
right: 0;
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
padding: 2px 4px;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: v-bind(typeBg); /* stylelint-disable-line value-keyword-case */
border-bottom-left-radius: 4px;
border-top-right-radius: 4px;
box-shadow: 1px 1px 6px var(--tgc-dark-1);
color: var(--tgc-white-1);
cursor: pointer;
font-size: 13px;
text-shadow: 0 0 4px var(--tgc-dark-1);
span:last-child:not(:first-child) {
font-family: var(--font-title);
}
}
.ph-pool-cover {
position: relative;
display: flex;
overflow: hidden;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
border-radius: 4px;
background: var(--box-bg-1); /* stylelint-disable-line value-keyword-case */
cursor: pointer;
.empty {
width: 64px;
height: 64px;
img {
width: 100%;
height: 100%;
}
}
img {
width: 100%;
border-radius: 4px;
transition: all 0.5s;
&:hover {
transform: scale(1.1);
transition: all 0.5s;
}
}
}
.ph-pool-bottom {
position: absolute;
bottom: 0;
left: 0;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
box-shadow: 0 -2px 10px var(--common-shadow-2);
}
.ph-pool-avatars {
position: relative;
display: flex;
width: auto;
max-width: 280px;
height: 60px;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
margin: 8px;
gap: 8px;
&.swiper {
width: 280px;
}
&::-webkit-scrollbar-thumb {
background: var(--common-shadow-t-4);
}
}
.ph-pool-icon {
width: 60px;
height: 60px;
img {
position: absolute;
width: 60px;
height: 60px;
border-radius: 4px;
cursor: pointer;
}
}
.ph-pool-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
margin-right: 10px;
color: var(--tgc-white-1);
font-size: 12px;
gap: 8px;
text-align: left;
text-shadow: 0 0 4px var(--tgc-dark-1);
}
.ph-pool-time {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;
column-gap: 4px;
:first-child {
color: var(--tgc-od-orange);
}
}
.ph-pool-stat {
margin-left: auto;
}
</style>

View File

@@ -122,6 +122,7 @@ const showItemsAll = shallowRef<Array<string>>(["素材日历", "限时祈愿",
const oldItems = shallowRef<Array<string>>([]);
onMounted(async () => {
homeStore.actCalendarData = undefined;
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.

View File

@@ -201,19 +201,23 @@ async function hardChallengeDetail(
/**
* 获取活动日历数据
* @since Beta v0.10.1
* @since Beta v0.10.2
* @param cookie - Cookie
* @param user - 用户
* @param challenge - 极验验证信息(可选)
* @returns 活动日历响应数据
*/
async function actCalendar(
cookie: TGApp.App.Account.Cookie,
user: TGApp.Sqlite.Account.Game,
challenge?: string,
): Promise<TGApp.Game.ActCalendar.ActResp> {
const ck = { account_id: cookie.account_id, cookie_token: cookie.cookie_token };
const body = { role_id: user.gameUid, server: user.region };
const headers = getRequestHeader(ck, "POST", body);
if (challenge) headers["x-rpc-challenge"] = challenge;
const resp = await TGHttps.post<TGApp.Game.ActCalendar.ActResp>(`${trgAbu}act_calendar`, {
headers: getRequestHeader(ck, "POST", body),
headers,
body: JSON.stringify(body),
});
return resp.data;

View File

@@ -1,6 +1,6 @@
/**
* 首页组件状态
* @since Beta v0.10.1
* @since Beta v0.10.2
*/
import { defineStore } from "pinia";
@@ -19,6 +19,7 @@ const defaultHomeShow: Array<TGApp.Store.Home.ShowItem> = [
const useHomeStore = defineStore("home", () => {
const homeShow = ref<Array<TGApp.Store.Home.ShowItem>>(defaultHomeShow);
const poolCover = ref<Record<number, string>>();
const actCalendarData = ref<TGApp.Game.ActCalendar.ActRes>();
function getShowItems(): Array<string> {
const homeShowLocal = localStorage.getItem("homeShow");
@@ -72,7 +73,7 @@ const useHomeStore = defineStore("home", () => {
localStorage.setItem("homeShow", JSON.stringify(homeShow.value));
}
return { poolCover, getShowItems, setShowItems, init };
return { poolCover, actCalendarData, getShowItems, setShowItems, init };
});
export default useHomeStore;