343 lines
7.9 KiB
Vue
343 lines
7.9 KiB
Vue
<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="getCountSizeClass(record.combo_count > 1 ? record.combo_count : record.quantity)"
|
||
>{{
|
||
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);
|
||
};
|
||
|
||
/**
|
||
* 根据数量值映射数字字号档位
|
||
* - >= 999 → count-largest(最大)
|
||
* - 520 ~ 998 → count-mid(次大)
|
||
* - < 520 → count-small(最小)
|
||
*/
|
||
const getCountSizeClass = (value) => {
|
||
const n = Number(value) || 0;
|
||
if (n >= 999) return "count-largest";
|
||
if (n >= 520) return "count-mid";
|
||
return "count-small";
|
||
};
|
||
|
||
/**
|
||
* 根据昵称(+赠送道具名)动态计算胶囊宽度
|
||
* 组成:左右内边距(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(10deg);
|
||
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;
|
||
}
|
||
|
||
/* 数量档位样式:999+ 顶格、520~998 中档、其他常规 */
|
||
.count-largest {
|
||
font-size: 76rpx;
|
||
}
|
||
.count-mid {
|
||
font-size: 52rpx;
|
||
}
|
||
.count-small {
|
||
font-size: 48rpx;
|
||
}
|
||
|
||
@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>
|