topfans/frontend/pages/support-activity/components/ContributionList.vue
2026-06-17 23:12:59 +08:00

334 lines
7.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="contribution-list" v-if="visible">
<!-- 渐变模糊背景层 -->
<view class="list-bg"></view>
<scroll-view class="list-content" scroll-y>
<view
v-for="(record, index) in records"
:key="record.id"
class="contribution-item"
:class="{ 'new-item': index === 0, 'fading-out': record.fading }"
>
<!-- 左侧:用户信息(头像 + 昵称 + 赠送动作) -->
<view class="user-info">
<image
class="user-avatar"
:src="record.avatar_url"
mode="aspectFill"
/>
<view class="user-text">
<text class="user-nickname">{{ record.nickname }}</text>
<text class="gift-text" v-if="record.item_name"
>送{{ record.item_name }}</text
>
</view>
</view>
<!-- 右侧:道具图标(外层模块带 11° 旋转 + 红色阴影) -->
<view class="item-icon-wrap">
<image class="item-icon" :src="record.item_icon" mode="aspectFill" />
<view class="quantity-info">
<text class="item-x">X</text>
<text
class="item-count"
:class="`count-${(record.combo_count > 1 ? record.combo_count : record.quantity).toString().length}-digit`"
>{{
record.combo_count > 1 ? record.combo_count : record.quantity
}}</text
>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
import { useContributionPolling } from "../composables/useContributionPolling.js";
const props = defineProps({
activityId: {
type: String,
required: true,
},
});
const isPageActive = ref(true);
// 使用轮询 composable
const { records, visible, loading, error, start, stop, reset } =
useContributionPolling(
computed(() => props.activityId),
isPageActive,
);
onMounted(() => {
isPageActive.value = true;
});
// 页面生命周期集成
onUnmounted(() => {
stop();
});
// 暴露给父组件使用
defineExpose({
reset,
});
/**
* 估算单行文本宽度rpx
* - CJK 字符按 1.0 * fontSize 计算(方形字宽 ≈ fontSize
* - 其它字符按 0.55 * fontSize 计算(拉丁/数字字宽 ≈ 半个 fontSize
*/
const estimateTextWidth = (text, fontSize) => {
if (!text) return 0;
let width = 0;
for (const ch of String(text)) {
// CJK Unified Ideographs + 标点 + 日韩文范围
if (/[ -鿿＀-￯]/.test(ch)) {
width += fontSize * 1.0;
} else {
width += fontSize * 0.55;
}
}
return Math.round(width);
};
/**
* 根据昵称(+赠送道具名)动态计算胶囊宽度
* 组成:左右内边距(32) + 头像(78) + 头像间距(16) + 文字宽度 + 文字右侧间距(16)
* + 数量区宽度X + 数字,数字按 1 位约 56rpx 兜底)
*/
const getItemWidth = (record) => {
const nickname = record?.nickname || "";
const giftName = record?.item_name || "";
const nicknameWidth = estimateTextWidth(nickname, 24);
const giftWidth = estimateTextWidth("送" + giftName, 20);
// 文字列宽度取两者中较宽的一行
const textWidth = Math.max(nicknameWidth, giftWidth);
// 数量区item-x (32) + 间距(6) + item-count按 1 位 56rpx 兜底,多位由 font-size 调整但宽度更窄)
// const quantityWidth = 32 + 6 + 56;
// 固定部分左内边距16 + 头像78 + 头像右侧间距16 + 文字右侧间距16 + 右内边距16
const FIXED = 16 + 78 + 16 + 16 + 16;
const total = FIXED + textWidth;
// 兜底 min/max 与 CSS 保持一致
return Math.min(420, Math.max(200, total));
};
</script>
<style scoped>
.contribution-list {
width: 100%;
padding: 0 24rpx;
margin-top: 32rpx;
position: relative;
}
/* 渐变模糊背景层Figma 中的 4 色径向 + 50px 模糊 */
.list-bg {
position: absolute;
top: 0;
left: 24rpx;
right: 24rpx;
height: 512rpx;
border-radius: 48rpx;
background: linear-gradient(
131.84deg,
rgba(154, 146, 255, 0.47) 9.69%,
rgba(255, 202, 229, 0.47) 43.91%,
rgba(255, 250, 253, 0.465) 76.13%,
rgba(63, 63, 76, 0.47) 91.61%
);
filter: blur(50rpx);
z-index: 0;
pointer-events: none;
}
.list-content {
height: 512rpx;
border-radius: 24rpx;
padding: 24rpx 16rpx;
position: relative;
z-index: 1;
}
/* 单条贡献 item胶囊形状 + 红色阴影 + 宽度跟随昵称 */
.contribution-item {
/* 宽度由 :style 注入的 --item-width 控制跨端最稳CSS 仅兜底 min/max */
width: max-content;
min-width: 200rpx;
max-width: 420rpx;
height: 88rpx;
display: flex;
align-items: center;
margin-bottom: 24rpx;
animation: fadeIn 0.3s ease-out;
position: relative;
}
.contribution-item:last-child {
margin-bottom: 0;
}
.new-item {
animation: slideIn 0.3s ease-out;
}
.fading-out {
animation: fadeOut 0.5s forwards;
}
/* 左侧:用户信息容器(头像 + 文字) */
.user-info {
display: flex;
align-items: center;
/* 不再 flex:1 撑满,宽度跟随内容 */
flex: 0 0 auto;
min-width: 0;
border-radius: 999rpx;
padding: 8rpx 16rpx;
padding-right: 64rpx;
box-shadow: 0 4rpx 8rpx rgba(102, 7, 7, 0.25);
background-image: url("@/static/rank/activity-support-icon/beijingkuang.png");
background-size: 100% 130%;
background-position: center;
}
/* 头像:圆形 + 红色阴影 */
.user-avatar {
width: 78rpx;
height: 78rpx;
border-radius: 50%;
margin-right: 16rpx;
box-shadow: 2rpx 2rpx 8rpx rgba(181, 7, 7, 0.54);
flex-shrink: 0;
}
/* 文字容器(昵称 + 赠送动作) */
.user-text {
display: flex;
flex-direction: column;
justify-content: center;
/* 不再 flex:1让昵称真实宽度决定容器宽度 */
flex: 0 0 auto;
white-space: nowrap;
}
/* 昵称:黄色 + 红色文字阴影 */
.user-nickname {
font-size: 24rpx;
font-weight: bold;
color: #fff9a1;
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);
white-space: nowrap;
line-height: 1.2;
font-family: "Abhaya Libre ExtraBold", sans-serif;
}
/* 赠送动作文本 */
.gift-text {
font-size: 20rpx;
color: #ffffff;
text-shadow: -2rpx 2rpx 8rpx rgba(206, 9, 9, 0.45);
white-space: nowrap;
line-height: 1.2;
margin-top: 2rpx;
font-family: "Abhaya Libre ExtraBold", sans-serif;
}
/* 道具图标外层:带 11° 旋转 + 红色阴影 + 绝对定位伸出版心 */
.item-icon-wrap {
display: flex;
/* position: absolute;
right: -64rpx; */
border-radius: 8rpx;
margin-left: -40rpx;
}
/* 道具图标本体:填满外层模块 */
.item-icon {
width: 96rpx;
height: 96rpx;
display: block;
transform: rotate(11deg);
transform-origin: center center;
}
/* 数量区域X 白色 + 数字黄色 */
.quantity-info {
display: flex;
align-items: flex-end;
text-shadow: -0.0625rem 0.0625rem 0.25rem rgba(206, 9, 9, 0.45);
margin-left: 16rpx;
}
.item-x {
font-size: 32rpx;
color: #ffffff;
font-style: italic;
margin-right: 6rpx;
}
.item-count {
font-size: 56rpx;
color: #f7e600;
font-weight: bold;
line-height: 1;
font-style: italic;
}
/* 数字位数不同对应不同尺寸,对照 Figma 设计3位 76rpx / 2位 52rpx / 1位 48rpx */
.count-1-digit {
font-size: 48rpx;
}
.count-2-digit {
font-size: 52rpx;
}
.count-3-digit {
font-size: 76rpx;
}
.count-4-digit,
.count-5-digit {
font-size: 64rpx;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20rpx);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
</style>