topfans/frontend/pages/support-activity/index.vue
2026-06-22 12:37:59 +08:00

1206 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="activity-container" :style="containerStyle">
<!-- 顶部导航 -->
<!-- <Header
:show-back="true"
backIconColor="#e6e6e6"
:showGuideIcon="false" :showTaskIcon="false" :showStarActivityIcon="false"
/> -->
<view class="nav-back" @tap="goBack">
<text class="nav-back-icon"></text>
</view>
<!-- 左上角水晶余额fixed 悬浮与右上角 TopRanking 对称 -->
<view class="crystal-balance-new">
<!-- 1. 左侧钻石图标层级最高盖住右侧背景 -->
<view class="crystal-icon-box">
<image
class="crystal-icon"
src="/static/icon/crystal.png"
mode="aspectFit"
></image>
</view>
<!-- 2. 右侧容器用于包裹背景和文字 -->
<view class="crystal-info-container">
<!-- <image
class="crystal-bg-img"
src="/static/rank/sjbj.png"
mode="aspectFill"
>
</image> -->
<view class="crystal-bg"></view>
<!-- 上层文字内容 -->
<view class="crystal-text-layer">
<text class="balance-number">{{ exhibitionRevenue }}</text>
</view>
</view>
</view>
<!-- 右上角 TOP3 + 我的排名悬浮 @open-ranking="openRankingModal"-->
<TopRanking
v-if="activityId && !isLoading"
:activity-id="activityId"
class="top-ranking-wrapper"
/>
<view class="rank-ph" @tap="openRankingModal">
<image class="ph-image" src="/static/rank/phtb.png" mode="aspectFit">
</image>
<text class="ph-text">排行榜</text>
</view>
<!-- 状态栏占位 -->
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<!-- 主题横幅 -->
<!-- <ThemeBanner
v-if="config"
:title="config.title"
:banner-image="config.bannerImage"
:current="progressData.current"
:target="progressData.target"
:is-stale-data="isStaleData"
@tap="openRankingModal"
/> -->
<!-- 竖向进度条fixed 定位,左侧) -->
<!-- <VerticalProgressBar
:current="progressData.current"
:target="progressData.target"
/> -->
<!-- 实时贡献列表 -->
<ContributionList
v-if="activityId && !isLoading"
:activity-id="activityId"
class="contribution-list-wrapper"
/>
<!-- 舞台区域 -->
<!-- <StageArea
v-if="config"
:activity-type="activityType"
:config="config"
:is-completed="isCompleted"
@animation-complete="onAnimationComplete"
/> -->
<!-- 悬浮气泡 -->
<!-- <FloatingBubbles
v-show="config"
:bubble-texts="config?.bubbleTexts"
:is-active="isPageActive"
:current="progressData.current"
:target="progressData.target"
/> -->
<!-- 底部操作栏触发按钮 -->
<view class="action-bar-trigger" @click="toggleActionBar">
<view class="trigger-btn">
<image
src="/static/rank/activity-support-icon/lihe.png"
class="trigger-icon"
mode="aspectFit"
/>
</view>
<!-- <text class="trigger-text">我要贡献</text> -->
</view>
<!-- 留言输入框:对齐底部确认赠送按钮位置 -->
<MessageInput
v-model:text="messageDraft"
placeholder="留下你的祝福..."
@send="handleSendMessage"
/>
<!-- 底部操作栏弹出框 -->
<view v-if="actionBarVisible" class="action-bar-popup">
<!-- 遮罩层 -->
<view class="action-bar-mask" @click="toggleActionBar"></view>
<!-- 弹出内容 -->
<view class="action-bar-content">
<ActionBar
v-if="activityId && config"
:activity-id="activityId"
:action-items="config.actionItems"
@contribute="handleContribute"
/>
</view>
</view>
<!-- 留言板弹出框 -->
<MessageBoard :messages="messageList" />
<!-- 底部导航 -->
<!-- <BottomNav
:activeTab="activeTab"
:isExpanded="navExpanded"
@update:activeTab="handleTabChange"
@update:isExpanded="navExpanded = $event"
/> -->
<!-- 蒙层 - 导航栏展开时显示 -->
<view
v-if="navExpanded"
class="nav-mask"
@click="navExpanded = false"
></view>
<!-- 加载状态 -->
<view v-if="isLoading" class="loading-overlay">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
<view v-if="loadingProgress > 0" class="loading-progress">
<view class="progress-bar-container">
<view
class="progress-bar-fill"
:style="{ width: loadingProgress + '%' }"
></view>
</view>
<text class="progress-text">{{ Math.round(loadingProgress) }}%</text>
</view>
</view>
<!-- 错误状态 -->
<view v-if="errorMessage" class="error-overlay">
<view class="error-content">
<text class="error-title">加载失败</text>
<text class="error-message">{{ errorMessage }}</text>
<button class="retry-button" @click="retryLoad">重试</button>
</view>
</view>
<!-- 排行榜弹窗 -->
<ActivityRankingModal
v-model:visible="rankingModalVisible"
:activity-id="activityId"
:activity-title="currentActivityTitle"
@visit="handleVisitUser"
@view-profile="handleViewUserProfile"
@view-artwork="handleViewArtwork"
/>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
import { onLoad, onUnload, onShow, onHide } from "@dcloudio/uni-app";
import {
fetchActivityDetail,
fetchActivityProgress,
} from "@/utils/activity-config";
import {
getProgressManager,
releaseProgressManager,
cleanupProgressManager,
} from "@/utils/progress-manager";
import screenCache from "@/utils/screen-cache";
import performanceMonitor from "@/utils/performance-monitor";
import Header from "../components/Header.vue";
import BottomNav from "../components/BottomNav.vue";
import ThemeBanner from "./components/ThemeBanner.vue";
import VerticalProgressBar from "./components/VerticalProgressBar.vue";
import ContributionList from "./components/ContributionList.vue";
import StageArea from "./components/StageArea.vue";
import FloatingBubbles from "./components/FloatingBubbles.vue";
import ActivityRankingModal from "./components/ActivityRankingModal.vue";
import ActionBar from "./components/ActionBar.vue";
import TopRanking from "./components/TopRanking.vue";
import MessageBoard from "./components/MessageBoard.vue";
import MessageInput from "./components/MessageInput.vue";
import { getEarningsSummaryApi } from "@/utils/api.js";
const activityType = ref("birthday");
const activityId = ref("");
const config = ref(null);
const progressData = ref({ current: 0, target: 1000 });
const isLoading = ref(true);
const isPageActive = ref(true);
const statusBarHeight = ref(0);
const errorMessage = ref("");
const retryCount = ref(0);
const loadingProgress = ref(0); // 资源加载进度 (0-100)
const isStaleData = ref(false); // 数据是否过时
const exhibitionRevenue = ref(0); // 当前展出收益
// 底部导航状态
const activeTab = ref(0);
const navExpanded = ref(false);
// ActionBar 弹出框状态
const actionBarVisible = ref(false);
// 留言板弹出框状态
const messageBoardVisible = ref(false);
// 排行榜弹出框状态
const rankingModalVisible = ref(false);
const currentActivityTitle = ref("");
// 留言草稿:MessageInput 通过 v-model:text 双向绑定,
// 点击骰子 emoji 时按顺序循环选用内置祝福语填入这里
const messageDraft = ref("今天第一天姐妹们大家冲鸭!!!");
// 留言板 mock 数据
const messageList = ref([
{
id: 1,
user: "小星星",
avatar: "",
content: "生日快乐!永远支持你~",
time: "刚刚",
isSelf: false,
},
{
id: 2,
user: "月光宝盒",
avatar: "",
content: "太棒了,继续加油!期待更多精彩作品!",
time: "5分钟前",
isSelf: false,
},
{
id: 3,
user: "彩虹糖",
avatar: "",
content: "为你打call~",
time: "10分钟前",
isSelf: false,
},
{
id: 4,
user: "夜空中最亮的星",
avatar: "",
content: "星光不问赶路人,岁月不负有心人,冲冲冲!",
time: "30分钟前",
isSelf: false,
},
]);
function toggleActionBar() {
actionBarVisible.value = !actionBarVisible.value;
}
// 发送留言
function handleSendMessage(text) {
const newMessage = {
id: Date.now(),
user: "我",
avatar: "",
content: text,
time: "刚刚",
isSelf: true,
};
messageList.value.push(newMessage);
uni.showToast({
title: "留言成功",
icon: "success",
duration: 1500,
});
}
const goBack = () => {
// 获取页面栈
const pages = getCurrentPages();
if (pages.length > 1) {
// 有上一页,执行返回
uni.navigateBack();
} else {
// 没有上一页跳转到square页面
uni.reLaunch({
url: "/pages/support-activity/center",
});
}
};
// 打开排行榜弹窗
function openRankingModal() {
currentActivityTitle.value = config.value?.title || "活动排名";
rankingModalVisible.value = true;
}
// 处理拜访用户
function handleVisitUser(data) {
console.log("拜访用户:", data);
}
// 处理查看用户资料
function handleViewUserProfile(data) {
console.log("查看用户资料:", data);
}
// 处理查看作品
function handleViewArtwork(data) {
console.log("查看作品:", data);
}
// 加载收益汇总
const loadEarningsSummary = async () => {
try {
const response = await getEarningsSummaryApi();
if (response.code === 0) {
const data = response.data;
exhibitionRevenue.value = data.crystal_balance || 0;
// hourlyEarnings.value = data.total_hourly_earnings || 0;
}
} catch (e) {
console.error("获取收益汇总失败:", e);
}
};
let progressManager = null;
const isCompleted = computed(
() => progressData.value.current >= progressData.value.target,
);
const containerStyle = computed(() => {
if (!config.value) {
return {
backgroundImage: "none",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
};
}
// 根据进度完成状态选择背景图
const bgImage = isCompleted.value
? config.value.mainAssetActive
: config.value.bgImage;
return {
backgroundImage: `url(${bgImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
transition: "background-image 0.5s ease-in-out", // 添加平滑过渡效果
};
});
const MAX_RETRY_COUNT = 3;
// 处理 tab 切换
function handleTabChange(newTab) {
activeTab.value = newTab;
navExpanded.value = false;
// 根据tab索引跳转到对应页面
const tabRoutes = [
"/pages/ai-dazi/index", // 搭子
"/pages/starbook/index", // 星册
"/pages/castlove/mall", // 铸爱
"/pages/starcity/index", // 星城
"/pages/profile/myWorks", // 广场
];
if (newTab >= 0 && newTab < tabRoutes.length) {
uni.navigateTo({
url: tabRoutes[newTab],
fail: (err) => {
console.error("页面跳转失败:", err);
},
});
}
}
function initProgressManager() {
if (!activityId.value) return;
// 先释放旧实例的引用,引用计数归零时会自动销毁
if (progressManager) {
releaseProgressManager(activityId.value);
}
progressManager = getProgressManager(activityId.value);
progressManager.addListener((data) => {
progressData.value = {
current: data.current || 0,
target: data.target || 1000,
};
// 更新过时数据标志
isStaleData.value = data.isStale || false;
if (data.isFromCache) {
// 如果数据过时,显示一次性提示
if (data.isStale && !data.hasShownStaleToast) {
uni.showToast({
title: "正在使用缓存数据",
icon: "none",
duration: 1500,
});
data.hasShownStaleToast = true;
}
} else {
// 收到新数据,清除过时标志
isStaleData.value = false;
}
});
}
function startProgressSync() {
if (progressManager && isPageActive.value) {
progressManager.startPolling();
}
}
function stopProgressSync() {
if (progressManager) {
progressManager.stopPolling();
}
}
async function handleContribute(itemType, remainingBalance) {
if (progressManager) {
await progressManager.refresh();
}
// 购买成功后刷新左上角水晶余额
if (remainingBalance !== null && remainingBalance !== undefined) {
exhibitionRevenue.value = Number(remainingBalance) || 0;
// 同步本地用户存储,保持和 ActionBar 内部逻辑一致
const userStr = uni.getStorageSync("user");
if (userStr) {
const user =
typeof userStr === "string" ? JSON.parse(userStr) : { ...userStr };
user.crystal_balance = Number(remainingBalance) || 0;
uni.setStorageSync("user", JSON.stringify(user));
// 通知其他订阅 balanceUpdated 的组件(如 Header
uni.$emit("balanceUpdated", { crystal_balance: user.crystal_balance });
}
}
}
function onAnimationComplete() {}
// 批量转换图片路径 - 直接使用后端返回的URL
async function convertActivityImages(activityData) {
try {
// 并发转换所有图片
const [bannerImage, coverImage, stageBackground] = await Promise.all([
Promise.resolve(activityData.banner_image),
Promise.resolve(activityData.cover_image),
Promise.resolve(activityData.current_stage_background),
]);
// 转换道具图标
const actionItems = activityData.items || [];
const convertedItems = actionItems.map((item) => ({
...item,
icon: item.icon,
}));
return {
bannerImage,
coverImage,
stageBackground,
actionItems: convertedItems,
};
} catch (error) {
console.error("批量转换图片失败:", error);
return {
bannerImage: activityData.banner_image,
coverImage: activityData.cover_image,
stageBackground: activityData.current_stage_background,
actionItems: activityData.items || [],
};
}
}
async function initializePage(options = {}) {
try {
isLoading.value = true;
errorMessage.value = "";
// 启动性能监控
performanceMonitor.start();
// 初始化屏幕缓存
screenCache.init();
statusBarHeight.value = screenCache.getStatusBarHeight();
// 从URL参数获取活动ID必需
activityId.value = options.id;
if (!activityId.value) {
throw new Error("缺少活动ID参数");
}
// 从后端获取活动详情
const activityData = await fetchActivityDetail(activityId.value);
if (!activityData) {
throw new Error("无法加载活动数据");
}
// 设置活动类型和配置
activityType.value = activityData.activity_type;
// 转换图片URL
const convertedImages = await convertActivityImages(activityData);
// 转换后端数据为页面配置格式
config.value = {
title: activityData.title,
bannerImage: convertedImages.bannerImage,
bgImage: convertedImages.stageBackground || convertedImages.coverImage,
mainAssetIdle: convertedImages.coverImage,
mainAssetActive: convertedImages.stageBackground,
bubbleTexts: activityData.bubble_texts || ["加油!", "一起努力!"],
actionItems: convertedImages.actionItems,
};
// 设置进度数据
progressData.value = {
current: activityData.current_progress || 0,
target: activityData.target_progress || 1000,
};
// 优化:只预加载首屏关键资源
await preloadCriticalAssets();
// 记录首屏加载时间
performanceMonitor.recordFirstScreenLoad();
initProgressManager();
startProgressSync();
isLoading.value = false;
// 在后台继续加载非关键资源
preloadNonCriticalAssets();
} catch (error) {
console.error("页面初始化失败:", error);
errorMessage.value = error.message || "页面加载失败";
isLoading.value = false;
}
}
/**
* 预加载首屏关键资源
*/
async function preloadCriticalAssets() {
if (!config.value) return;
try {
// 只加载首屏必需的图片:背景图和横幅图
const criticalImages = [config.value.bgImage, config.value.bannerImage];
const loadPromises = criticalImages.map((src) => {
return new Promise((resolve) => {
const startTime = Date.now();
let isResolved = false;
const img = new Image();
// 超时定时器
const timeoutId = setTimeout(() => {
if (!isResolved) {
isResolved = true;
performanceMonitor.recordImageLoad(false, 3000);
resolve();
}
}, 3000);
img.onload = () => {
if (!isResolved) {
isResolved = true;
clearTimeout(timeoutId);
const loadTime = Date.now() - startTime;
performanceMonitor.recordImageLoad(true, loadTime);
resolve();
}
};
img.onerror = () => {
if (!isResolved) {
isResolved = true;
clearTimeout(timeoutId);
const loadTime = Date.now() - startTime;
performanceMonitor.recordImageLoad(false, loadTime);
resolve(); // 即使失败也继续
}
};
img.src = src;
});
});
await Promise.all(loadPromises);
} catch (error) {
console.error("[页面] 关键资源加载异常:", error);
}
}
/**
* 预加载非关键资源(后台加载)
*/
function preloadNonCriticalAssets() {
if (!config.value) return;
setTimeout(() => {
// 加载其他资源:舞台图标、道具图标等
const nonCriticalImages = [
config.value.mainAssetIdle,
config.value.mainAssetActive,
...(config.value.actionItems?.map((item) => item.icon) || []),
];
nonCriticalImages.forEach((src) => {
if (src) {
const startTime = Date.now();
const img = new Image();
img.onload = () => {
const loadTime = Date.now() - startTime;
performanceMonitor.recordImageLoad(true, loadTime);
};
img.onerror = () => {
const loadTime = Date.now() - startTime;
performanceMonitor.recordImageLoad(false, loadTime);
};
img.src = src;
}
});
}, 1000); // 延迟1秒后开始加载
}
async function retryLoad() {
if (retryCount.value >= MAX_RETRY_COUNT) {
uni.showToast({
title: "重试次数已达上限",
icon: "none",
});
return;
}
retryCount.value++;
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
await initializePage(options);
}
onLoad(async (options) => {
await initializePage(options);
});
onShow(() => {
isPageActive.value = true;
if (!isLoading.value && !errorMessage.value && progressManager) {
// 使用 resumePolling 而不是 startPolling避免重复启动
progressManager.resumePolling();
}
loadEarningsSummary();
});
onHide(() => {
isPageActive.value = false;
// 使用 pausePolling 暂停轮询,而不是完全停止
if (progressManager) {
progressManager.pausePolling();
}
});
onUnload(() => {
stopProgressSync();
if (activityId.value) {
releaseProgressManager(activityId.value);
}
// 停止性能监控(会自动打印报告)
performanceMonitor.stop();
});
</script>
<style scoped>
.activity-container {
width: 100%;
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.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;
}
.nav-back-icon {
font-size: 48rpx;
font-weight: bold;
color: #fff;
}
/* 水晶余额组件 */
.crystal-balance-new {
/* height: 120rpx; */
display: flex;
align-items: center;
/* 底部对齐 */
position: fixed;
/* 左侧留出足够空间给钻石图标,防止文字被压在图标正下方 */
padding-left: 50rpx;
top: 96rpx;
left: 112rpx;
}
/* --- 左侧钻石图标 --- */
.crystal-icon-box {
position: absolute;
left: 8rpx;
bottom: -16rpx;
/* 与右侧底部平齐 */
z-index: 10;
width: 68rpx;
height: 60rpx;
transform: rotate(-15deg);
opacity: 1;
}
.crystal-icon {
width: 100%;
height: 100%;
}
/* --- 右侧容器 --- */
.crystal-info-container {
position: relative;
height: 34rpx;
/* width: 120rpx; */
opacity: 1;
border-radius: 20rpx;
border-width: 1px;
border: 1px solid #f3a68a40;
box-shadow: 2px 2px 4.1px 0px #f936365c;
}
.crystal-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 10.405px;
border: 1px solid rgba(243, 166, 138, 0.25);
background: url("/static/rank/sjbj.png") lightgray 50% / cover no-repeat;
box-shadow: 2px 2px 4.1px 0 rgba(249, 54, 54, 0.36);
}
.crystal-bg-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 120rpx;
z-index: 0;
}
/* --- 上层:文字内容 --- */
.crystal-text-layer {
height: 100%;
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 120rpx;
opacity: 0.75;
background: linear-gradient(
270deg,
rgba(168, 17, 255, 0.83) 0%,
rgba(30, 180, 217, 0.47) 81.01%,
rgba(101, 10, 153, 0) 178.07%
);
padding: 0 24rpx;
}
.balance-number {
font-size: 24rpx;
font-weight: bold;
color: #ffb800;
font-family: "yt", sans-serif;
text-shadow:
0 0 10rpx rgba(255, 184, 0, 0.8),
0 2rpx 4rpx rgba(0, 0, 0, 0.5);
letter-spacing: 1rpx;
display: flex;
align-items: center;
white-space: nowrap;
}
.status-bar {
width: 100%;
background: transparent;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}
.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;
}
.loading-progress {
margin-top: 40rpx;
width: 400rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.progress-bar-container {
width: 100%;
height: 8rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 4rpx;
overflow: hidden;
margin-bottom: 16rpx;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #ff6b9d, #ffa06b);
border-radius: 4rpx;
transition: width 0.3s ease;
}
.progress-text {
color: rgba(255, 255, 255, 0.8);
font-size: 24rpx;
}
.error-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.error-content {
background: #fff;
border-radius: 20rpx;
padding: 60rpx 40rpx;
margin: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.error-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.error-message {
font-size: 28rpx;
color: #666;
margin-bottom: 40rpx;
line-height: 1.5;
}
.retry-button {
background: linear-gradient(135deg, #ff6b9d, #ffa06b);
color: #fff;
border: none;
border-radius: 50rpx;
padding: 20rpx 60rpx;
font-size: 28rpx;
font-weight: bold;
}
.retry-button:active {
opacity: 0.8;
}
/* 导航栏展开时的蒙层 */
.nav-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
z-index: 999;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ActionBar 弹出框样式 */
.action-bar-trigger {
width: 80rpx;
height: 80rpx;
border-radius: 80rpx;
position: fixed;
bottom: 32rpx;
right: 32rpx;
/* transform: translateX(-50%); */
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
gap: 8rpx;
/* background-image: url("@/static/rank/activity-support-icon/beijingkuang1.png");
background-size: cover;
background-position: center; */
background: rgba(217, 217, 217, 0.4);
box-shadow: 1px 2px 4px 0 rgba(0, 0, 0, 0.25);
}
.trigger-btn {
width: 100%;
height: 100%;
border-radius: 24rpx;
display: flex;
justify-content: center;
align-items: center;
}
.trigger-icon {
width: 56rpx;
height: 56rpx;
}
.trigger-text {
font-size: 24rpx;
color: #fff;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.action-bar-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 998;
}
.action-bar-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.action-bar-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
/* 实时贡献列表样式 */
.contribution-list-wrapper {
width: 100%;
padding: 0 24rpx;
position: relative;
top: 416rpx;
}
/* 留言板触发按钮(与 action-bar-trigger 对称,位于左侧) */
.message-trigger {
width: 80rpx;
height: 80rpx;
border-radius: 80rpx;
position: fixed;
bottom: 32rpx;
left: 32rpx;
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
background: rgba(217, 217, 217, 0.4);
box-shadow: 1px 2px 4px 0 rgba(0, 0, 0, 0.25);
}
.message-trigger .trigger-icon-text {
font-size: 44rpx;
}
/* 留言板弹出框样式 */
.message-board-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 998;
}
.message-board-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.message-board-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
max-height: 80vh;
background-image: url("@/static/rank/activity-support-icon/beijingkuang.png");
background-size: 105% 130%;
background-position: center;
border-radius: 40rpx 40rpx 0 0;
padding: 32rpx 32rpx 40rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
animation: slideUp 0.3s ease-out;
}
.message-board-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 16rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
}
.message-board-title {
font-size: 32rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.message-board-close {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 48rpx;
font-weight: bold;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
}
/*
* 留言输入框浮层:对齐底部确认赠送按钮
* 与 ActionBar 的 .quantity-control 处于同一水平线
*/
.message-input-floating-bottom {
width: 100%;
padding-top: 16rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.3);
}
/* 右上角 TOP3 + 我的排名 悬浮组件 */
.top-ranking-wrapper {
position: fixed;
top: 96rpx;
right: 24rpx;
z-index: 50;
max-width: 320rpx;
}
.rank-ph {
position: fixed;
top: 288rpx;
right: 24rpx;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
}
.ph-image {
width: 96rpx;
height: 96rpx;
}
.ph-text {
color: #fff;
text-shadow: -1px 1px 4px rgba(82, 8, 8, 0.45);
font-family: "Abhaya Libre ExtraBold";
font-size: 22rpx;
font-style: normal;
font-weight: 600;
line-height: normal;
}
</style>