feat: 修改数据看板
@ -1,9 +1,13 @@
|
||||
<template>
|
||||
<view class="collection-matrix">
|
||||
<text class="section-title">藏品矩阵</text>
|
||||
|
||||
<image
|
||||
class="chart-bg"
|
||||
src="/static/dashboard/image-bj.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<!-- TOP 5 -->
|
||||
<TopFiveAssets :items="topFive || []" />
|
||||
<TopFiveAssets :items="topFive || []" class="top-five-assets" />
|
||||
|
||||
<!-- 等级分布 -->
|
||||
<LevelDistribution :items="levels" />
|
||||
@ -17,26 +21,43 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TopFiveAssets from './TopFiveAssets.vue'
|
||||
import LevelDistribution from './LevelDistribution.vue'
|
||||
import UpcomingUpgrades from './UpcomingUpgrades.vue'
|
||||
import RecentUpgrades from './RecentUpgrades.vue'
|
||||
import TopFiveAssets from "./TopFiveAssets.vue";
|
||||
import LevelDistribution from "./LevelDistribution.vue";
|
||||
import UpcomingUpgrades from "./UpcomingUpgrades.vue";
|
||||
import RecentUpgrades from "./RecentUpgrades.vue";
|
||||
|
||||
defineProps({
|
||||
topFive: { type: Array, default: () => [] },
|
||||
levels: { type: Array, default: () => [] },
|
||||
upgrades: { type: Object, default: () => ({ upcoming: [], recent: [] }) },
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collection-matrix {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
backdrop-filter: blur(10px);
|
||||
background:
|
||||
linear-gradient(
|
||||
106.77deg,
|
||||
rgba(255, 223, 119, 0.13) -9.76%,
|
||||
rgba(132, 255, 210, 0.13) 44.65%,
|
||||
rgba(255, 129, 131, 0.13) 117.82%
|
||||
),
|
||||
linear-gradient(0deg, rgba(255, 159, 16, 0.2), rgba(255, 159, 16, 0.2));
|
||||
border-radius: 22rpx;
|
||||
padding: 24rpx;
|
||||
margin: 24rpx 0;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
padding: 12rpx;
|
||||
margin: 12rpx 0;
|
||||
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::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 {
|
||||
@ -44,8 +65,22 @@ defineProps({
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 24rpx;
|
||||
margin-bottom: 34rpx;
|
||||
text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
z-index: 2; // 在 ::before 之上
|
||||
}
|
||||
|
||||
.chart-bg {
|
||||
position: absolute;
|
||||
top: -64rpx;
|
||||
left: -56rpx;
|
||||
width: 312rpx;
|
||||
height: 312rpx;
|
||||
z-index: 1; // 在 ::before(z:0) 之上,在标题(z:2) 和内容(z:1) 之间
|
||||
pointer-events: none; // 不拦截 tap,事件穿透到 canvas
|
||||
opacity: 0.5;
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
|
||||
.upgrades-two-col {
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<text class="card-label">水晶余额</text>
|
||||
<text class="card-value">{{ data.crystal_balance }}</text>
|
||||
</view>
|
||||
<view class="data-card card-today">
|
||||
<view class="data-card card-crystal">
|
||||
<text class="card-label">今日收益</text>
|
||||
<text class="card-value">+ {{ data.today_income }}</text>
|
||||
</view>
|
||||
@ -29,13 +29,13 @@ defineProps({
|
||||
data: { type: Object, default: null }, // { crystal_balance, today_income, week_rank? }
|
||||
loading: { type: Boolean, default: false },
|
||||
error: { type: String, default: null },
|
||||
})
|
||||
defineEmits(['retry'])
|
||||
});
|
||||
defineEmits(["retry"]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.crystal-overview {
|
||||
margin: 24rpx 0;
|
||||
margin:0 0 12rpx 0;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
@ -44,41 +44,53 @@ defineEmits(['retry'])
|
||||
}
|
||||
|
||||
.data-card {
|
||||
flex: 1;
|
||||
height: 200rpx;
|
||||
border-radius: 22rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
border-top-left-radius: 19px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 7px;
|
||||
border-bottom-left-radius: 7px;
|
||||
opacity: 1;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card-crystal {
|
||||
background: linear-gradient(135deg, #FFDF77 0%, #8E95E2 40%, #F48CFF 100%);
|
||||
box-shadow: 0px 4px 4px rgba(189, 50, 50, 0.25);
|
||||
background: linear-gradient(
|
||||
274.19deg,
|
||||
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: 0px 4px 4px 0px #00000040;
|
||||
}
|
||||
|
||||
.card-today {
|
||||
background: linear-gradient(137deg, #FFDF77 0%, #8E95E2 40%, #F48CFF 100%);
|
||||
box-shadow: 0px 4px 4px rgba(189, 50, 50, 0.25);
|
||||
}
|
||||
// .card-today {
|
||||
// background: linear-gradient(137deg, #ffdf77 0%, #8e95e2 40%, #f48cff 100%);
|
||||
// box-shadow: 0px 4px 4px rgba(189, 50, 50, 0.25);
|
||||
// }
|
||||
|
||||
.card-label {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
text-shadow: 0px 4px 4px rgba(164, 60, 60, 0.55);
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 70rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFABD;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 12rpx;
|
||||
font-size: 64rpx;
|
||||
font-weight: 600;
|
||||
color: #fffabd;
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
font-family: 'Baloo Bhai', sans-serif;
|
||||
line-height: 1;
|
||||
font-family: "Baloo Bhai", sans-serif;
|
||||
}
|
||||
|
||||
/* 骨架态 */
|
||||
@ -91,14 +103,23 @@ defineEmits(['retry'])
|
||||
flex: 1;
|
||||
height: 200rpx;
|
||||
border-radius: 22rpx;
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.1) 25%,
|
||||
rgba(255, 255, 255, 0.2) 50%,
|
||||
rgba(255, 255, 255, 0.1) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 错误态 */
|
||||
|
||||
@ -4,35 +4,40 @@
|
||||
<view class="header-deco-bg"></view>
|
||||
|
||||
<!-- 装饰光晕(红粉色) -->
|
||||
<view class="header-glow"></view>
|
||||
<!-- <view class="header-glow"></view> -->
|
||||
|
||||
<!-- 状态栏占位(iPhone 44px) -->
|
||||
<view class="status-bar-placeholder"></view>
|
||||
<!-- <view class="status-bar-placeholder"></view> -->
|
||||
|
||||
<!-- 渐变标题 -->
|
||||
<view class="title-wrap">
|
||||
<text class="header-title">数据看板</text>
|
||||
</view>
|
||||
|
||||
<view class="header-content">
|
||||
<!-- 毛绒怪头像(占位:纯色块 + emoji) -->
|
||||
<view class="mascot">
|
||||
<text class="mascot-emoji">🐾</text>
|
||||
</view>
|
||||
|
||||
<!-- 渐变标题 -->
|
||||
<view class="title-wrap">
|
||||
<text class="header-title">数据看板</text>
|
||||
</view>
|
||||
|
||||
<!-- Tab 胶囊 -->
|
||||
<view class="header-tabs">
|
||||
<view
|
||||
:class="['tab', activeTab === 'crystal' ? 'tab-active' : '']"
|
||||
@tap="$emit('update:activeTab', 'crystal')"
|
||||
>
|
||||
水晶相关
|
||||
<image
|
||||
class="tab-icon"
|
||||
src="/static/dashboard/crystal-bg.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="tab-title">水晶相关</text>
|
||||
</view>
|
||||
<view
|
||||
:class="['tab', activeTab === 'season' ? 'tab-active' : '']"
|
||||
@tap="$emit('update:activeTab', 'season')"
|
||||
>
|
||||
赛季总览
|
||||
<image
|
||||
class="tab-icon"
|
||||
src="/static/dashboard/season-bg.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="tab-title">赛季总览</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -41,16 +46,16 @@
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
activeTab: { type: String, default: 'crystal' },
|
||||
})
|
||||
defineEmits(['update:activeTab'])
|
||||
activeTab: { type: String, default: "crystal" },
|
||||
});
|
||||
defineEmits(["update:activeTab"]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-header {
|
||||
position: relative;
|
||||
height: 360rpx;
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
}
|
||||
|
||||
.status-bar-placeholder {
|
||||
@ -58,14 +63,26 @@ defineEmits(['update:activeTab'])
|
||||
}
|
||||
|
||||
.header-deco-bg {
|
||||
background-image: url("/static/dashboard/header-bj.png");
|
||||
background-size: 130%;
|
||||
// background-repeat: no-repeat;
|
||||
// background-position: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 700rpx;
|
||||
background: linear-gradient(179deg, #FFE5E5 0%, #F3A0A1 50%, #FF9C9C 86%, #FF2024 100%);
|
||||
filter: blur(4px);
|
||||
z-index: 0;
|
||||
// 底部渐隐蒙版:让 header 底部的红色 bg 渐变到透明,
|
||||
// 下方页面的 bj.png + 渐变蒙层从底部自然透出,形成柔和衔接
|
||||
// 700rpx 中 0-30%(0-210rpx) 完全不透明,30-50%(210-360rpx) 渐变到完全透明
|
||||
mask-image: linear-gradient(to bottom, #000 0%, #000 60%, transparent 80%);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0%,
|
||||
#000 60%,
|
||||
transparent 80%
|
||||
);
|
||||
}
|
||||
|
||||
.header-glow {
|
||||
@ -75,7 +92,11 @@ defineEmits(['update:activeTab'])
|
||||
transform: translateX(-50%);
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
background: radial-gradient(circle, rgba(255, 200, 100, 0.5) 0%, transparent 70%);
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(255, 200, 100, 0.5) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
filter: blur(30px);
|
||||
z-index: 1;
|
||||
}
|
||||
@ -84,16 +105,14 @@ defineEmits(['update:activeTab'])
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0 32rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
top: 192rpx;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FFB199 100%);
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ffb199 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -101,10 +120,6 @@ defineEmits(['update:activeTab'])
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.mascot-emoji {
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
.title-wrap {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
@ -112,7 +127,7 @@ defineEmits(['update:activeTab'])
|
||||
.header-title {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, #FFE5B4 0%, #FFB199 50%, #FF8A95 100%);
|
||||
background: linear-gradient(90deg, #ffe5b4 0%, #ffb199 50%, #ff8a95 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
@ -121,27 +136,69 @@ defineEmits(['update:activeTab'])
|
||||
|
||||
.header-tabs {
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 22rpx;
|
||||
// background: rgba(0, 0, 0, 0.15);
|
||||
// border-radius: 22rpx;
|
||||
padding: 6rpx;
|
||||
width: 100%;
|
||||
max-width: 500rpx;
|
||||
height: 184rpx;
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 14rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 28rpx 0;
|
||||
border-radius: 22rpx;
|
||||
transition: background 0.25s ease, color 0.25s ease;
|
||||
margin-left: 8px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 222, 8, 0.1519) -17.54%,
|
||||
rgba(252, 100, 102, 0.31) 64.4%,
|
||||
rgba(244, 88, 104, 0.31) 116.67%
|
||||
);
|
||||
box-shadow: 2px 2px 4px 0px #f2151578;
|
||||
|
||||
backdrop-filter: blur(29.299999237060547px);
|
||||
// 点击时上浮反馈
|
||||
transition:
|
||||
transform 0.15s ease,
|
||||
box-shadow 0.15s ease,
|
||||
opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
text-shadow: 1px 4px 4px #00000054;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.tab-active {
|
||||
background: linear-gradient(231deg, #A8A6ED 0%, #88C8D8 64%, #F380EF 100%);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 222, 8, 0.4018) -17.54%,
|
||||
rgba(252, 100, 102, 0.82) 64.4%,
|
||||
rgba(244, 88, 104, 0.82) 116.67%
|
||||
);
|
||||
box-shadow: 2px 2px 4px 0px #f2151578;
|
||||
backdrop-filter: blur(29.799999237060547px);
|
||||
opacity: 0.79;
|
||||
transform: translateY(-22rpx);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<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>
|
||||
@ -16,11 +20,13 @@
|
||||
</view>
|
||||
|
||||
<!-- 正常态 -->
|
||||
<view v-else>
|
||||
<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-value"
|
||||
>{{ data.exhibiting_count }} / {{ data.starbook_count }}</text
|
||||
>
|
||||
<text class="stat-label">展出中 / 星册中</text>
|
||||
</view>
|
||||
<view class="stat-cell">
|
||||
@ -36,7 +42,7 @@
|
||||
<!-- 5 行表格 -->
|
||||
<view class="table">
|
||||
<view class="table-header">
|
||||
<text class="th th-thumb"></text>
|
||||
<text class="th th-thumb">藏品</text>
|
||||
<text class="th th-duration">七日展出时长</text>
|
||||
<text class="th th-earnings">七日收益</text>
|
||||
<text class="th th-avg">平均收益</text>
|
||||
@ -47,13 +53,19 @@
|
||||
class="table-row"
|
||||
>
|
||||
<view class="td td-thumb">
|
||||
<view class="thumb-placeholder" :class="`thumb-grad-${idx % 5}`">
|
||||
<text class="thumb-emoji">🎨</text>
|
||||
<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">{{ item.duration_7d }}</text>
|
||||
<text class="td td-earnings">{{ item.earnings_7d }}</text>
|
||||
<text class="td td-avg">{{ item.avg_earnings }} / H</text>
|
||||
<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>
|
||||
@ -65,18 +77,38 @@ defineProps({
|
||||
data: { type: Object, default: null }, // ExhibitionIncomeSummary
|
||||
loading: { type: Boolean, default: false },
|
||||
error: { type: String, default: null },
|
||||
})
|
||||
defineEmits(['retry'])
|
||||
});
|
||||
defineEmits(["retry"]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.exhibition-center {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
backdrop-filter: blur(10px);
|
||||
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: 24rpx;
|
||||
margin: 24rpx 0;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
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 {
|
||||
@ -86,22 +118,55 @@ defineEmits(['retry'])
|
||||
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;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
justify-content: space-between;
|
||||
// background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 17rpx;
|
||||
padding: 24rpx 0;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.stat-cell {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
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;
|
||||
@ -109,20 +174,28 @@ defineEmits(['retry'])
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
position: absolute;
|
||||
bottom: 4rpx;
|
||||
right: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFABD;
|
||||
font-family: 'Baloo Bhai', sans-serif;
|
||||
margin-bottom: 8rpx;
|
||||
font-weight: 600;
|
||||
color: #fffabd;
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
|
||||
font-family: "Baloo Bhai", sans-serif;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
position: absolute;
|
||||
top: 6rpx;
|
||||
left: 12rpx;
|
||||
font-size: 20rpx;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.table {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: rgba(121, 120, 215, 0.23);
|
||||
box-shadow: 0px 4px 4px 0px rgba(96, 13, 13, 0.25);
|
||||
border-radius: 14rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -132,7 +205,7 @@ defineEmits(['retry'])
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 12rpx;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
// border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.table-row:last-child {
|
||||
@ -141,8 +214,10 @@ defineEmits(['retry'])
|
||||
|
||||
.th,
|
||||
.td {
|
||||
font-size: 24rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
text-shadow: 0px 0px 4px rgba(164, 60, 60, 1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -165,35 +240,55 @@ defineEmits(['retry'])
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.th {
|
||||
font-size: 20rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
// 让 .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: 56rpx;
|
||||
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-grad-0 { background: linear-gradient(135deg, #FF6B6B 0%, #FFB199 100%); }
|
||||
.thumb-grad-1 { background: linear-gradient(135deg, #B17BFF 0%, #FF8FE6 100%); }
|
||||
.thumb-grad-2 { background: linear-gradient(135deg, #5EDFFF 0%, #6FA9FF 100%); }
|
||||
.thumb-grad-3 { background: linear-gradient(135deg, #FFE066 0%, #FFB199 100%); }
|
||||
.thumb-grad-4 { background: linear-gradient(135deg, #C5C5C5 0%, #8C8C8C 100%); }
|
||||
|
||||
.thumb-emoji {
|
||||
font-size: 32rpx;
|
||||
.thumb-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.td-earnings {
|
||||
color: #FFFABD;
|
||||
color: #fffabd;
|
||||
font-weight: 600;
|
||||
font-family: 'Baloo Bhai', sans-serif;
|
||||
font-family: "Baloo Bhai", sans-serif;
|
||||
}
|
||||
|
||||
/* 骨架 */
|
||||
@ -206,7 +301,12 @@ defineEmits(['retry'])
|
||||
.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: 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;
|
||||
}
|
||||
@ -220,14 +320,23 @@ defineEmits(['retry'])
|
||||
.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: 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; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.error-box {
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<view class="income-curve-card">
|
||||
<text class="curve-title">七日收益曲线</text>
|
||||
|
||||
<image
|
||||
class="chart-bg"
|
||||
src="/static/dashboard/ucharts-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 || !points || points.length === 0" class="skeleton-chart"></view>
|
||||
<view
|
||||
v-else-if="loading || !points || points.length === 0"
|
||||
class="skeleton-chart"
|
||||
></view>
|
||||
|
||||
<!-- 图表 -->
|
||||
<view v-else class="chart-wrap">
|
||||
@ -19,129 +26,154 @@
|
||||
:chartData="chartData"
|
||||
:ontouch="true"
|
||||
:onmovetip="true"
|
||||
canvas2d
|
||||
:in-scroll-view="true"
|
||||
:tooltipShow="true"
|
||||
:canvas2d="false"
|
||||
canvasId="incomeCurveCanvas"
|
||||
:canvasHeight="240"
|
||||
ontap
|
||||
@complete="handleChartComplete"
|
||||
@tap="onChartTap"
|
||||
/>
|
||||
<!-- 默认指向最后一天的指示器(位置来自 uCharts calPoints,跟图表完全对齐) -->
|
||||
<view
|
||||
v-if="latestPoint && latestCalPoint"
|
||||
class="latest-indicator"
|
||||
:style="{
|
||||
left: latestCalPoint.x / pixel + 'px',
|
||||
top: latestCalPoint.y / pixel + 'px',
|
||||
}"
|
||||
>
|
||||
<view class="latest-line"></view>
|
||||
<view class="latest-dot"></view>
|
||||
<view class="latest-tooltip">
|
||||
<text class="latest-value">+{{ latestPoint.income }}</text>
|
||||
<text class="latest-date">{{ latestPoint.date.slice(5) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from "vue";
|
||||
// 显式 import 兜底,避免 easycom 漏注册
|
||||
import QiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
|
||||
import QiunDataCharts from "@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue";
|
||||
|
||||
const props = defineProps({
|
||||
points: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
error: { type: String, default: null },
|
||||
})
|
||||
defineEmits(['retry'])
|
||||
});
|
||||
defineEmits(["retry"]);
|
||||
|
||||
// canvas2d 模式 opts.pix=1,非 canvas2d 模式 opts.pix=systemInfo.pixelRatio
|
||||
// 用 emit 回来的 opts 拿真实值,兜底 1
|
||||
const pixel = ref(1)
|
||||
const chartOptsRef = ref(null)
|
||||
const calPoints = ref([])
|
||||
// 当前选中的数据点索引:默认指向最后一条,tap 时更新
|
||||
const currentIndex = ref(0);
|
||||
watch(
|
||||
() => props.points.length,
|
||||
(len) => {
|
||||
currentIndex.value = Math.max(0, len - 1);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 监听 @complete:uCharts 渲染完成后会 emit 一次,opts.chartData.calPoints 是每个点的真实 canvas 坐标
|
||||
function handleChartComplete(e) {
|
||||
const opts = e?.opts
|
||||
if (!opts) return
|
||||
chartOptsRef.value = opts
|
||||
pixel.value = opts.pix || 1
|
||||
calPoints.value = opts.chartData?.calPoints || []
|
||||
}
|
||||
|
||||
// 跟 chartOpts.padding / chart-canvas 高度保持一致,单位 rpx
|
||||
const CHART_HEIGHT = 240
|
||||
const PADDING = [16, 16, 8, 16] // top, right, bottom, left
|
||||
|
||||
const peak = computed(() => props.points.find((p) => p.is_peak) || null)
|
||||
const latestPoint = computed(() =>
|
||||
props.points.length > 0 ? props.points[props.points.length - 1] : null
|
||||
)
|
||||
|
||||
// 最后一点的真实 canvas 坐标(来自 uCharts @complete 事件的 calPoints)
|
||||
// calPoints 结构:calPoints[seriesIndex][dataIndex] = { x, y },y 是 canvas 像素
|
||||
const latestCalPoint = computed(() => {
|
||||
if (!latestPoint.value || calPoints.value.length === 0) return null
|
||||
const seriesPoints = calPoints.value[0]
|
||||
if (!seriesPoints || seriesPoints.length === 0) return null
|
||||
return seriesPoints[seriesPoints.length - 1] || null
|
||||
})
|
||||
const onChartTap = (e) => {
|
||||
const idx = e?.index;
|
||||
if (typeof idx === "number" && idx >= 0 && idx < props.points.length) {
|
||||
currentIndex.value = idx;
|
||||
}
|
||||
};
|
||||
|
||||
const chartData = computed(() => {
|
||||
const categories = props.points.map((p) => p.date.slice(5))
|
||||
const lineData = props.points.map((p) => p.income)
|
||||
const categories = props.points.map((p) => p.date.slice(5));
|
||||
const lineData = props.points.map((p) => p.income);
|
||||
return {
|
||||
categories,
|
||||
series: [
|
||||
{ name: '收益', type: 'area', data: lineData, color: '#1BAFEE' },
|
||||
{
|
||||
name: "收益",
|
||||
type: "area",
|
||||
data: lineData,
|
||||
color: "#1BAFEE",
|
||||
// [自定义] u-charts.js 已 patch:series.linearColor 启用横向多色渐变
|
||||
// 对应 CSS: linear-gradient(90deg, #D1EFFFBC 0%, #E7E8A9D8 49.52%, #FF9CE5FA 100%)
|
||||
linearColor: [
|
||||
[0, "rgba(209, 239, 255, 0.735)"],
|
||||
[0.4952, "rgba(231, 232, 169, 0.846)"],
|
||||
[1, "rgba(255, 156, 229, 0.98)"],
|
||||
],
|
||||
linearDirection: "horizontal", // 'horizontal'(默认) | 'vertical'
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
const PADDING = [16, -16, 8, -16]; // top, right, bottom, left
|
||||
|
||||
const chartOpts = {
|
||||
color: ['#1BAFEE'],
|
||||
color: ["#1BAFEE"],
|
||||
padding: PADDING,
|
||||
dataLabel: false,
|
||||
legend: { show: false },
|
||||
// 关闭曲线上每个数据点的小圆圈
|
||||
dataPointShape: false,
|
||||
xAxis: { disabled: true, disableGrid: true, axisLine: false, fontColor: '#FFFFFF', fontSize: 9 },
|
||||
xAxis: {
|
||||
disabled: true,
|
||||
disableGrid: true,
|
||||
axisLine: false,
|
||||
fontColor: "#FFFFFF",
|
||||
fontSize: 9,
|
||||
},
|
||||
yAxis: {
|
||||
disabled: true,
|
||||
showTitle: false,
|
||||
data: [{ min: 0, disabled: true, axisLine: false }],
|
||||
disableGrid: true,
|
||||
fontColor: '#FFFFFF',
|
||||
fontColor: "#FFFFFF",
|
||||
fontSize: 9,
|
||||
},
|
||||
extra: {
|
||||
tooltip: {
|
||||
showBox: true,
|
||||
showArrow: true,
|
||||
showCategory: true,
|
||||
bgColor: '#000000',
|
||||
showCategory: false,
|
||||
bgColor: "#000000",
|
||||
bgOpacity: 0.6,
|
||||
fontColor: '#FFFFFF',
|
||||
fontColor: "#FFFFFF",
|
||||
fontSize: 11,
|
||||
splitLine: true,
|
||||
horizentalLine: { type: 'dash', width: 1, color: '#FFFFFF' },
|
||||
horizentalLine: { type: "dash", width: 1, color: "#FFFFFF" },
|
||||
// 函数形式:uCharts 每次渲染 tooltip 时调用,index 为当前触点对应的数据点索引
|
||||
// 未触摸时 index < 0,返回空 tooltip 避免干扰
|
||||
tooltipCustom: (_opts, _categories, index) => {
|
||||
if (typeof index !== "number" || index < 0) {
|
||||
return { textList: [] };
|
||||
}
|
||||
const point = props.points[index];
|
||||
if (!point) return { textList: [] };
|
||||
return {
|
||||
textList: [
|
||||
{ text: `+${point.income}`, color: "#1BAFEE" },
|
||||
{ text: point.date.slice(5), color: "#999999" },
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
// area 类型曲线区域图,搭配 series.linearType=custom + linearColor 实现自定义多色渐变
|
||||
// addLine:false 不绘制曲线(曲线透明),仅保留渐变面积填充
|
||||
area: {
|
||||
type: "curve",
|
||||
opacity: 1,
|
||||
addLine: false,
|
||||
width: 2,
|
||||
gradient: true,
|
||||
activeType: "hollow",
|
||||
},
|
||||
// area 类型曲线区域图,gradient: true 启用从线色到透明的渐变填充
|
||||
area: { type: 'curve', opacity: 0.3, addLine: true, width: 2, gradient: true, activeType: 'hollow' },
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.income-curve-card {
|
||||
background: linear-gradient(135deg, #FFDF77 0%, #8E95E2 40%, #F48CFF 100%);
|
||||
background: linear-gradient(
|
||||
107.96deg,
|
||||
rgba(255, 223, 119, 0.59) -28.33%,
|
||||
rgba(142, 149, 226, 0.59) 44.4%,
|
||||
rgba(244, 140, 255, 0.59) 117.77%
|
||||
);
|
||||
border-radius: 22rpx;
|
||||
padding: 24rpx;
|
||||
margin: 24rpx 0;
|
||||
box-shadow: 0px 4px 4px rgba(189, 50, 50, 0.25);
|
||||
min-height: 360rpx;
|
||||
box-sizing: border-box; // padding 计入总高度,避免内部撑爆
|
||||
box-shadow: 0px 4px 4px 0px #BD323240;
|
||||
position: relative;
|
||||
height: 256rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; // 裁掉超出圆角的部分(包括背景图)
|
||||
}
|
||||
|
||||
.curve-title {
|
||||
@ -149,80 +181,51 @@ const chartOpts = {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
margin-bottom: 4rpx; // 卡片高度有限,缩小标题与图表间距
|
||||
text-shadow: 0px 2px 2px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
z-index: 2; // 标题在背景图和图表之上
|
||||
}
|
||||
|
||||
.chart-wrap {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
position: relative;
|
||||
flex: 1; // 撑满标题下方的剩余空间
|
||||
min-height: 0; // flex 子项防止溢出的兜底
|
||||
border-radius: 17rpx;
|
||||
padding: 16rpx;
|
||||
backdrop-filter: blur(10px);
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
// backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.chart-bg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 224rpx;
|
||||
height: 100%;
|
||||
z-index: 0; // 在标题(z:2)和图表(z:1)之下
|
||||
pointer-events: none; // 不拦截 tap,事件穿透到 canvas
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.chart-canvas {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
}
|
||||
|
||||
.latest-indicator {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
// left/top 是数据点的中心,translate(-50%, -100%) 让 tooltip 在点的正上方、点对齐 indicator 中心
|
||||
transform: translate(-50%, -100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.latest-line {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: -2rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.latest-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #1BAFEE;
|
||||
border: 2rpx solid #ffffff;
|
||||
align-self: center;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.latest-tooltip {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 8rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.latest-value {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFABD;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.latest-date {
|
||||
font-size: 18rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
line-height: 1.2;
|
||||
margin-top: 2rpx;
|
||||
height: 100%; // 跟随 .chart-wrap 撑满
|
||||
// 阴影:drop-shadow 沿 canvas 内容 alpha 通道生效,跟随曲线形状
|
||||
// 对应 CSS: box-shadow: -1px -5px 4px 0px #CF232338 (#CF2323 + alpha 0x38 ≈ 0.22)
|
||||
filter: drop-shadow(-1px -5px 4px rgba(207, 35, 35, 0.22));
|
||||
}
|
||||
|
||||
.skeleton-chart {
|
||||
height: 360rpx;
|
||||
border-radius: 17rpx;
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.1) 25%,
|
||||
rgba(255, 255, 255, 0.2) 50%,
|
||||
rgba(255, 255, 255, 0.1) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-shimmer 1.5s infinite;
|
||||
}
|
||||
@ -243,7 +246,11 @@ const chartOpts = {
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -5,71 +5,141 @@
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
<view v-else class="ring-row">
|
||||
<view
|
||||
v-for="item in items"
|
||||
:key="item.level"
|
||||
class="ring-cell"
|
||||
>
|
||||
<view
|
||||
class="ring-outer"
|
||||
:style="getRingStyle(item)"
|
||||
>
|
||||
<view class="ring-inner">
|
||||
<view v-for="item in items" :key="item.level" class="ring-cell">
|
||||
<view class="ring-chart">
|
||||
<qiun-data-charts
|
||||
type="arcbar"
|
||||
:opts="getOpts(item)"
|
||||
:chartData="getChartData(item)"
|
||||
:canvasId="`arcbar-${item.level}`"
|
||||
:canvas2d="false"
|
||||
:ontouch="false"
|
||||
:in-scroll-view="true"
|
||||
:tooltipShow="false"
|
||||
/>
|
||||
<!--
|
||||
中心数字用 HTML 覆盖层而非 ucharts title:
|
||||
ucharts canvas 文本(fillText)不支持 text-shadow,
|
||||
覆盖层可以走 CSS text-shadow。
|
||||
-->
|
||||
<view class="ring-center" pointer-events="none">
|
||||
<text class="ring-count">{{ item.count }}</text>
|
||||
<text class="ring-count ring-text">{{ getPercent(item) }}%</text>
|
||||
<image
|
||||
class="ring-label-img"
|
||||
:src="getGradeBadge(item.level)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<text class="ring-label">{{ item.level }}</text>
|
||||
<text class="ring-pct">{{ getPercent(item) }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import QiunDataCharts from "@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue";
|
||||
|
||||
const props = defineProps({
|
||||
items: { type: Array, default: () => [] }, // AssetLevelItem[]
|
||||
})
|
||||
});
|
||||
|
||||
// 等级 → 渐变色(左→右),与原 conic-gradient 配色保持一致
|
||||
const COLOR_MAP = {
|
||||
UR: { start: "#FF9C84", end: "#88F4EB" },
|
||||
SSR: { start: "#FF6640", end: "#B5F488" },
|
||||
SR: { start: "#DFFF5E", end: "#F48896" },
|
||||
R: { start: "#8FA9FF", end: "#4FFFF0" },
|
||||
N: { start: "#C5C5C5", end: "#8C8C8C" },
|
||||
};
|
||||
|
||||
// 等级徽章图片映射:item.level 是字符串 ('UR'/'SSR'/'SR'/'R'/'N')
|
||||
// 注意 UR 的文件名是 URengji.png(没有 d,与其他不同)
|
||||
const GRADE_BADGE_MAP = {
|
||||
N: "/static/starbookcontent/grade/Ndengji.png",
|
||||
R: "/static/starbookcontent/grade/Rdengji.png",
|
||||
SR: "/static/starbookcontent/grade/SRdengji.png",
|
||||
SSR: "/static/starbookcontent/grade/SSRdengji.png",
|
||||
UR: "/static/starbookcontent/grade/URengji.png",
|
||||
};
|
||||
const getGradeBadge = (level) => GRADE_BADGE_MAP[level] || GRADE_BADGE_MAP.N;
|
||||
|
||||
function getPercent(item) {
|
||||
if (!item.total) return 0
|
||||
return Math.round((item.count / item.total) * 100)
|
||||
if (!item.total) return 0;
|
||||
return Math.round((item.count / item.total) * 100);
|
||||
}
|
||||
|
||||
function getRingStyle(item) {
|
||||
const pct = getPercent(item)
|
||||
const colorMap = {
|
||||
UR: '#FF8A65, #FFD740',
|
||||
SSR: '#FF5E9C, #FFB199',
|
||||
SR: '#B17BFF, #FF8FE6',
|
||||
R: '#5EDFFF, #6FA9FF',
|
||||
N: '#C5C5C5, #8C8C8C',
|
||||
}
|
||||
const colors = colorMap[item.level] || '#999, #ccc'
|
||||
// 占位 0 显示灰色环
|
||||
if (pct === 0) {
|
||||
return {
|
||||
background: 'conic-gradient(rgba(255,255,255,0.1) 0deg, rgba(255,255,255,0.1) 360deg)',
|
||||
}
|
||||
}
|
||||
// 渐变环:从顶部开始,pct% 处停止
|
||||
function getChartData(item) {
|
||||
const pct = getPercent(item);
|
||||
return {
|
||||
background: `conic-gradient(${colors} 0deg ${pct * 3.6}deg, rgba(255,255,255,0.1) ${pct * 3.6}deg 360deg)`,
|
||||
}
|
||||
series: [
|
||||
{
|
||||
name: item.level,
|
||||
color: COLOR_MAP[item.level]?.start || "#999",
|
||||
data: pct / 100,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function getOpts(item) {
|
||||
const colors = COLOR_MAP[item.level] || { start: "#999", end: "#ccc" };
|
||||
return {
|
||||
color: [colors.start],
|
||||
padding: [0, 0, 0, 0],
|
||||
dataLabel: false,
|
||||
legend: { show: false },
|
||||
title: {
|
||||
// 中心数字改由 .ring-center 覆盖层渲染(支持 text-shadow),这里关掉
|
||||
name: "",
|
||||
fontSize: 0,
|
||||
color: "transparent",
|
||||
},
|
||||
subtitle: {
|
||||
name: "",
|
||||
fontSize: 0,
|
||||
color: "transparent",
|
||||
},
|
||||
extra: {
|
||||
arcbar: {
|
||||
// 'default' 走 u-charts.js 的 if 分支:背景只画 startAngle→endAngle 范围,
|
||||
// 留下底部 90° 缺口(典型"圆弧进度条"造型)
|
||||
type: "default",
|
||||
width: 5,
|
||||
// [PATCH] 背景改由 u-charts.js 内的 backgroundLinearType 接管,
|
||||
// backgroundColor 留空字符串避免重复描边
|
||||
backgroundColor: "",
|
||||
// 背景渐变:对应 CSS linear-gradient(36.72deg, rgba(199,244,255,.45) 13.45%, rgba(233,193,255,.45) 79.47%)
|
||||
backgroundLinearType: "custom",
|
||||
backgroundLinearAngle: 36.72,
|
||||
backgroundLinearColor: [
|
||||
[0.1345, "rgba(199, 244, 255, 0.45)"],
|
||||
[0.7947, "rgba(233, 193, 255, 0.45)"],
|
||||
],
|
||||
startAngle: 0.75, // 12 点钟
|
||||
endAngle: 0.25, // 6 点钟(顺时针走 3/4 圈)
|
||||
gap: 0,
|
||||
// 进度条渐变:fillColor = linear-gradient(left=color, right=customColor[0])
|
||||
linearType: "custom",
|
||||
customColor: [colors.end],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.level-distribution {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: rgba(121, 120, 215, 0.31);
|
||||
border-radius: 17rpx;
|
||||
padding: 20rpx;
|
||||
margin-top: 16rpx;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0px 4px 4px 0px rgba(96, 13, 13, 0.25);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
@ -89,46 +159,50 @@ function getRingStyle(item) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ring-outer {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
.ring-chart {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
box-shadow: 0 0 12px rgba(255, 255, 255, 0.15);
|
||||
// 原 conic-gradient 版本的环外发光,改由容器兜住,canvas 透明区域也能看到
|
||||
filter: drop-shadow(0 0 12rpx rgba(255, 255, 255, 0.15));
|
||||
}
|
||||
|
||||
.ring-inner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
// 中心数字覆盖层:HTML 文本支持 text-shadow(canvas 不支持)
|
||||
.ring-center {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(4px);
|
||||
z-index: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ring-count {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFABD;
|
||||
font-family: 'Baloo Bhai', sans-serif;
|
||||
}
|
||||
|
||||
.ring-label {
|
||||
margin-top: 8rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.ring-pct {
|
||||
font-size: 18rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 2rpx;
|
||||
font-weight: 700;
|
||||
font-family: "Baloo Bhai", sans-serif;
|
||||
line-height: 1;
|
||||
color: rgba(255, 241, 163, 1);
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
}
|
||||
|
||||
.ring-text{
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
|
||||
.ring-label-img {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-top: 4rpx;
|
||||
position: absolute;
|
||||
bottom: -8rpx;
|
||||
}
|
||||
|
||||
.empty-row {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<view class="like-income-board">
|
||||
<text class="section-title">点赞收益看板</text>
|
||||
|
||||
<image
|
||||
class="chart-bg"
|
||||
src="/static/dashboard/liked-bj.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<!-- 错误/骨架态 -->
|
||||
<view v-if="error" class="error-box" @tap="$emit('retry')">
|
||||
<text class="error-text">加载失败,点击重试</text>
|
||||
@ -27,17 +31,28 @@
|
||||
|
||||
<!-- 右侧等级列表 -->
|
||||
<view class="right-list">
|
||||
<view
|
||||
v-for="(item, idx) in levels"
|
||||
:key="idx"
|
||||
class="level-row"
|
||||
>
|
||||
<view class="level-header">
|
||||
<text class="th th-thumb">藏品</text>
|
||||
<text class="th th-name">等级</text>
|
||||
<text class="th th-income">累计收益</text>
|
||||
</view>
|
||||
<view v-for="(item, idx) in levels" :key="idx" class="level-row">
|
||||
<view class="level-thumb">
|
||||
<view class="thumb-circle" :class="`level-${item.level}`">
|
||||
<text class="thumb-letter">{{ item.level }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="item.thumb"
|
||||
class="thumb-asset-img"
|
||||
:src="item.thumb"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text v-else class="thumb-emoji">🎨</text>
|
||||
</view>
|
||||
<view class="level-name">
|
||||
<image
|
||||
class="level-badge-img"
|
||||
:src="getGradeBadge(item.level)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="level-name">{{ item.level }}</text>
|
||||
<text class="level-income">{{ item.total_income }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -51,18 +66,73 @@ defineProps({
|
||||
levels: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
error: { type: String, default: null },
|
||||
})
|
||||
defineEmits(['retry'])
|
||||
});
|
||||
defineEmits(["retry"]);
|
||||
|
||||
// 等级徽章图片映射:item.level 是字符串 ('UR'/'SSR'/'SR'/'R'/'N')
|
||||
// 注意 UR 的文件名是 URengji.png(没有 d,与其他不同)
|
||||
const GRADE_BADGE_MAP = {
|
||||
N: "/static/starbookcontent/grade/Ndengji.png",
|
||||
R: "/static/starbookcontent/grade/Rdengji.png",
|
||||
SR: "/static/starbookcontent/grade/SRdengji.png",
|
||||
SSR: "/static/starbookcontent/grade/SSRdengji.png",
|
||||
UR: "/static/starbookcontent/grade/URengji.png",
|
||||
};
|
||||
const getGradeBadge = (level) => GRADE_BADGE_MAP[level] || GRADE_BADGE_MAP.N;
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.like-income-board {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 22rpx;
|
||||
padding: 24rpx;
|
||||
margin: 24rpx 0;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
background:
|
||||
linear-gradient(
|
||||
106.77deg,
|
||||
rgba(255, 223, 119, 0.13) -9.76%,
|
||||
rgba(132, 255, 210, 0.13) 44.65%,
|
||||
rgba(255, 129, 131, 0.13) 117.82%
|
||||
),
|
||||
linear-gradient(0deg, rgba(249, 69, 69, 0.5), rgba(249, 69, 69, 0.5));
|
||||
// bj.png 移到 ::before,单独控制 opacity
|
||||
border-top-left-radius: 17px;
|
||||
border-top-right-radius: 14px;
|
||||
border-bottom-right-radius: 14px;
|
||||
border-bottom-left-radius: 14px;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
padding: 12rpx;
|
||||
margin: 12rpx 0;
|
||||
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有子内容上浮到 ::before 之上
|
||||
.board-row,
|
||||
.error-box,
|
||||
.skeleton-board {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.chart-bg {
|
||||
position: absolute;
|
||||
top: -24rpx;
|
||||
left: -16rpx;
|
||||
width: 224rpx;
|
||||
height: 224rpx;
|
||||
z-index: 1; // 在 ::before(z:0) 之上,在标题(z:2) 和内容(z:1) 之间
|
||||
pointer-events: none; // 不拦截 tap,事件穿透到 canvas
|
||||
opacity: 0.44;
|
||||
// transform: rotate(60deg);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@ -72,6 +142,8 @@ defineEmits(['retry'])
|
||||
color: #ffffff;
|
||||
margin-bottom: 24rpx;
|
||||
text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.board-row {
|
||||
@ -90,32 +162,84 @@ defineEmits(['retry'])
|
||||
}
|
||||
|
||||
.stat-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 264rpx;
|
||||
height: 98rpx;
|
||||
width: 147;
|
||||
border-top-left-radius: 14px;
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 8rpx 16rpx;
|
||||
background: linear-gradient(
|
||||
106.77deg,
|
||||
rgba(255, 223, 119, 0.43) -9.76%,
|
||||
rgba(185, 132, 255, 0.43) 44.65%,
|
||||
rgba(255, 129, 131, 0.43) 117.82%
|
||||
);
|
||||
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 40rpx;
|
||||
position: absolute;
|
||||
bottom: 4rpx;
|
||||
right: 16rpx;
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFABD;
|
||||
font-family: 'Baloo Bhai', sans-serif;
|
||||
color: #fffabd;
|
||||
font-family: "Baloo Bhai", sans-serif;
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.stat-text {
|
||||
position: absolute;
|
||||
top: 6rpx;
|
||||
left: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.right-list {
|
||||
flex: 1.5;
|
||||
width: 320rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
// 表头行:列宽 / gap / padding 与 .level-row 对齐
|
||||
.level-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4rpx 12rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.th {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-shadow: 0px 0px 4px rgba(164, 60, 60, 1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.th-thumb {
|
||||
width: 56rpx;
|
||||
flex-shrink: 0; // 占位,对齐 .level-thumb
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.th-name {
|
||||
flex: 0 0 100rpx; // 对齐 .level-name
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.th-income {
|
||||
flex: 1; // 对齐 .level-income
|
||||
text-align: center; // 跟数据行一起居中
|
||||
}
|
||||
|
||||
.level-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -126,48 +250,57 @@ defineEmits(['retry'])
|
||||
}
|
||||
|
||||
.level-thumb {
|
||||
width: 56rpx;
|
||||
width: 40rpx;
|
||||
height: 56rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.thumb-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 2px 2px 4.5px 2px rgba(230, 46, 46, 0.46);
|
||||
transform: rotate(-10deg);
|
||||
// backdrop-filter: blur(0px);
|
||||
}
|
||||
|
||||
.thumb-letter {
|
||||
font-size: 18rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
// 藏品缩略图:填满 .level-thumb 容器
|
||||
.thumb-asset-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.level-UR { background: linear-gradient(135deg, #FF8A65 0%, #FFD740 100%); }
|
||||
.level-SSR { background: linear-gradient(135deg, #FF5E9C 0%, #FFB199 100%); }
|
||||
.level-SR { background: linear-gradient(135deg, #B17BFF 0%, #FF8FE6 100%); }
|
||||
.level-R { background: linear-gradient(135deg, #5EDFFF 0%, #6FA9FF 100%); }
|
||||
.level-N { background: linear-gradient(135deg, #C5C5C5 0%, #8C8C8C 100%); }
|
||||
// 空数据兜底 emoji
|
||||
.thumb-emoji {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.level-name {
|
||||
flex: 0 0 60rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
flex: 0 0 96rpx; // 与 .th-name 同宽
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end; // 徽章图水平居中
|
||||
}
|
||||
|
||||
// 等级徽章图:保持比例,限制最大尺寸贴合行高
|
||||
.level-badge-img {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.level-income {
|
||||
flex: 1;
|
||||
flex: 0 0 120rpx;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(27, 175, 238, 0.19) 0%,
|
||||
rgba(255, 204, 20, 0.19) 100%
|
||||
);
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #FFFABD;
|
||||
font-family: 'Baloo Bhai', sans-serif;
|
||||
text-align: right;
|
||||
border-radius: 7px;
|
||||
color: #fffabd;
|
||||
font-family: "Baloo Bhai", sans-serif;
|
||||
text-align: center; // 居中
|
||||
}
|
||||
|
||||
/* 骨架/错误 */
|
||||
@ -181,7 +314,12 @@ defineEmits(['retry'])
|
||||
.skeleton-list {
|
||||
height: 200rpx;
|
||||
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: 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;
|
||||
}
|
||||
@ -202,7 +340,11 @@ defineEmits(['retry'])
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,27 +1,35 @@
|
||||
<template>
|
||||
<view class="recent-upgrades">
|
||||
<text class="card-title">最近升级</text>
|
||||
<text class="card-title">即将升级藏品</text>
|
||||
<view v-if="!items || items.length === 0" class="empty-row">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
<view v-else class="upgrades-list">
|
||||
<view
|
||||
v-for="item in items"
|
||||
:key="item.asset_id"
|
||||
class="upgrade-row"
|
||||
>
|
||||
<view v-for="item in items" :key="item.asset_id" class="upgrade-row">
|
||||
<view class="upgrade-thumb">
|
||||
<view class="thumb-circle" :style="{ background: 'linear-gradient(135deg, #B17BFF 0%, #FF8FE6 100%)' }">
|
||||
<text class="thumb-letter">{{ item.asset_name[0] }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="item.asset_thumb"
|
||||
class="upgrade-thumb-img"
|
||||
:src="item.asset_thumb"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text v-else class="thumb-emoji">{{ item.asset_name[0] }}</text>
|
||||
<!-- 等级徽章叠加在左上角(显示升级前的等级) -->
|
||||
<image
|
||||
class="upgrade-thumb-badge"
|
||||
:src="getGradeBadge(getPreviousLevel(item.new_level))"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<view class="upgrade-info">
|
||||
<text class="upgrade-name">{{ item.asset_name }}</text>
|
||||
<text class="upgrade-time">{{ formatTime(item.upgrade_time) }}</text>
|
||||
<text class="lv-up">Lv <text class="lv-up lv-up-text">UP</text></text>
|
||||
</view>
|
||||
<view class="level-badge" :class="`level-${item.new_level}`">
|
||||
<text class="level-letter">{{ item.new_level }}</text>
|
||||
<text class="lv-up">Lv UP</text>
|
||||
<view class="level-badge">
|
||||
<image
|
||||
class="level-badge-img"
|
||||
:src="getGradeBadge(item.new_level)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -31,28 +39,52 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
items: { type: Array, default: () => [] }, // RecentLevelUpItem[]
|
||||
})
|
||||
});
|
||||
|
||||
// 等级徽章图片映射:item.new_level 是字符串 ('UR'/'SSR'/'SR'/'R'/'N')
|
||||
// 注意 UR 的文件名是 URengji.png(没有 d,与其他不同)
|
||||
const GRADE_BADGE_MAP = {
|
||||
N: "/static/starbookcontent/grade/Ndengji.png",
|
||||
R: "/static/starbookcontent/grade/Rdengji.png",
|
||||
SR: "/static/starbookcontent/grade/SRdengji.png",
|
||||
SSR: "/static/starbookcontent/grade/SSRdengji.png",
|
||||
UR: "/static/starbookcontent/grade/URengji.png",
|
||||
};
|
||||
const getGradeBadge = (level) => GRADE_BADGE_MAP[level] || GRADE_BADGE_MAP.N;
|
||||
|
||||
// 等级升序:N < R < SR < SSR < UR。返回 level 的"前一级"(用于左上角徽章)
|
||||
const LEVEL_ORDER = ["N", "R", "SR", "SSR", "UR"];
|
||||
const getPreviousLevel = (level) => {
|
||||
const idx = LEVEL_ORDER.indexOf(level);
|
||||
return idx > 0 ? LEVEL_ORDER[idx - 1] : LEVEL_ORDER[0];
|
||||
};
|
||||
|
||||
function formatTime(ts) {
|
||||
const now = Date.now()
|
||||
const diff = now - ts
|
||||
if (diff < 60 * 60 * 1000) return `${Math.floor(diff / 60000)} 分钟前`
|
||||
if (diff < 24 * 60 * 60 * 1000) return `${Math.floor(diff / 3600000)} 小时前`
|
||||
return `${Math.floor(diff / 86400000)} 天前`
|
||||
const now = Date.now();
|
||||
const diff = now - ts;
|
||||
if (diff < 60 * 60 * 1000) return `${Math.floor(diff / 60000)} 分钟前`;
|
||||
if (diff < 24 * 60 * 60 * 1000) return `${Math.floor(diff / 3600000)} 小时前`;
|
||||
return `${Math.floor(diff / 86400000)} 天前`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recent-upgrades {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 17rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
background: linear-gradient(
|
||||
106.77deg,
|
||||
rgba(255, 223, 119, 0.24) -9.76%,
|
||||
rgba(185, 132, 255, 0.24) 44.65%,
|
||||
rgba(255, 129, 131, 0.24) 117.82%
|
||||
);
|
||||
|
||||
border-radius: 14px;
|
||||
padding: 16rpx;
|
||||
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
@ -75,32 +107,48 @@ function formatTime(ts) {
|
||||
|
||||
.upgrade-thumb {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
height: 67rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.thumb-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
// overflow: hidden; // 裁切 image 贴合圆角
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
|
||||
position: relative; // 作为 .upgrade-thumb-badge 的定位上下文
|
||||
transform: rotate(-10deg);
|
||||
box-shadow: 0px 4px 4px 0px rgba(214, 53, 53, 0.43);
|
||||
}
|
||||
|
||||
.thumb-letter {
|
||||
font-size: 22rpx;
|
||||
// 等级徽章图:绝对定位在缩略图左上角
|
||||
.upgrade-thumb-badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
left: -8rpx;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
z-index: 1;
|
||||
filter: drop-shadow(0 0 2rpx rgba(0, 0, 0, 0.5)); // 提升深色图上的可读性
|
||||
}
|
||||
|
||||
// 藏品缩略图:填满 .upgrade-thumb 容器
|
||||
.upgrade-thumb-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 无图时的 emoji 兜底
|
||||
.thumb-emoji {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.upgrade-info {
|
||||
flex: 1;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
min-width: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.upgrade-name {
|
||||
@ -121,28 +169,25 @@ function formatTime(ts) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
padding: 4rpx 6rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.level-UR { background: linear-gradient(135deg, #FF8A65 0%, #FFD740 100%); }
|
||||
.level-SSR { background: linear-gradient(135deg, #FF5E9C 0%, #FFB199 100%); }
|
||||
.level-SR { background: linear-gradient(135deg, #B17BFF 0%, #FF8FE6 100%); }
|
||||
.level-R { background: linear-gradient(135deg, #5EDFFF 0%, #6FA9FF 100%); }
|
||||
.level-N { background: linear-gradient(135deg, #C5C5C5 0%, #8C8C8C 100%); }
|
||||
|
||||
.level-letter {
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||
// 等级徽章图:保持比例,限制最大尺寸
|
||||
.level-badge-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.lv-up {
|
||||
font-size: 14rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
color: rgba(255, 241, 163, 1);
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
}
|
||||
|
||||
.lv-up-text {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.empty-row {
|
||||
|
||||
@ -5,15 +5,17 @@
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
<view v-else class="top-five-row">
|
||||
<view
|
||||
v-for="item in items"
|
||||
:key="item.asset_id"
|
||||
class="top-cell"
|
||||
>
|
||||
<view class="top-thumb" :class="`top-grad-${item.rank}`">
|
||||
<text class="top-emoji">🏆</text>
|
||||
<view v-for="item in items" :key="item.asset_id" class="top-cell">
|
||||
<view class="top-thumb">
|
||||
<image
|
||||
v-if="item.asset_thumb"
|
||||
class="top-thumb-img"
|
||||
:src="item.asset_thumb"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text v-else class="top-emoji">🏆</text>
|
||||
</view>
|
||||
<view class="top-badge" :class="`top-badge-${item.rank}`">
|
||||
<view class="top-badge">
|
||||
<text class="top-badge-text">TOP {{ item.rank }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -24,20 +26,32 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
items: { type: Array, default: () => [] }, // TopAssetItem[]
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-five-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 17rpx;
|
||||
background: linear-gradient(
|
||||
106.77deg,
|
||||
rgba(255, 223, 119, 0.24) -9.76%,
|
||||
rgba(185, 132, 255, 0.24) 44.65%,
|
||||
rgba(255, 129, 131, 0.24) 117.82%
|
||||
);
|
||||
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
|
||||
|
||||
border-radius: 14px;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
backdrop-filter: blur(3.5px);
|
||||
// backdrop-filter: blur(1px);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
@ -57,21 +71,24 @@ defineProps({
|
||||
}
|
||||
|
||||
.top-thumb {
|
||||
width: 100rpx;
|
||||
width: 80rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8rpx;
|
||||
box-shadow: 0 0 12px rgba(255, 200, 100, 0.3);
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0px 4px 5.8px 0px rgba(226, 51, 51, 0.53);
|
||||
transform: rotate(-10deg);
|
||||
overflow: hidden; // 裁切 image 贴合圆角
|
||||
}
|
||||
|
||||
.top-grad-1 { background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); }
|
||||
.top-grad-2 { background: linear-gradient(135deg, #C0C0C0 0%, #808080 100%); }
|
||||
.top-grad-3 { background: linear-gradient(135deg, #CD7F32 0%, #8B4513 100%); }
|
||||
.top-grad-4 { background: linear-gradient(135deg, #B17BFF 0%, #FF8FE6 100%); }
|
||||
.top-grad-5 { background: linear-gradient(135deg, #5EDFFF 0%, #6FA9FF 100%); }
|
||||
// 藏品缩略图:填满 .top-thumb 容器
|
||||
.top-thumb-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top-emoji {
|
||||
font-size: 48rpx;
|
||||
@ -81,20 +98,26 @@ defineProps({
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
opacity: 0.85;
|
||||
background: linear-gradient(
|
||||
93.1deg,
|
||||
rgba(224, 180, 247, 0.71) -12.06%,
|
||||
rgba(178, 246, 204, 0.71) 52.09%,
|
||||
rgba(98, 178, 244, 0.71) 163.5%
|
||||
);
|
||||
backdrop-filter: blur(11.699999809265137px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.top-badge-1 { background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%); }
|
||||
.top-badge-2 { background: linear-gradient(90deg, #C0C0C0 0%, #808080 100%); }
|
||||
.top-badge-3 { background: linear-gradient(90deg, #CD7F32 0%, #8B4513 100%); }
|
||||
.top-badge-4 { background: linear-gradient(90deg, #B17BFF 0%, #FF8FE6 100%); }
|
||||
.top-badge-5 { background: linear-gradient(90deg, #5EDFFF 0%, #6FA9FF 100%); }
|
||||
|
||||
.top-badge-text {
|
||||
font-size: 18rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||
font-family: C800;
|
||||
font-weight: 600;
|
||||
color: #fffabd;
|
||||
text-shadow: -1px 1px 4px #ce0909d6;
|
||||
}
|
||||
|
||||
.empty-row {
|
||||
|
||||
@ -1,27 +1,57 @@
|
||||
<template>
|
||||
<view class="upcoming-upgrades">
|
||||
<text class="card-title">即将升级</text>
|
||||
<view class="header-row">
|
||||
<text class="card-title">即将升级藏品</text>
|
||||
<!-- 右上角图例:标识两条进度条 -->
|
||||
<view class="legend">
|
||||
<view class="legend-item" style="margin-bottom: 14rpx">
|
||||
<image
|
||||
class="legend-tube-img"
|
||||
src="/static/icon/like-after.png"
|
||||
mode="heightFix"
|
||||
/>
|
||||
<view class="legend-dot legend-dot-cyan">
|
||||
<text class="legend-text">获赞数</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<image
|
||||
class="legend-tube-img"
|
||||
src="/static/assetDetail/time.png"
|
||||
mode="heightFix"
|
||||
/>
|
||||
<view class="legend-dot legend-dot-pink">
|
||||
<text class="legend-text">展出时长</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!items || items.length === 0" class="empty-row">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
<view v-else class="upgrades-list">
|
||||
<view
|
||||
v-for="item in items"
|
||||
:key="item.asset_id"
|
||||
class="upgrade-row"
|
||||
>
|
||||
<view v-for="item in items" :key="item.asset_id" class="upgrade-row">
|
||||
<view class="upgrade-thumb">
|
||||
<view class="thumb-circle" :style="{ background: 'linear-gradient(135deg, #FF6B6B 0%, #FFB199 100%)' }">
|
||||
<text class="thumb-letter">{{ item.asset_name[0] }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="item.asset_thumb"
|
||||
class="upgrade-thumb-img"
|
||||
:src="item.asset_thumb"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="upgrade-progress">
|
||||
<view class="progress-bar progress-cyan">
|
||||
<view class="progress-fill" :style="{ width: item.like_progress + '%' }"></view>
|
||||
<view
|
||||
class="progress-fill"
|
||||
:style="{ width: item.like_progress + '%' }"
|
||||
></view>
|
||||
<text class="progress-text">{{ item.like_progress }}%</text>
|
||||
</view>
|
||||
<view class="progress-bar progress-pink">
|
||||
<view class="progress-fill" :style="{ width: item.duration_progress + '%' }"></view>
|
||||
<view
|
||||
class="progress-fill"
|
||||
:style="{ width: item.duration_progress + '%' }"
|
||||
></view>
|
||||
<text class="progress-text">{{ item.duration_progress }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -33,25 +63,85 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
items: { type: Array, default: () => [] }, // UpcomingLevelUpItem[]
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.upcoming-upgrades {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 17rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.15);
|
||||
background: linear-gradient(
|
||||
106.77deg,
|
||||
rgba(255, 223, 119, 0.52) -9.76%,
|
||||
rgba(185, 132, 255, 0.52) 44.65%,
|
||||
rgba(255, 129, 131, 0.52) 117.82%
|
||||
);
|
||||
|
||||
border-radius: 14px;
|
||||
padding: 16rpx;
|
||||
box-shadow: 0px 4px 4px 0px rgba(189, 50, 50, 0.25);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
// 标题行:标题在左,图例在右(右上角)
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
// 进度条图例
|
||||
.legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.legend-tube-img {
|
||||
width: 22rpx;
|
||||
height: 22rpx;
|
||||
position: absolute;
|
||||
top: -4rpx;
|
||||
left: -16rpx;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 64rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.legend-dot-cyan {
|
||||
background: #5edfff;
|
||||
box-shadow: 0 0 4rpx rgba(94, 223, 255, 0.6);
|
||||
}
|
||||
|
||||
.legend-dot-pink {
|
||||
background: #ff6b84;
|
||||
box-shadow: 0 0 4rpx rgba(255, 107, 132, 0.6);
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
font-size: 8rpx;
|
||||
color: rgba(255, 241, 163, 1);
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
}
|
||||
|
||||
.upgrades-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -61,27 +151,35 @@ defineProps({
|
||||
.upgrade-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.upgrade-thumb {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
width: 56rpx;
|
||||
height: 67rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.thumb-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
// overflow: hidden; // 裁切 image 贴合圆角
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
|
||||
position: relative; // 作为 .upgrade-thumb-badge 的定位上下文
|
||||
left: 8rpx;
|
||||
z-index: 2;
|
||||
transform: rotate(-10deg);
|
||||
box-shadow: 0px 4px 4px 0px rgba(214, 53, 53, 0.43);
|
||||
}
|
||||
|
||||
.thumb-letter {
|
||||
font-size: 24rpx;
|
||||
// 藏品缩略图:填满 .upgrade-thumb 容器
|
||||
.upgrade-thumb-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 无图时的 emoji 兜底
|
||||
.thumb-emoji {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
@ -91,6 +189,7 @@ defineProps({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
@ -108,22 +207,23 @@ defineProps({
|
||||
}
|
||||
|
||||
.progress-cyan .progress-fill {
|
||||
background: linear-gradient(90deg, #5EDFFF 0%, #FFC8C8 100%);
|
||||
background: linear-gradient(90deg, #5edfff 0%, #ffc8c8 100%);
|
||||
}
|
||||
|
||||
.progress-pink .progress-fill {
|
||||
background: linear-gradient(90deg, #FFF375 0%, #FF6B84 100%);
|
||||
background: linear-gradient(90deg, #fff375 0%, #ff6b84 100%);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
position: absolute;
|
||||
right: 6rpx;
|
||||
// right: 6rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 16rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 18rpx;
|
||||
font-weight: 400;
|
||||
color: rgba(255, 241, 163, 1);
|
||||
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.84);
|
||||
}
|
||||
|
||||
.empty-row {
|
||||
|
||||
@ -5,53 +5,62 @@
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="loading.overall"
|
||||
@refresherrefresh="handlePullDownRefresh"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="dashboard-container">
|
||||
<DashboardHeader
|
||||
:active-tab="activeTab"
|
||||
@update:active-tab="handleTabChange"
|
||||
/>
|
||||
<view class="dashboard-box">
|
||||
<!-- Tab 1: 水晶相关 -->
|
||||
<view v-if="activeTab === 'crystal'" class="dashboard-content">
|
||||
<CrystalOverview
|
||||
:data="data.today"
|
||||
:loading="loading.today"
|
||||
:error="error.today"
|
||||
@retry="refresh('today')"
|
||||
/>
|
||||
<IncomeCurve
|
||||
:points="data.curve?.points || []"
|
||||
:loading="loading.curve"
|
||||
:error="error.curve"
|
||||
@retry="refresh('curve')"
|
||||
/>
|
||||
<ExhibitionCenter
|
||||
:data="data.exhibition"
|
||||
:loading="loading.exhibition"
|
||||
:error="error.exhibition"
|
||||
@retry="refresh('exhibition')"
|
||||
/>
|
||||
<LikeIncomeBoard
|
||||
:stats="
|
||||
data.likeIncome
|
||||
? {
|
||||
total_like_count: data.likeIncome.total_like_count,
|
||||
total_income: data.likeIncome.total_income,
|
||||
}
|
||||
: null
|
||||
"
|
||||
:levels="data.likeIncome?.levels || []"
|
||||
:loading="loading.likeIncome"
|
||||
:error="error.likeIncome"
|
||||
@retry="refresh('likeIncome')"
|
||||
/>
|
||||
<CollectionMatrix
|
||||
:top-five="data.topAssets?.items || []"
|
||||
:levels="data.levels?.items || []"
|
||||
:upgrades="data.upgrades || { upcoming: [], recent: [] }"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- Tab 1: 水晶相关 -->
|
||||
<view v-if="activeTab === 'crystal'" class="dashboard-content">
|
||||
<CrystalOverview
|
||||
:data="data.today"
|
||||
:loading="loading.today"
|
||||
:error="error.today"
|
||||
@retry="refresh('today')"
|
||||
/>
|
||||
<IncomeCurve
|
||||
:points="data.curve?.points || []"
|
||||
:loading="loading.curve"
|
||||
:error="error.curve"
|
||||
@retry="refresh('curve')"
|
||||
/>
|
||||
<ExhibitionCenter
|
||||
:data="data.exhibition"
|
||||
:loading="loading.exhibition"
|
||||
:error="error.exhibition"
|
||||
@retry="refresh('exhibition')"
|
||||
/>
|
||||
<LikeIncomeBoard
|
||||
:stats="data.likeIncome ? { total_like_count: data.likeIncome.total_like_count, total_income: data.likeIncome.total_income } : null"
|
||||
:levels="data.likeIncome?.levels || []"
|
||||
:loading="loading.likeIncome"
|
||||
:error="error.likeIncome"
|
||||
@retry="refresh('likeIncome')"
|
||||
/>
|
||||
<CollectionMatrix
|
||||
:top-five="data.topAssets?.items || []"
|
||||
:levels="data.levels?.items || []"
|
||||
:upgrades="data.upgrades || { upcoming: [], recent: [] }"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- Tab 2: 赛季总览(占位) -->
|
||||
<view v-else class="dashboard-content">
|
||||
<view class="season-placeholder">
|
||||
<text class="placeholder-icon">🏆</text>
|
||||
<text class="placeholder-title">赛季总览 · 即将上线</text>
|
||||
<text class="placeholder-sub">历史赛季数据正在筹备中</text>
|
||||
<!-- Tab 2: 赛季总览(占位) -->
|
||||
<view v-else class="dashboard-content">
|
||||
<view class="season-placeholder">
|
||||
<text class="placeholder-icon">🏆</text>
|
||||
<text class="placeholder-title">赛季总览 · 即将上线</text>
|
||||
<text class="placeholder-sub">历史赛季数据正在筹备中</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -59,55 +68,58 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import DashboardHeader from './components/DashboardHeader.vue'
|
||||
import CrystalOverview from './components/CrystalOverview.vue'
|
||||
import IncomeCurve from './components/IncomeCurve.vue'
|
||||
import ExhibitionCenter from './components/ExhibitionCenter.vue'
|
||||
import LikeIncomeBoard from './components/LikeIncomeBoard.vue'
|
||||
import CollectionMatrix from './components/CollectionMatrix.vue'
|
||||
import { useDashboardData } from '@/composables/useDashboardData'
|
||||
import { ref } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import DashboardHeader from "./components/DashboardHeader.vue";
|
||||
import CrystalOverview from "./components/CrystalOverview.vue";
|
||||
import IncomeCurve from "./components/IncomeCurve.vue";
|
||||
import ExhibitionCenter from "./components/ExhibitionCenter.vue";
|
||||
import LikeIncomeBoard from "./components/LikeIncomeBoard.vue";
|
||||
import CollectionMatrix from "./components/CollectionMatrix.vue";
|
||||
import { useDashboardData } from "@/composables/useDashboardData";
|
||||
|
||||
const activeTab = ref('crystal')
|
||||
const starId = ref(uni.getStorageSync('star_id') || null)
|
||||
const isFirstShow = ref(true)
|
||||
const activeTab = ref("crystal");
|
||||
const starId = ref(uni.getStorageSync("star_id") || null);
|
||||
const isFirstShow = ref(true);
|
||||
|
||||
const { loading, error, data, refresh } = useDashboardData({
|
||||
starId: starId.value,
|
||||
})
|
||||
});
|
||||
|
||||
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时 cache-aware 刷新
|
||||
function handleTabChange(tab) {
|
||||
activeTab.value = tab
|
||||
if (tab === 'crystal') {
|
||||
activeTab.value = tab;
|
||||
if (tab === "crystal") {
|
||||
// refresh() 默认走 30 分钟缓存(不闪骨架屏);需要强刷用 refresh(null, true)
|
||||
refresh()
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
async function handlePullDownRefresh() {
|
||||
await refresh(null, true) // force=true 绕缓存
|
||||
uni.stopPullDownRefresh()
|
||||
await refresh(null, true); // force=true 绕缓存
|
||||
uni.stopPullDownRefresh();
|
||||
}
|
||||
|
||||
// 从其他页面返回时强制刷新(绕 30 分钟缓存,spec §4.1)
|
||||
// 首次 onShow 跳过:composable 内部已主动 loadAll(),避免冷启动 14 个并发请求
|
||||
onShow(() => {
|
||||
if (isFirstShow.value) {
|
||||
isFirstShow.value = false
|
||||
return
|
||||
isFirstShow.value = false;
|
||||
return;
|
||||
}
|
||||
refresh(null, true)
|
||||
})
|
||||
refresh(null, true);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/uni.scss';
|
||||
@import "@/uni.scss";
|
||||
.dashboard-page-bg {
|
||||
background: $d-page-bg;
|
||||
min-height: 100vh;
|
||||
// 多层背景:第一层 $d-page-bg 渐变作为蒙层(在最上),第二层是 bj.png 图片
|
||||
background:
|
||||
$d-page-bg,
|
||||
url("/static/dashboard/bj.png") center / cover no-repeat;
|
||||
}
|
||||
.dashboard-scroll {
|
||||
height: 100vh;
|
||||
@ -115,8 +127,26 @@ onShow(() => {
|
||||
.dashboard-container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.dashboard-box {
|
||||
min-height: 100vh;
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dashboard-content {
|
||||
padding: 24rpx 32rpx 80rpx;
|
||||
padding: 8px 9px 0;
|
||||
background: linear-gradient(
|
||||
174.13deg,
|
||||
rgba(255, 149, 151, 0.57) -6.18%,
|
||||
rgba(128, 223, 255, 0.57) 34.5%,
|
||||
rgba(184, 184, 184, 0.57) 73.48%,
|
||||
rgba(217, 217, 217, 0.57) 111.34%
|
||||
);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 22px;
|
||||
}
|
||||
.season-placeholder {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
@ -127,7 +157,18 @@ onShow(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.placeholder-icon { font-size: 96rpx; margin-bottom: 24rpx; }
|
||||
.placeholder-title { color: #ffffff; font-size: 36rpx; font-weight: 700; margin-bottom: 16rpx; }
|
||||
.placeholder-sub { color: rgba(255, 255, 255, 0.7); font-size: 26rpx; }
|
||||
.placeholder-icon {
|
||||
font-size: 96rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.placeholder-title {
|
||||
color: #ffffff;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.placeholder-sub {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 26rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
After Width: | Height: | Size: 63 KiB |
BIN
frontend/static/dashboard/bj.png
Normal file
|
After Width: | Height: | Size: 975 KiB |
BIN
frontend/static/dashboard/crystal-bg.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
frontend/static/dashboard/dashboard-title.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
frontend/static/dashboard/exhibition-bj.png
Normal file
|
After Width: | Height: | Size: 358 KiB |
BIN
frontend/static/dashboard/header-bj.png
Normal file
|
After Width: | Height: | Size: 481 KiB |
BIN
frontend/static/dashboard/image-bj.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
frontend/static/dashboard/liked-bj.png
Normal file
|
After Width: | Height: | Size: 781 KiB |
BIN
frontend/static/dashboard/season-bg.png
Normal file
|
After Width: | Height: | Size: 420 KiB |
BIN
frontend/static/dashboard/ucharts-bj.png
Normal file
|
After Width: | Height: | Size: 599 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
@ -87,7 +87,8 @@ $d-progress-cyan: linear-gradient(90deg, #5EDFFF 0%, #FFC8C8 100%);
|
||||
$d-progress-pink: linear-gradient(90deg, #FFF375 0%, #FF6B84 100%);
|
||||
$d-bar-blue-yellow: linear-gradient(90deg, #1BAFEE 0%, #FFCC14 100%);
|
||||
$d-bar-fill: linear-gradient(135deg, #FFDF77 0%, #B984FF 60%, #FF8183 100%);
|
||||
$d-page-bg: linear-gradient(153deg, #FF9597 0%, #80DFFF 33%, #B8B8B8 74%, #D9D9D9 100%);
|
||||
$d-page-bg: linear-gradient(174.13deg, rgba(255, 149, 151, 0.17) -6.18%, rgba(128, 223, 255, 0.17) 34.5%, rgba(184, 184, 184, 0.17) 73.48%, rgba(217, 217, 217, 0.17) 111.34%);
|
||||
|
||||
|
||||
/* 5 个等级专属渐变(环图、徽章、TOP 徽章) */
|
||||
$d-level-ur: linear-gradient(135deg, #FF8A65 0%, #FFD740 100%);
|
||||
|
||||
@ -3745,10 +3745,28 @@ function drawAreaDataPoints(series, opts, config, context) {
|
||||
context.beginPath();
|
||||
context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));
|
||||
if (areaOption.gradient) {
|
||||
let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
|
||||
gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));
|
||||
gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
|
||||
context.setFillStyle(gradient);
|
||||
// [patch] 支持 series.linearColor + linearDirection 自定义多色渐变
|
||||
// linearColor: [[offset, cssColor], ...]
|
||||
// linearDirection: 'horizontal' (默认) | 'vertical'
|
||||
if (eachSeries.linearColor && Array.isArray(eachSeries.linearColor) && eachSeries.linearColor.length > 0) {
|
||||
let gradient;
|
||||
let xs = opts.chartData.xAxisData && opts.chartData.xAxisData.startX;
|
||||
let xe = opts.chartData.xAxisData && opts.chartData.xAxisData.endX;
|
||||
if (eachSeries.linearDirection === 'vertical' || xs == null || xe == null) {
|
||||
gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
|
||||
} else {
|
||||
gradient = context.createLinearGradient(xs, 0, xe, 0);
|
||||
}
|
||||
eachSeries.linearColor.forEach(function(stop) {
|
||||
gradient.addColorStop(String(stop[0]), stop[1]);
|
||||
});
|
||||
context.setFillStyle(gradient);
|
||||
} else {
|
||||
let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
|
||||
gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));
|
||||
gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
|
||||
context.setFillStyle(gradient);
|
||||
}
|
||||
} else {
|
||||
context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));
|
||||
}
|
||||
@ -5171,6 +5189,11 @@ function drawArcbarDataPoints(series, opts, config, context) {
|
||||
gap: 2 ,
|
||||
linearType: 'none',
|
||||
customColor: [],
|
||||
// [PATCH] 背景渐变支持:与 .linearType/.customColor 对称,但作用于 background 描边
|
||||
// 用法:backgroundLinearType:'custom' + backgroundLinearColor:[[pos,color],...] + backgroundLinearAngle(度, CSS 语义 0=下→上, 90=左→右)
|
||||
backgroundLinearType: 'none',
|
||||
backgroundLinearColor: [],
|
||||
backgroundLinearAngle: 0,
|
||||
}, opts.extra.arcbar);
|
||||
series = getArcbarDataPoints(series, arcbarOption, process);
|
||||
var centerPosition;
|
||||
@ -5200,7 +5223,26 @@ function drawArcbarDataPoints(series, opts, config, context) {
|
||||
let eachSeries = series[i];
|
||||
//背景颜色
|
||||
context.setLineWidth(arcbarOption.width * opts.pix);
|
||||
context.setStrokeStyle(arcbarOption.backgroundColor || '#E9E9E9');
|
||||
// [PATCH] 背景渐变:若启用 backgroundLinearType=='custom',按 CSS 角度 + 颜色 stop 列表
|
||||
// 在 canvas 上生成 createLinearGradient,覆盖 backgroundColor 的纯色描边
|
||||
var bgStroke = arcbarOption.backgroundColor || '#E9E9E9';
|
||||
if (arcbarOption.backgroundLinearType == 'custom' && Array.isArray(arcbarOption.backgroundLinearColor) && arcbarOption.backgroundLinearColor.length >= 2) {
|
||||
var bgAngle = (arcbarOption.backgroundLinearAngle || 0) * Math.PI / 180;
|
||||
// CSS α 度:0%=α+180° 处,100%=α 处(顺时针从正上)
|
||||
// 半径取 ring 外缘 (radius + width/2),让渐变覆盖整条环
|
||||
var bgHalf = radius + (arcbarOption.width * opts.pix) / 2;
|
||||
var bgX1 = centerPosition.x - bgHalf * Math.sin(bgAngle);
|
||||
var bgY1 = centerPosition.y + bgHalf * Math.cos(bgAngle);
|
||||
var bgX2 = centerPosition.x + bgHalf * Math.sin(bgAngle);
|
||||
var bgY2 = centerPosition.y - bgHalf * Math.cos(bgAngle);
|
||||
var bgGrd = context.createLinearGradient(bgX1, bgY1, bgX2, bgY2);
|
||||
for (var bs = 0; bs < arcbarOption.backgroundLinearColor.length; bs++) {
|
||||
var bsStop = arcbarOption.backgroundLinearColor[bs];
|
||||
bgGrd.addColorStop(bsStop[0], bsStop[1]);
|
||||
}
|
||||
bgStroke = bgGrd;
|
||||
}
|
||||
context.setStrokeStyle(bgStroke);
|
||||
context.setLineCap(arcbarOption.lineCap);
|
||||
context.beginPath();
|
||||
if (arcbarOption.type == 'default') {
|
||||
|
||||
@ -55,11 +55,11 @@ export async function mockExhibitionSummary({ star_id }) {
|
||||
total_duration: '712:13:56',
|
||||
total_earnings: 39721,
|
||||
top5: [
|
||||
{ asset_id: 1, asset_name: '璀璨星河', asset_thumb: '', duration_7d: '144:13:56', earnings_7d: 2173, avg_earnings: 15 },
|
||||
{ asset_id: 2, asset_name: '夏日微风', asset_thumb: '', duration_7d: '77:13:56', earnings_7d: 1332, avg_earnings: 15 },
|
||||
{ asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '', duration_7d: '64:15:37', earnings_7d: 1201, avg_earnings: 12 },
|
||||
{ asset_id: 4, asset_name: '黎明序曲', asset_thumb: '', duration_7d: '51:22:12', earnings_7d: 783, avg_earnings: 12 },
|
||||
{ asset_id: 5, asset_name: '深海回响', asset_thumb: '', duration_7d: '51:22:12', earnings_7d: 783, avg_earnings: 12 },
|
||||
{ asset_id: 1, asset_name: '璀璨星河', asset_thumb: '/static/sucai/image-07.png', duration_7d: '144:13:56', earnings_7d: 2173, avg_earnings: 15 },
|
||||
{ asset_id: 2, asset_name: '夏日微风', asset_thumb: '/static/sucai/image-19.png', duration_7d: '77:13:56', earnings_7d: 1332, avg_earnings: 15 },
|
||||
{ asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '/static/sucai/image-23.png', duration_7d: '64:15:37', earnings_7d: 1201, avg_earnings: 12 },
|
||||
{ asset_id: 4, asset_name: '黎明序曲', asset_thumb: '/static/sucai/image-38.png', duration_7d: '51:22:12', earnings_7d: 783, avg_earnings: 12 },
|
||||
{ asset_id: 5, asset_name: '深海回响', asset_thumb: '/static/sucai/image-46.png', duration_7d: '51:22:12', earnings_7d: 783, avg_earnings: 12 },
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -74,11 +74,11 @@ export async function mockLikeIncomeByLevel({ star_id }) {
|
||||
total_like_count: 231,
|
||||
total_income: 12719,
|
||||
levels: [
|
||||
{ level: 'UR', asset_count: 1, total_income: 723, thumb: '' },
|
||||
{ level: 'SSR', asset_count: 2, total_income: 381, thumb: '' },
|
||||
{ level: 'SR', asset_count: 5, total_income: 233, thumb: '' },
|
||||
{ level: 'SR', asset_count: 4, total_income: 169, thumb: '' },
|
||||
{ level: 'R', asset_count: 6, total_income: 57, thumb: '' },
|
||||
{ level: 'UR', asset_count: 1, total_income: 723, thumb: '/static/sucai/image-03.png' },
|
||||
{ level: 'SSR', asset_count: 2, total_income: 381, thumb: '/static/sucai/image-14.png' },
|
||||
{ level: 'SR', asset_count: 5, total_income: 233, thumb: '/static/sucai/image-27.png' },
|
||||
{ level: 'SR', asset_count: 4, total_income: 169, thumb: '/static/sucai/image-35.png' },
|
||||
{ level: 'R', asset_count: 6, total_income: 57, thumb: '/static/sucai/image-49.png' },
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -91,11 +91,11 @@ export async function mockTopAssets({ star_id }) {
|
||||
code: 200,
|
||||
data: {
|
||||
items: [
|
||||
{ asset_id: 1, asset_name: '璀璨星河', asset_thumb: '', total_earnings: 8420, rank: 1 },
|
||||
{ asset_id: 2, asset_name: '夏日微风', asset_thumb: '', total_earnings: 6230, rank: 2 },
|
||||
{ asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '', total_earnings: 5180, rank: 3 },
|
||||
{ asset_id: 4, asset_name: '黎明序曲', asset_thumb: '', total_earnings: 4320, rank: 4 },
|
||||
{ asset_id: 5, asset_name: '深海回响', asset_thumb: '', total_earnings: 3980, rank: 5 },
|
||||
{ asset_id: 1, asset_name: '璀璨星河', asset_thumb: '/static/sucai/image-04.png', total_earnings: 8420, rank: 1 },
|
||||
{ asset_id: 2, asset_name: '夏日微风', asset_thumb: '/static/sucai/image-11.png', total_earnings: 6230, rank: 2 },
|
||||
{ asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '/static/sucai/image-26.png', total_earnings: 5180, rank: 3 },
|
||||
{ asset_id: 4, asset_name: '黎明序曲', asset_thumb: '/static/sucai/image-39.png', total_earnings: 4320, rank: 4 },
|
||||
{ asset_id: 5, asset_name: '深海回响', asset_thumb: '/static/sucai/image-50.png', total_earnings: 3980, rank: 5 },
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -126,14 +126,14 @@ export async function mockUpgradeProgress({ star_id }) {
|
||||
code: 200,
|
||||
data: {
|
||||
upcoming: [
|
||||
{ asset_id: 1, asset_name: '璀璨星河', asset_thumb: '', like_progress: 73, duration_progress: 92 },
|
||||
{ asset_id: 2, asset_name: '夏日微风', asset_thumb: '', like_progress: 75, duration_progress: 96 },
|
||||
{ asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '', like_progress: 97, duration_progress: 71 },
|
||||
{ asset_id: 1, asset_name: '璀璨星河', asset_thumb: '/static/sucai/image-02.png', like_progress: 73, duration_progress: 92 },
|
||||
{ asset_id: 2, asset_name: '夏日微风', asset_thumb: '/static/sucai/image-16.png', like_progress: 75, duration_progress: 96 },
|
||||
{ asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '/static/sucai/image-28.png', like_progress: 97, duration_progress: 71 },
|
||||
],
|
||||
recent: [
|
||||
{ asset_id: 4, asset_name: '黎明序曲', asset_thumb: '', new_level: 'SSR', upgrade_time: Date.now() - 3600000 },
|
||||
{ asset_id: 5, asset_name: '深海回响', asset_thumb: '', new_level: 'SR', upgrade_time: Date.now() - 86400000 },
|
||||
{ asset_id: 6, asset_name: '晨曦微光', asset_thumb: '', new_level: 'SR', upgrade_time: Date.now() - 172800000 },
|
||||
{ asset_id: 4, asset_name: '黎明序曲', asset_thumb: '/static/sucai/image-02.png', new_level: 'SSR', upgrade_time: Date.now() - 3600000 },
|
||||
{ asset_id: 5, asset_name: '深海回响', asset_thumb: '/static/sucai/image-16.png', new_level: 'SR', upgrade_time: Date.now() - 86400000 },
|
||||
{ asset_id: 6, asset_name: '晨曦微光', asset_thumb: '/static/sucai/image-28.png', new_level: 'SR', upgrade_time: Date.now() - 172800000 },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||