mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-05-21 05:25:45 +08:00
✨ 获取近期活动
This commit is contained in:
@@ -1,40 +1,106 @@
|
|||||||
<template>
|
<template>
|
||||||
<THomeCard>
|
<THomeCard :append="isLogin">
|
||||||
<template #title>近期活动</template>
|
<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>
|
<template #default>
|
||||||
<div class="position-grid">
|
<div class="tp-grid" v-show="!isUserPos">
|
||||||
<PhPositionCard v-for="(card, index) in positions" :key="index" :pos="card" />
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</THomeCard>
|
</THomeCard>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import showLoading from "@comp/func/loading.js";
|
||||||
import showSnackbar from "@comp/func/snackbar.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 takumiReq from "@req/takumiReq.js";
|
||||||
|
import useAppStore from "@store/app.js";
|
||||||
|
import useUserStore from "@store/user.js";
|
||||||
import TGLogger from "@utils/TGLogger.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";
|
import THomeCard from "./ph-comp-card.vue";
|
||||||
|
|
||||||
type TPositionEmits = (e: "success") => void;
|
type TPositionEmits = (e: "success") => void;
|
||||||
|
|
||||||
|
const { isLogin } = storeToRefs(useAppStore());
|
||||||
|
const { cookie, account } = storeToRefs(useUserStore());
|
||||||
|
|
||||||
const emits = defineEmits<TPositionEmits>();
|
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 () => {
|
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();
|
const resp = await takumiReq.obc.position();
|
||||||
if (Array.isArray(resp)) positions.value = resp;
|
if (isInit.value) await showLoading.end();
|
||||||
else {
|
if (Array.isArray(resp)) {
|
||||||
|
obsPos.value = resp;
|
||||||
|
if (resp.length === 0) showSnackbar.warn("暂无近期活动");
|
||||||
|
} else {
|
||||||
showSnackbar.error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
showSnackbar.error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
||||||
await TGLogger.Error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
await TGLogger.Error(`获取近期活动失败:[${resp.retcode}-${resp.message}`);
|
||||||
}
|
}
|
||||||
emits("success");
|
}
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<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;
|
display: grid;
|
||||||
grid-gap: 12px;
|
gap: 12px;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(400px), 0.5fr));
|
grid-template-columns: repeat(auto-fill, minmax(calc(400px), 0.5fr));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ph-position-card">
|
<div class="ph-pos-obc-card">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="left" @click="openPosition()">
|
<div class="left" @click="openPosition()">
|
||||||
@@ -40,11 +40,11 @@ import { toObcPage, createPost } from "@utils/TGWindow.js";
|
|||||||
import { stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
|
import { stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
|
||||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
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
|
// eslint-disable-next-line no-undef
|
||||||
let timer: NodeJS.Timeout | null = null;
|
let timer: NodeJS.Timeout | null = null;
|
||||||
const props = defineProps<PhPositionCardProps>();
|
const props = defineProps<PhPosObcProps>();
|
||||||
const endTs = ref<number>(0);
|
const endTs = ref<number>(0);
|
||||||
const restTs = ref<number>(0);
|
const restTs = ref<number>(0);
|
||||||
const durationTs = ref<number>(0);
|
const durationTs = ref<number>(0);
|
||||||
@@ -93,7 +93,7 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.ph-position-card {
|
.ph-pos-obc-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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