910 lines
21 KiB
Vue
910 lines
21 KiB
Vue
<template>
|
||
<view class="center-container">
|
||
<!-- 顶部导航 -->
|
||
<!-- <Header
|
||
:show-back="true"
|
||
backIconColor="#3a2540"
|
||
:showGuideIcon="false"
|
||
:showTaskIcon="false"
|
||
:showStarActivityIcon="false"
|
||
/> -->
|
||
<view class="nav-back" @tap="goBack">
|
||
<text class="nav-back-icon">←</text>
|
||
</view>
|
||
<!-- 状态栏占位 -->
|
||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||
|
||
<!-- 顶部毛绒装饰区:直接用项目里现成的 centerhbj.png -->
|
||
<view class="hero-section">
|
||
<image
|
||
class="hero-bg-img"
|
||
src="/static/rank/centerhbj.png"
|
||
mode="aspectFill"
|
||
/>
|
||
</view>
|
||
<view class="main-box">
|
||
<!-- 统计条:3 个独立渐变模块背景(对应 Figma 108:567 / 108:579 / 108:581) -->
|
||
<view class="stats-strip">
|
||
<view class="stat-card stat-card-1">
|
||
<image
|
||
class="image-1"
|
||
src="/static/rank/hd.png"
|
||
mode="aspectFit"
|
||
></image>
|
||
<text class="stat-label">已参与活动</text>
|
||
<text class="stat-value">{{ stats.participated }}</text>
|
||
</view>
|
||
<view class="stat-card stat-card-2">
|
||
<image
|
||
class="image-2"
|
||
src="/static/icon/crystal.png"
|
||
mode="aspectFit"
|
||
></image>
|
||
<text class="stat-label">水晶贡献数</text>
|
||
<text class="stat-value">{{ formattedContributions }}</text>
|
||
</view>
|
||
<view class="stat-card stat-card-3">
|
||
<image
|
||
class="image-3"
|
||
src="/static/rank/lsph.png"
|
||
mode="aspectFit"
|
||
></image>
|
||
<text class="stat-label">历史最高排名</text>
|
||
<text class="stat-value">{{ formattedBestRank }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 标签切换 -->
|
||
<view class="tab-bar">
|
||
<view
|
||
v-for="tab in tabs"
|
||
:key="tab.key"
|
||
class="tab-item"
|
||
:class="{ 'tab-active': activeTab === tab.key }"
|
||
@click="activeTab = tab.key"
|
||
>
|
||
<text
|
||
class="tab-text"
|
||
:class="{ 'tab-text-active': activeTab === tab.key }"
|
||
>{{ tab.label }}</text
|
||
>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 活动列表 -->
|
||
<scroll-view
|
||
class="activity-scroll"
|
||
scroll-y
|
||
:refresher-enabled="true"
|
||
:refresher-triggered="refreshing"
|
||
@refresherrefresh="onRefresh"
|
||
@scrolltolower="onScrollToLower"
|
||
>
|
||
<view class="activity-list">
|
||
<view
|
||
v-for="item in filteredActivities"
|
||
:key="item.id"
|
||
class="activity-card"
|
||
@click="handleCardTap(item)"
|
||
>
|
||
<!-- 左侧封面 -->
|
||
<view class="card-cover">
|
||
<image
|
||
v-if="item.banner_image"
|
||
:src="item.banner_image"
|
||
class="cover-img"
|
||
mode="aspectFill"
|
||
:lazy-load="true"
|
||
/>
|
||
<view v-else class="cover-fallback">{{
|
||
item.title || "活动"
|
||
}}</view>
|
||
<view
|
||
v-if="item.status === 'expired' || item.status === 'completed'"
|
||
class="cover-ended"
|
||
>
|
||
<text class="ended-text">活动已结束</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 右侧信息 -->
|
||
<view class="card-info">
|
||
<view class="info-row">
|
||
<view class="year-pill">
|
||
<text class="year-text">{{ getYear(item) }}</text>
|
||
<text class="name-text">{{ getShortName(item) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- <view class="date-row">
|
||
<text class="date-text">{{ formatDateRange(item) }}</text>
|
||
<text v-if="!isEnded(item)" class="countdown-text">
|
||
距离活动结束还有{{ getDaysLeft(item) }}天
|
||
</text>
|
||
</view> -->
|
||
|
||
<view class="stat-row">
|
||
<view class="stat-block">
|
||
<text class="block-value">{{
|
||
formatStatValue(item.my_contribution)
|
||
}}</text>
|
||
<text class="block-label">水晶贡献数</text>
|
||
<image
|
||
class="stat-image"
|
||
src="/static/icon/crystal.png"
|
||
mode="aspectFit"
|
||
></image>
|
||
</view>
|
||
<view class="stat-block">
|
||
<text class="block-value">{{
|
||
formatRank(item.my_rank)
|
||
}}</text>
|
||
<text class="block-label">排名</text>
|
||
<image
|
||
class="block-image"
|
||
src="/static/rank/ph.png"
|
||
mode="aspectFit"
|
||
></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
v-if="filteredActivities.length === 0 && !isLoading"
|
||
class="empty-block"
|
||
>
|
||
<text class="empty-text">暂无活动</text>
|
||
</view>
|
||
|
||
<view v-if="filteredActivities.length > 0" class="footer-end">
|
||
<text class="footer-text">已经到底啦~</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 加载/错误遮罩 -->
|
||
<view v-if="isLoading" class="loading-mask">
|
||
<view class="loading-spinner"></view>
|
||
<text class="loading-text">加载中…</text>
|
||
</view>
|
||
<view v-if="errorMessage" class="error-mask">
|
||
<text class="error-title">加载失败</text>
|
||
<text class="error-msg">{{ errorMessage }}</text>
|
||
<view class="retry-btn" @click="loadData">
|
||
<text class="retry-text">重试</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted } from "vue";
|
||
import { onShow } from "@dcloudio/uni-app";
|
||
import Header from "../components/Header.vue";
|
||
import { getActivityListApi, getActivityRankingApi } from "@/utils/api.js";
|
||
import screenCache from "@/utils/screen-cache";
|
||
|
||
const statusBarHeight = ref(0);
|
||
const isLoading = ref(true);
|
||
const refreshing = ref(false);
|
||
const errorMessage = ref("");
|
||
const activities = ref([]);
|
||
|
||
const activeTab = ref("all");
|
||
const tabs = [
|
||
{ key: "all", label: "全部" },
|
||
{ key: "active", label: "进行中" },
|
||
{ key: "ended", label: "已结束" },
|
||
];
|
||
|
||
// 顶部 3 项统计(聚合自活动列表)
|
||
const stats = ref({
|
||
participated: 0,
|
||
contributions: 0,
|
||
bestRank: 0,
|
||
});
|
||
|
||
const formattedContributions = computed(() =>
|
||
formatStatValue(stats.value.contributions),
|
||
);
|
||
const formattedBestRank = computed(() => {
|
||
const r = stats.value.bestRank;
|
||
if (!r) return "—";
|
||
if (r >= 10000) return `${Math.floor(r / 1000)}W+`;
|
||
return String(r);
|
||
});
|
||
|
||
const filteredActivities = computed(() => {
|
||
if (activeTab.value === "all") return activities.value;
|
||
if (activeTab.value === "active") {
|
||
return activities.value.filter((a) => a.status === "active");
|
||
}
|
||
return activities.value.filter(
|
||
(a) => a.status === "expired" || a.status === "completed",
|
||
);
|
||
});
|
||
|
||
const goBack = () => {
|
||
// 获取页面栈
|
||
const pages = getCurrentPages();
|
||
if (pages.length > 1) {
|
||
// 有上一页,执行返回
|
||
uni.navigateBack();
|
||
} else {
|
||
// 没有上一页,跳转到square页面
|
||
uni.reLaunch({
|
||
url: "/pages/square/square",
|
||
});
|
||
}
|
||
};
|
||
|
||
function isEnded(item) {
|
||
return item.status === "expired" || item.status === "completed";
|
||
}
|
||
|
||
function getYear(item) {
|
||
const t = item.start_time || "";
|
||
return t.slice(0, 4) || "—";
|
||
}
|
||
|
||
function getShortName(item) {
|
||
// Figma 显示 "巴士星援会/生日星援会/星演会",对应 activity.theme
|
||
return item.theme || item.title || "活动";
|
||
}
|
||
|
||
function formatDateRange(item) {
|
||
const s = (item.start_time || "").slice(0, 10).replace(/-/g, ".");
|
||
const e = (item.end_time || "").slice(0, 10).replace(/-/g, ".");
|
||
if (s && e) return `${s}~${e}`;
|
||
if (s) return s;
|
||
return "—";
|
||
}
|
||
|
||
function getDaysLeft(item) {
|
||
if (!item.end_time) return 0;
|
||
const end = new Date(item.end_time.replace(/-/g, "/")).getTime();
|
||
const now = Date.now();
|
||
if (Number.isNaN(end) || end < now) return 0;
|
||
return Math.max(0, Math.ceil((end - now) / 86400000));
|
||
}
|
||
|
||
function formatStatValue(v) {
|
||
if (v === null || v === undefined || v === 0) return "0";
|
||
return String(v);
|
||
}
|
||
|
||
function formatRank(rank) {
|
||
if (rank === null || rank === undefined) return "—";
|
||
if (rank === 0) return "未上榜";
|
||
if (rank >= 10000) return "1W+";
|
||
return String(rank);
|
||
}
|
||
|
||
async function fetchWithRanking(list) {
|
||
// 并发拉取每个活动的「我的贡献/排名」
|
||
const starId = uni.getStorageSync("star_id") || null;
|
||
const tasks = list.map(async (a) => {
|
||
try {
|
||
const res = await getActivityRankingApi(a.id, starId, 1, 10);
|
||
if (res?.code === 0 && res.data) {
|
||
const mine = res.data.my_contribution;
|
||
return {
|
||
...a,
|
||
my_contribution: mine?.total_contribution ?? 0,
|
||
my_rank: mine?.rank ?? null,
|
||
};
|
||
}
|
||
} catch (e) {
|
||
console.error("[center] 拉取活动贡献失败", a.id, e?.message ?? e);
|
||
}
|
||
return { ...a, my_contribution: 0, my_rank: null };
|
||
});
|
||
return Promise.all(tasks);
|
||
}
|
||
|
||
function aggregate(merged) {
|
||
const participated = merged.length;
|
||
const contributions = merged.reduce(
|
||
(sum, a) => sum + (a.my_contribution || 0),
|
||
0,
|
||
);
|
||
const ranks = merged.map((a) => a.my_rank).filter((r) => r && r > 0);
|
||
const bestRank = ranks.length ? Math.min(...ranks) : 0;
|
||
stats.value = { participated, contributions, bestRank };
|
||
}
|
||
|
||
async function loadData() {
|
||
try {
|
||
isLoading.value = true;
|
||
errorMessage.value = "";
|
||
|
||
const starId = uni.getStorageSync("star_id");
|
||
if (!starId) {
|
||
throw new Error("缺少粉丝身份,请先选择明星");
|
||
}
|
||
|
||
// 1. 拉活动列表
|
||
const res = await getActivityListApi(starId, 1, 50);
|
||
if (res?.code !== 0) {
|
||
throw new Error(res?.message || "获取活动列表失败");
|
||
}
|
||
const list = res.data?.activities || [];
|
||
|
||
// 2. 并发拉每个活动的我的贡献/排名
|
||
const merged = await fetchWithRanking(list);
|
||
|
||
// 3. 按开始时间倒序(新→旧)
|
||
merged.sort((a, b) => {
|
||
const ta = a.start_time ? new Date(a.start_time).getTime() : 0;
|
||
const tb = b.start_time ? new Date(b.start_time).getTime() : 0;
|
||
return tb - ta;
|
||
});
|
||
|
||
activities.value = merged;
|
||
aggregate(merged);
|
||
} catch (e) {
|
||
console.error("[center] loadData failed", e);
|
||
errorMessage.value = e?.message || "加载失败";
|
||
} finally {
|
||
isLoading.value = false;
|
||
refreshing.value = false;
|
||
}
|
||
}
|
||
|
||
function onRefresh() {
|
||
refreshing.value = true;
|
||
loadData();
|
||
}
|
||
|
||
function onScrollToLower() {
|
||
// 已是全量加载,无更多分页
|
||
}
|
||
|
||
function handleCardTap(item) {
|
||
// 跳转到对应活动详情页
|
||
uni.navigateTo({
|
||
url: `/pages/support-activity/index?id=${item.id}`,
|
||
fail: (err) => {
|
||
console.error("[center] 跳转活动详情失败:", err);
|
||
},
|
||
});
|
||
}
|
||
|
||
onMounted(() => {
|
||
screenCache.init();
|
||
statusBarHeight.value = screenCache.getStatusBarHeight();
|
||
loadData();
|
||
});
|
||
|
||
onShow(() => {
|
||
// 每次回到页面刷新一次(贡献数可能有变化)
|
||
if (activities.value.length > 0) {
|
||
loadData();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.center-container {
|
||
position: relative;
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(255, 229, 229, 0.25) -32.49%,
|
||
rgba(243, 160, 161, 0.25) -32.49%,
|
||
rgba(255, 156, 156, 0.25) 86.46%,
|
||
rgba(255, 32, 36, 0.25) 180.79%
|
||
);
|
||
// filter: blur(2px);
|
||
|
||
&::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: url("/static/rank/centerbj.png") center no-repeat;
|
||
background-size: 100% 100%;
|
||
}
|
||
}
|
||
|
||
.nav-back {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
/* background: rgba(255,255,255,0.5);
|
||
border-radius: 50%; */
|
||
position: fixed;
|
||
top: 88rpx;
|
||
left: 32rpx;
|
||
z-index: 4;
|
||
}
|
||
|
||
.nav-back-icon {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-bar {
|
||
width: 100%;
|
||
background: transparent;
|
||
}
|
||
|
||
/* ============ 顶部毛绒装饰区 ============ */
|
||
.hero-section {
|
||
position: absolute;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 360rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.main-box {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin: 328rpx 10rpx 0;
|
||
padding: 0 0 40rpx;
|
||
min-height: 1324rpx;
|
||
border-radius: 28rpx;
|
||
background: linear-gradient(
|
||
180deg,
|
||
#ffd8dc 0%,
|
||
rgba(255, 90, 93, 0.2) 38%,
|
||
transparent 81.31%,
|
||
transparent 128.86%
|
||
);
|
||
// box-shadow: 0 12rpx 24rpx rgba(179, 50, 50, 0.15);
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.hero-bg-img {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* ============ 统计条 ============ */
|
||
.stats-strip {
|
||
position: relative;
|
||
margin: -80rpx 24rpx 0;
|
||
z-index: 5;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
// gap: 18rpx;
|
||
height: 200rpx;
|
||
padding: 0 16rpx;
|
||
}
|
||
|
||
.stat-card {
|
||
position: relative;
|
||
width: 200rpx;
|
||
height: 81.92rpx;
|
||
// padding-bottom: 12rpx;
|
||
border-radius: 38rpx 20rpx 14rpx 14rpx;
|
||
background: linear-gradient(
|
||
274deg,
|
||
rgba(168, 166, 237, 0.49) -9.28%,
|
||
rgba(136, 200, 216, 0.49) 61.89%,
|
||
rgba(243, 128, 239, 0.49) 106.57%
|
||
);
|
||
box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25);
|
||
z-index: 0;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 3 个模块各自的 ::before 背景图(按需替换路径) */
|
||
.image-1 {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: absolute;
|
||
left: -32rpx;
|
||
bottom: -8rpx;
|
||
}
|
||
.image-2 {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: absolute;
|
||
left: -32rpx;
|
||
bottom: -16rpx;
|
||
transform: rotate(-10deg);
|
||
opacity: 0.6;
|
||
}
|
||
.image-3 {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: absolute;
|
||
left: -32rpx;
|
||
bottom: 0;
|
||
transform: rotate(-2deg);
|
||
}
|
||
|
||
.stat-label {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 8rpx;
|
||
z-index: 2;
|
||
font-size: 16rpx;
|
||
font-weight: bold;
|
||
color: #fffabd;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
letter-spacing: 0.5rpx;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.stat-value {
|
||
position: absolute;
|
||
bottom: 4rpx;
|
||
right: 12rpx;
|
||
z-index: 2;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #fffabd;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
line-height: 1.1;
|
||
margin-top: 2rpx;
|
||
}
|
||
|
||
/* ============ 标签切换(对应 Figma 108:589 title组) ============ */
|
||
.tab-bar {
|
||
position: relative;
|
||
z-index: 5;
|
||
margin: 30rpx 128rpx 18rpx;
|
||
height: 64rpx;
|
||
border-radius: 32rpx;
|
||
background: rgba(217, 217, 217, 0.11);
|
||
box-shadow: 0 8rpx 8rpx 0 rgba(179, 50, 50, 0.25);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
padding: 0 8rpx;
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
height: 46rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 23rpx;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.tab-active {
|
||
background: linear-gradient(
|
||
90deg,
|
||
rgba(255, 222, 8, 0.55) 0%,
|
||
rgba(252, 100, 102, 0.85) 64%,
|
||
rgba(244, 88, 104, 0.85) 100%
|
||
);
|
||
box-shadow: 4rpx 4rpx 8rpx 0 rgba(242, 21, 21, 0.47);
|
||
}
|
||
|
||
.tab-text {
|
||
font-size: 26rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
text-shadow: 0 4rpx 6rpx rgba(0, 0, 0, 0.55);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.tab-text-active {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* ============ 活动列表 ============ */
|
||
.activity-scroll {
|
||
flex: 1;
|
||
width: 100%;
|
||
padding: 0 24rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.activity-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 36rpx;
|
||
padding-bottom: 60rpx;
|
||
}
|
||
|
||
.activity-card {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 24rpx;
|
||
min-height: 168rpx;
|
||
}
|
||
|
||
.card-cover {
|
||
position: relative;
|
||
width: 384rpx;
|
||
height: 168rpx;
|
||
border-radius: 24rpx;
|
||
overflow: hidden;
|
||
background: linear-gradient(135deg, #ffd6e0 0%, #ff9eb5 100%);
|
||
box-shadow: 0 8rpx 16rpx rgba(179, 50, 50, 0.25);
|
||
flex-shrink: 0;
|
||
z-index: 2;
|
||
}
|
||
|
||
.cover-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.cover-fallback {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #fff;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
padding: 0 16rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.cover-ended {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 24rpx;
|
||
}
|
||
|
||
.ended-text {
|
||
color: #ffffff;
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.card-info {
|
||
width: 320rpx;
|
||
height: 150.4rpx;
|
||
display: flex;
|
||
margin: 16rpx 0;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
background: url("static/rank/infobj.png") center no-repeat;
|
||
background-size: 100% 100%;
|
||
position: absolute;
|
||
right: 0;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
top: -16rpx;
|
||
}
|
||
|
||
.year-pill {
|
||
min-width: 192rpx;
|
||
height: 40rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 12rpx;
|
||
// background: linear-gradient(
|
||
// 176deg,
|
||
// rgba(255, 90, 93, 0.6) 16.6%,
|
||
// rgba(76, 237, 255, 0.6) 48%,
|
||
// rgba(255, 122, 124, 0.6) 84%
|
||
// );
|
||
// box-shadow: 0 4rpx 6rpx rgba(201, 60, 159, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
|
||
background: url("static/rank/textbj.png") center no-repeat;
|
||
background-size: 100%;
|
||
}
|
||
|
||
.year-text {
|
||
display: block;
|
||
color: #fffabd;
|
||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||
font-family: "Baloo Bhai";
|
||
font-size: 28rpx;
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
line-height: normal;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.name-text {
|
||
display: block;
|
||
color: #fffabd;
|
||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||
font-family: "Abhaya Libre ExtraBold";
|
||
font-size: 20rpx;
|
||
font-style: normal;
|
||
font-weight: 800;
|
||
line-height: normal;
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
.date-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.date-text {
|
||
font-size: 18rpx;
|
||
color: #fffabd;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
font-weight: bold;
|
||
}
|
||
|
||
.countdown-text {
|
||
font-size: 14rpx;
|
||
color: #fffabd;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.stat-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: 64rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.stat-block {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 2rpx;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.block-label {
|
||
font-size: 14rpx;
|
||
font-weight: bold;
|
||
color: #fffabd;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
display: block;
|
||
}
|
||
|
||
.block-image {
|
||
width: 96rpx;
|
||
height: 64rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.block-value {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #fffabd;
|
||
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.84);
|
||
line-height: 1.1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
display: block;
|
||
}
|
||
|
||
.stat-image {
|
||
width: 96rpx;
|
||
height: 64rpx;
|
||
position: relative;
|
||
bottom: -16rpx;
|
||
transform: scale(1.3) rotate(-15deg);
|
||
}
|
||
|
||
.empty-block {
|
||
padding: 80rpx 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.empty-text {
|
||
color: rgba(255, 250, 189, 0.6);
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.footer-end {
|
||
padding: 40rpx 0 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.footer-text {
|
||
color: rgba(95, 95, 95, 0.66);
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* ============ 加载/错误遮罩 ============ */
|
||
.loading-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 200;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border: 6rpx solid rgba(255, 255, 255, 0.3);
|
||
border-top-color: #fff;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.loading-text {
|
||
color: #fff;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.error-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 200;
|
||
padding: 0 60rpx;
|
||
}
|
||
|
||
.error-title {
|
||
color: #fff;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.error-msg {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-size: 26rpx;
|
||
margin-bottom: 40rpx;
|
||
text-align: center;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.retry-btn {
|
||
padding: 16rpx 60rpx;
|
||
border-radius: 40rpx;
|
||
background: linear-gradient(135deg, #ff5a5d 0%, #fc6466 50%, #f45868 100%);
|
||
box-shadow: 0 4rpx 8rpx rgba(242, 21, 21, 0.5);
|
||
}
|
||
|
||
.retry-text {
|
||
color: #fff;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style>
|