mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-19 04:29:45 +08:00
✨ 获取近期活动
This commit is contained in:
@@ -1,40 +1,106 @@
|
||||
<template>
|
||||
<THomeCard>
|
||||
<THomeCard :append="isLogin">
|
||||
<template #title>近期活动</template>
|
||||
<template #title-append v-if="isLogin">
|
||||
<v-switch class="tp-switch" v-model="isUserPos"></v-switch>
|
||||
<span>{{ isUserPos ? "用户" : "百科" }}</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="position-grid">
|
||||
<PhPositionCard v-for="(card, index) in positions" :key="index" :pos="card" />
|
||||
<div class="tp-grid" v-show="!isUserPos">
|
||||
<PhPosObc v-for="(card, index) in obsPos" :key="index" :pos="card" />
|
||||
</div>
|
||||
<div class="tp-grid" v-show="isUserPos">
|
||||
<PhPosUser v-for="(card, index) in userPos" :key="index" :pos="card" />
|
||||
</div>
|
||||
</template>
|
||||
</THomeCard>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import PhPositionCard from "@comp/pageHome/ph-position-card.vue";
|
||||
import PhPosObc from "@comp/pageHome/ph-pos-obc.vue";
|
||||
import PhPosUser from "@comp/pageHome/ph-pos-user.vue";
|
||||
import recordReq from "@req/recordReq.js";
|
||||
import takumiReq from "@req/takumiReq.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { onMounted, shallowRef } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, shallowRef, ref, watch } from "vue";
|
||||
|
||||
import THomeCard from "./ph-comp-card.vue";
|
||||
|
||||
type TPositionEmits = (e: "success") => void;
|
||||
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
const { cookie, account } = storeToRefs(useUserStore());
|
||||
|
||||
const emits = defineEmits<TPositionEmits>();
|
||||
const positions = shallowRef<Array<TGApp.BBS.Obc.PositionItem>>([]);
|
||||
|
||||
const isInit = ref<boolean>(false);
|
||||
const isUserPos = ref<boolean>(isLogin.value);
|
||||
const obsPos = shallowRef<Array<TGApp.BBS.Obc.PositionItem>>([]);
|
||||
const userPos = shallowRef<Array<TGApp.Game.ActCalendar.ActItem>>([]);
|
||||
|
||||
watch(
|
||||
() => isUserPos.value,
|
||||
async () => {
|
||||
if (isUserPos.value) await loadUserPosition();
|
||||
else await loadWikiPosition();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
if (isLogin.value) await loadUserPosition();
|
||||
else await loadWikiPosition();
|
||||
emits("success");
|
||||
isInit.value = true;
|
||||
});
|
||||
|
||||
async function loadUserPosition(): Promise<void> {
|
||||
if (userPos.value.length > 0) return;
|
||||
if (!cookie.value) {
|
||||
showSnackbar.warn("获取近期活动失败:未登录");
|
||||
isLogin.value = false;
|
||||
return;
|
||||
}
|
||||
if (isInit.value) await showLoading.start("正在获取近期活动");
|
||||
const resp = await recordReq.actCalendar(cookie.value, account.value);
|
||||
if (isInit.value) await showLoading.end();
|
||||
if ("retcode" in resp) {
|
||||
showSnackbar.error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
||||
await TGLogger.Error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
||||
return;
|
||||
}
|
||||
userPos.value = [...resp.act_list, ...resp.fixed_act_list];
|
||||
}
|
||||
|
||||
async function loadWikiPosition(): Promise<void> {
|
||||
if (obsPos.value.length > 0) return;
|
||||
if (isInit.value) await showLoading.start("正在加载近期活动");
|
||||
const resp = await takumiReq.obc.position();
|
||||
if (Array.isArray(resp)) positions.value = resp;
|
||||
else {
|
||||
if (isInit.value) await showLoading.end();
|
||||
if (Array.isArray(resp)) {
|
||||
obsPos.value = resp;
|
||||
if (resp.length === 0) showSnackbar.warn("暂无近期活动");
|
||||
} else {
|
||||
showSnackbar.error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
||||
await TGLogger.Error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
||||
}
|
||||
emits("success");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.position-grid {
|
||||
.tp-switch {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.tp-grid {
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(calc(400px), 0.5fr));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ph-position-card">
|
||||
<div class="ph-pos-obc-card">
|
||||
<div class="top">
|
||||
<div class="main">
|
||||
<div class="left" @click="openPosition()">
|
||||
@@ -40,11 +40,11 @@ import { toObcPage, createPost } from "@utils/TGWindow.js";
|
||||
import { stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
type PhPositionCardProps = { pos: TGApp.BBS.Obc.PositionItem };
|
||||
type PhPosObcProps = { pos: TGApp.BBS.Obc.PositionItem };
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
const props = defineProps<PhPositionCardProps>();
|
||||
const props = defineProps<PhPosObcProps>();
|
||||
const endTs = ref<number>(0);
|
||||
const restTs = ref<number>(0);
|
||||
const durationTs = ref<number>(0);
|
||||
@@ -93,7 +93,7 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-position-card {
|
||||
.ph-pos-obc-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
198
src/components/pageHome/ph-pos-user.vue
Normal file
198
src/components/pageHome/ph-pos-user.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="ph-pos-user-card">
|
||||
<div class="ph-puc-top">
|
||||
<div class="title">
|
||||
<v-icon title="已完成" color="var(--tgc-od-green)" v-if="props.pos.is_finished">
|
||||
mdi-checkbox-marked-circle-outline
|
||||
</v-icon>
|
||||
<v-icon v-else title="未完成" color="var(--tgc-od-white)">mdi-circle</v-icon>
|
||||
<span>{{ props.pos.name }}</span>
|
||||
</div>
|
||||
<!-- TODO: 枚举 -->
|
||||
<div class="subtitle">
|
||||
<template v-if="props.pos.type === 'ActTypeExplore'">
|
||||
<span>当前区域探索度: {{ props.pos.explore_detail.explore_percent }}%</span>
|
||||
</template>
|
||||
<template v-else-if="props.pos.type === 'ActTypeDouble'">
|
||||
<span>
|
||||
剩余双倍次数: {{ props.pos.double_detail.left }}/{{ props.pos.double_detail.total }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ph-puc-duration">
|
||||
<span>{{ stamp2LastTime(restTs * 1000) }}</span>
|
||||
<span>
|
||||
{{ timestampToDate(Number(props.pos.start_timestamp) * 1000) }} ~
|
||||
{{ timestampToDate(Number(props.pos.end_timestamp) * 1000) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ph-puc-rewards">
|
||||
<div
|
||||
:title="`${reward.name}${reward.num > 0 ? `x${reward.num}` : ''}`"
|
||||
v-for="reward in props.pos.reward_list"
|
||||
:key="reward.item_id"
|
||||
class="ph-puc-reward"
|
||||
>
|
||||
<TMiImg :src="`/icon/bg/${reward.rarity}-Star.webp`" class="bg" alt="bg" />
|
||||
<TMiImg :ori="true" :alt="reward.name" :src="reward.icon" class="icon" />
|
||||
<span v-if="reward.num > 0" class="count">{{ reward.num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import { stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
type PhCompPositionUserProps = { pos: TGApp.Game.ActCalendar.ActItem };
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
const props = defineProps<PhCompPositionUserProps>();
|
||||
const endTs = ref<number>(0);
|
||||
const restTs = ref<number>(0);
|
||||
const durationTs = ref<number>(0);
|
||||
|
||||
onMounted(() => {
|
||||
endTs.value = Number(props.pos.end_timestamp);
|
||||
restTs.value = props.pos.countdown_seconds || 0;
|
||||
if (restTs.value > 0) {
|
||||
durationTs.value = endTs.value - Number(props.pos.start_timestamp);
|
||||
}
|
||||
if (timer !== null) clearInterval(timer);
|
||||
timer = setInterval(handlePosition, 1000);
|
||||
});
|
||||
|
||||
function handlePosition(): void {
|
||||
if (restTs.value < 1) {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
timer = null;
|
||||
restTs.value = 0;
|
||||
return;
|
||||
}
|
||||
restTs.value = endTs.value - Math.floor(Date.now() / 1000);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-pos-user-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-1);
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
.ph-puc-top {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
column-gap: 8px;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
column-gap: 4px;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
column-gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.ph-puc-duration {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
color: var(--box-text-2);
|
||||
column-gap: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
span:last-child {
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.ph-puc-rewards {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ph-puc-reward {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
cursor: pointer;
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
z-index: 1;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.count {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgb(0 0 0 / 50%);
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:hover .icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user