topfans/frontend/pages/dashboard/components/ExhibitionCenter.vue

357 lines
7.9 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="exhibition-center">
<text class="section-title">展出收益中心</text>
<image
class="chart-bg"
src="/static/dashboard/exhibition-bj.png"
mode="scaleToFill"
/>
<!-- 错误态 -->
<view v-if="error" class="error-box" @tap="$emit('retry')">
<text class="error-text">加载失败,点击重试</text>
</view>
<!-- 骨架态 -->
<view v-else-if="loading || !data" class="skeleton-section">
<view class="skeleton-stats"></view>
<view class="skeleton-rows">
<view v-for="i in 5" :key="i" class="skeleton-row"></view>
</view>
</view>
<!-- 正常态 -->
<view v-else class="content">
<!-- 顶部 3 联统计 -->
<view class="stats-row">
<view class="stat-cell">
<text class="stat-value"
>{{ data.exhibiting_count }} / {{ data.starbook_count }}</text
>
<text class="stat-label">展出中 / 星册中</text>
</view>
<view class="stat-cell">
<text class="stat-value">{{ data.total_duration }}</text>
<text class="stat-label">累计展出时长</text>
</view>
<view class="stat-cell">
<text class="stat-value">{{ data.total_earnings }}</text>
<text class="stat-label">累计展出收益</text>
</view>
</view>
<!-- 5 行表格 -->
<view class="table">
<view class="table-header">
<text class="th th-thumb">藏品</text>
<text class="th th-duration">七日展出时长</text>
<text class="th th-earnings">七日收益</text>
<text class="th th-avg">平均收益</text>
</view>
<view
v-for="(item, idx) in data.top5"
:key="item.asset_id"
class="table-row"
>
<view class="td td-thumb">
<view class="thumb-placeholder">
<image
v-if="item.asset_thumb"
class="thumb-image"
:src="item.asset_thumb"
mode="aspectFill"
/>
<text v-else class="thumb-emoji">🎨</text>
</view>
</view>
<text class="td td-duration thd">{{ item.duration_7d }}</text>
<text class="td td-earnings thd">{{ item.earnings_7d }}</text>
<text class="td td-avg thd">{{ item.avg_earnings }} / H</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
defineProps({
data: { type: Object, default: null }, // ExhibitionIncomeSummary
loading: { type: Boolean, default: false },
error: { type: String, default: null },
});
defineEmits(["retry"]);
</script>
<style lang="scss" scoped>
.exhibition-center {
background:
linear-gradient(
106.77deg,
rgba(255, 223, 119, 0.33) -9.76%,
rgba(132, 255, 210, 0.33) 44.65%,
rgba(255, 129, 131, 0.33) 117.82%
),
linear-gradient(0deg, rgba(0, 75, 238, 0.2), rgba(0, 75, 238, 0.2));
// backdrop-filter: blur(10px);
border-radius: 22rpx;
padding: 12rpx;
margin: 12rpx 0;
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
position: relative;
overflow: hidden;
// [方案3] 伪元素承载 bj.png对图片单独设 opacity
&::before {
content: "";
position: absolute;
inset: 0;
background: url("/static/dashboard/bj.png") center / cover no-repeat;
opacity: 0.2; // ⬅ 调这个数控制图片透明度0=完全透明1=完全不透明)
pointer-events: none;
z-index: 0;
}
}
.section-title {
display: block;
font-size: 36rpx;
font-weight: 700;
color: #ffffff;
margin-bottom: 24rpx;
text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.3);
position: relative;
z-index: 2;
}
.chart-bg {
position: absolute;
top: -24rpx;
left: -16rpx;
width: 224rpx;
height: 224rpx;
z-index: 0; // 在标题(z:2)和图表(z:1)之下
pointer-events: none; // 不拦截 tap事件穿透到 canvas
opacity: 0.5;
transform: rotate(60deg);
}
.content {
position: relative;
z-index: 2;
}
.stats-row {
display: flex;
justify-content: space-between;
// background: rgba(255, 255, 255, 0.08);
border-radius: 17rpx;
padding: 24rpx 0;
margin-bottom: 24rpx;
}
.stat-cell {
min-width: 192rpx;
height: 80rpx;
position: relative;
box-sizing: border-box;
padding: 8rpx 12rpx;
border-top-left-radius: 14px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
border-bottom-left-radius: 12px;
opacity: 1;
background: linear-gradient(
106.77deg,
rgba(255, 223, 119, 0.5) -9.76%,
rgba(185, 132, 255, 0.5) 44.65%,
rgba(255, 129, 131, 0.5) 117.82%
);
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
&:last-child {
border-right: none;
}
}
.stat-value {
position: absolute;
bottom: 4rpx;
right: 12rpx;
font-size: 32rpx;
font-weight: 600;
color: #fffabd;
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
font-family: "Baloo Bhai", sans-serif;
}
.stat-label {
position: absolute;
top: 6rpx;
left: 12rpx;
font-size: 20rpx;
color: rgba(255, 255, 255, 1);
}
.table {
background: rgba(121, 120, 215, 0.23);
box-shadow: 0px 4px 4px 0px rgba(96, 13, 13, 0.25);
border-radius: 14rpx;
overflow: hidden;
}
.table-header,
.table-row {
display: flex;
align-items: center;
padding: 16rpx 12rpx;
// border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.table-row:last-child {
border-bottom: none;
}
.th,
.td {
font-size: 20rpx;
font-weight: 500;
color: #ffffff;
text-shadow: 0px 0px 4px rgba(164, 60, 60, 1);
text-align: center;
}
.th-thumb,
.td-thumb {
width: 80rpx;
flex-shrink: 0;
}
.th-duration,
.td-duration,
.th-earnings,
.td-earnings {
flex: 1;
}
.th-avg,
.td-avg {
width: 100rpx;
flex-shrink: 0;
}
// 让 .thd 不参与 flex 拉伸,宽度由内容 + 左右 32rpx padding 决定
// 用 .td.thd 复合选择器提高优先级,覆盖 .td-duration / .td-earnings / .td-avg 的 flex/width
.td.thd {
flex: 0 0 auto;
width: auto;
padding: 8rpx 32rpx;
background: linear-gradient(
90deg,
rgba(27, 175, 238, 0.15) 0%,
rgba(255, 204, 20, 0.15) 100%
);
border-radius: 7px;
}
// 数据行只放 thumb + 3 个 chip用 space-between 让最右 chip 与右侧对齐
// header 不加这条,仍按 flex 列宽布局
.table-row {
justify-content: space-between;
}
// .th {
// font-size: 20rpx;
// color: rgba(255, 255, 255, 0.6);
// }
.thumb-placeholder {
width: 40rpx;
height: 56rpx;
border-radius: 3rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
overflow: hidden; // 裁切 image 以贴合圆角
transform: rotate(-10deg);
box-shadow: 2px 2px 4.5px 2px rgba(230, 46, 46, 0.46);
// backdrop-filter: blur(0px);
}
.thumb-image {
width: 100%;
height: 100%;
display: block;
}
.td-earnings {
color: #fffabd;
font-weight: 600;
font-family: "Baloo Bhai", sans-serif;
}
/* 骨架 */
.skeleton-section {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.skeleton-stats {
height: 120rpx;
border-radius: 17rpx;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 25%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.05) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-rows {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.skeleton-row {
height: 80rpx;
border-radius: 14rpx;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 25%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.05) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.error-box {
height: 240rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 100, 100, 0.15);
border: 2rpx solid rgba(255, 100, 100, 0.4);
border-radius: 17rpx;
}
.error-text {
color: #ff8080;
font-size: 28rpx;
}
</style>