feat: improve home notice ticker
This commit is contained in:
@@ -524,19 +524,57 @@ body {
|
||||
font-weight: 500;
|
||||
}
|
||||
.m-notice {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
border: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
color: #6c5a45;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.m-notice p {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.m-notice-tag {
|
||||
flex: none;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
color: #8d5b17;
|
||||
background: #fff2d8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.m-notice-tag.is-activity {
|
||||
color: #24704b;
|
||||
background: #e9f7ef;
|
||||
}
|
||||
.m-notice-copy {
|
||||
animation: m-notice-enter 0.32s ease-out;
|
||||
}
|
||||
.m-notice-arrow {
|
||||
flex: none;
|
||||
color: #b4a590;
|
||||
font-size: 17px;
|
||||
}
|
||||
@keyframes m-notice-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.m-section {
|
||||
margin-top: 12px;
|
||||
padding: 17px 16px;
|
||||
|
||||
@@ -53,10 +53,22 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="home?.notices?.length" class="m-notice m-card">
|
||||
<button
|
||||
v-if="currentNotice"
|
||||
class="m-notice m-card"
|
||||
type="button"
|
||||
aria-label="查看全部通知"
|
||||
@click="router.push('/app/content/notices')"
|
||||
>
|
||||
<n-icon :component="MegaphoneOutline" />
|
||||
<p>{{ home.notices[0].title || home.notices[0].content }}</p>
|
||||
</div>
|
||||
<span :class="['m-notice-tag', { 'is-activity': currentNotice.source_type === 'operational_simulation' }]">
|
||||
{{ currentNotice.source_type === 'operational_simulation' ? '活动' : '公告' }}
|
||||
</span>
|
||||
<p :key="currentNotice.id" class="m-notice-copy">
|
||||
{{ currentNotice.title || currentNotice.content || '查看最新通知' }}
|
||||
</p>
|
||||
<n-icon class="m-notice-arrow" :component="ChevronForwardOutline" />
|
||||
</button>
|
||||
|
||||
<section class="m-section m-card m-home-ranking">
|
||||
<div class="m-section-title"
|
||||
@@ -101,7 +113,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { SearchOutline, MegaphoneOutline } from '@vicons/ionicons5';
|
||||
import { ChevronForwardOutline, MegaphoneOutline, SearchOutline } from '@vicons/ionicons5';
|
||||
import AsyncState from '../components/AsyncState.vue';
|
||||
import AnnouncementPopup, {
|
||||
type AnnouncementNotice,
|
||||
@@ -121,7 +133,7 @@
|
||||
wallet: { available_amount: number; frozen_amount: number; currency: string };
|
||||
features: Record<string, boolean>;
|
||||
banners: BannerData[];
|
||||
notices: Array<{ title?: string; content?: string }>;
|
||||
notices: NoticeData[];
|
||||
task_summary: {
|
||||
today_count: number;
|
||||
today_commission_amount: number;
|
||||
@@ -135,6 +147,12 @@
|
||||
target_type: 'none' | 'internal_route' | 'external_url';
|
||||
target_value?: string;
|
||||
}
|
||||
interface NoticeData {
|
||||
id: number;
|
||||
title?: string;
|
||||
content?: string;
|
||||
source_type?: string;
|
||||
}
|
||||
interface LeaderboardEntry {
|
||||
rank: number;
|
||||
username: string;
|
||||
@@ -149,12 +167,15 @@
|
||||
const leaderboard = ref<LeaderboardEntry[]>([]);
|
||||
const popupNotice = ref<AnnouncementNotice | null>(null);
|
||||
const activeBannerIndex = ref(0);
|
||||
const activeNoticeIndex = ref(0);
|
||||
let bannerTimer: number | undefined;
|
||||
let noticeTimer: number | undefined;
|
||||
const formatPoints = (value: number) =>
|
||||
new Intl.NumberFormat('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(
|
||||
(value || 0) / 100
|
||||
);
|
||||
const currentBanner = computed(() => home.value?.banners?.[activeBannerIndex.value]);
|
||||
const currentNotice = computed(() => home.value?.notices?.[activeNoticeIndex.value]);
|
||||
const bannerStyle = computed(() => {
|
||||
const url = safeImage(currentBanner.value?.image_url);
|
||||
return {
|
||||
@@ -234,6 +255,18 @@
|
||||
}, 4500);
|
||||
}
|
||||
}
|
||||
|
||||
function restartNoticeTimer() {
|
||||
if (noticeTimer) window.clearInterval(noticeTimer);
|
||||
noticeTimer = undefined;
|
||||
const count = home.value?.notices?.length || 0;
|
||||
if (count > 1) {
|
||||
noticeTimer = window.setInterval(() => {
|
||||
activeNoticeIndex.value = (activeNoticeIndex.value + 1) % count;
|
||||
}, 4000);
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
@@ -269,8 +302,13 @@
|
||||
activeBannerIndex.value = 0;
|
||||
restartBannerTimer();
|
||||
});
|
||||
watch(() => home.value?.notices?.length, () => {
|
||||
activeNoticeIndex.value = 0;
|
||||
restartNoticeTimer();
|
||||
});
|
||||
onMounted(load);
|
||||
onBeforeUnmount(() => {
|
||||
if (bannerTimer) window.clearInterval(bannerTimer);
|
||||
if (noticeTimer) window.clearInterval(noticeTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user