topfans/frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue

168 lines
4.7 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="scattered-ranks">
<!-- 椭圆轨道装饰虚线 -->
<svg class="orbit-svg" viewBox="0 0 375 170" preserveAspectRatio="none">
<defs>
<linearGradient id="sg-orbit-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgba(255,255,255,0.15)" />
<stop offset="50%" stop-color="rgba(255,255,255,0.4)" />
<stop offset="100%" stop-color="rgba(255,250,189,0.85)" />
</linearGradient>
<radialGradient id="sg-center-glow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="rgba(255,250,189,0.5)" />
<stop offset="100%" stop-color="transparent" />
</radialGradient>
</defs>
<ellipse cx="187" cy="55" rx="80" ry="35" fill="url(#sg-center-glow)" />
<ellipse cx="187" cy="55" rx="130" ry="55" fill="none" stroke="url(#sg-orbit-grad)" stroke-width="1.5" stroke-dasharray="3,3" />
<path d="M 57,55 A 130,55 0 0,0 317,55" stroke="rgba(255,250,189,0.85)" stroke-width="2.5" fill="none" />
</svg>
<!-- 9 个散落 item -->
<view
v-for="(p, i) in positions"
:key="p.rank"
class="ring-item"
:class="'r' + (p.rank - 4)"
:style="ringItemStyle(p)"
@click="handleClick(items[i])"
>
<view class="top-label">{{ formatLabel(p.rank) }}</view>
<image
class="cover-image"
:src="(items[i]?.cover_url) || (items[i]?.cover_image) || ''"
mode="aspectFill"
/>
</view>
</view>
</template>
<script setup>
// Note: @keyframes orbit is inlined below (not imported from config.js ORBIT_KEYFRAMES)
// because Vue <style> blocks cannot interpolate JS string constants.
// config.js ORBIT_KEYFRAMES is kept as documentation/source-of-truth.
import { RING_DELAYS } from './config.js'
const props = defineProps({
items: { type: Array, required: true }, // length 9
positions: { type: Array, required: true }, // from generateRingPositions()
})
const emit = defineEmits(['cardClick'])
// 静态 base 位置slot 0 中心 (187, 565)item 46×72top-left = (164, 530)
const BASE_X = 164
const BASE_Y = 530
function ringItemStyle(p) {
return {
left: BASE_X + 'rpx',
top: BASE_Y + 'rpx',
zIndex: p.zIndex,
transform: `scale(${p.scale})`,
animationDelay: RING_DELAYS[p.rank - 4] + 's',
}
}
function formatLabel(rank) {
return 'TOP ' + rank
}
function handleClick(item) {
if (item) emit('cardClick', item)
}
</script>
<style scoped>
.scattered-ranks {
position: relative;
width: 750rpx;
height: 720rpx;
pointer-events: none;
}
.orbit-svg {
position: absolute;
top: 390rpx;
left: 0;
width: 750rpx;
height: 340rpx;
pointer-events: none;
z-index: 0;
}
.ring-item {
position: absolute;
width: 46rpx;
height: 72rpx; /* 14 label + 2 gap + 56 cover */
transform-origin: center;
pointer-events: auto;
cursor: pointer;
display: flex;
flex-direction: column;
animation: orbit 36s linear infinite;
}
.top-label {
width: 46rpx;
height: 14rpx;
background: radial-gradient(ellipse, #C8E6FF, #fff 50%, #4D9AF8);
border-radius: 7rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 7rpx;
font-weight: 900;
color: #FFFABD;
text-shadow: -1rpx 1rpx 2rpx rgba(206, 9, 9, 0.84);
}
/* TOP 4 label 是金渐变(最显眼) */
.r0 .top-label {
background: radial-gradient(ellipse, #FFFFFF, #FFFABD 30%, #4D9AF8 100%);
box-shadow: 0 4rpx 12rpx rgba(255, 250, 189, 0.55);
font-size: 8rpx;
}
.cover-image {
margin-top: 2rpx;
width: 46rpx;
height: 56rpx;
border-radius: 5rpx;
box-shadow: 3rpx 3rpx 6rpx rgba(198, 13, 13, 0.45);
}
.r0 .cover-image {
box-shadow: 0 12rpx 28rpx rgba(255, 32, 36, 0.5), 0 0 24rpx rgba(255, 250, 189, 0.55);
}
</style>
<style>
/* 关键帧:放在非 scoped 块中,让所有 ring-item 共享 */
@keyframes orbit {
0% { transform: translate(0,0) scale(1.15); }
11.11% { transform: translate(84px,-13px) scale(1.05); }
22.22% { transform: translate(157px,-43px) scale(0.95); }
33.33% { transform: translate(113px,-83px) scale(0.85); }
44.44% { transform: translate(45px,-107px) scale(0.75); }
55.55% { transform: translate(-45px,-107px) scale(0.75); }
66.66% { transform: translate(-113px,-83px) scale(0.85); }
77.77% { transform: translate(-156px,-43px) scale(0.95); }
88.88% { transform: translate(-84px,-13px) scale(1.05); }
100% { transform: translate(0,0) scale(1.15); }
}
@keyframes crownPulse {
0%, 100% { transform: translateX(-50%) scale(1); }
50% { transform: translateX(-50%) scale(1.15); }
}
/* 可访问性:减少动画 */
@media (prefers-reduced-motion: reduce) {
.ring-item,
.crown {
animation: none !important;
}
}
</style>