|
| 1 | +<script setup> |
| 2 | +import { inject, ref, computed, onMounted } from 'vue' |
| 3 | +
|
| 4 | +const persona = inject('persona') |
| 5 | +const visible = ref(false) |
| 6 | +const sectionRef = ref(null) |
| 7 | +
|
| 8 | +const DIMENSIONS = [ |
| 9 | + { key: 'immersion', name: '沉浸度', icon: '🌊', desc: '连续观看的沉浸倾向' }, |
| 10 | + { key: 'quality', name: '精品度', icon: '💎', desc: '对作品品质的要求' }, |
| 11 | + { key: 'diversity', name: '广度', icon: '🌈', desc: '类型覆盖的多样性' }, |
| 12 | + { key: 'depth', name: '深度', icon: '🔮', desc: '长篇剧集的深耕程度' }, |
| 13 | + { key: 'night_owl', name: '夜猫指数', icon: '🌙', desc: '深夜观影的倾向' }, |
| 14 | + { key: 'freshness', name: '新鲜度', icon: '✨', desc: '追新剧的积极性' }, |
| 15 | + { key: 'global', name: '国际化', icon: '🌍', desc: '非主流地区内容比例' }, |
| 16 | + { key: 'binge', name: '连贯追剧', icon: '🔥', desc: '一口气追完的节奏' }, |
| 17 | +] |
| 18 | +
|
| 19 | +const dimensions = computed(() => { |
| 20 | + const r = persona.value?.radar || {} |
| 21 | + return DIMENSIONS.map(d => ({ ...d, value: r[d.key] || 0 })) |
| 22 | +}) |
| 23 | +
|
| 24 | +const overallScore = computed(() => { |
| 25 | + const vals = dimensions.value.map(d => d.value) |
| 26 | + if (!vals.length) return 0 |
| 27 | + return Math.round(vals.reduce((a, b) => a + b, 0) / vals.length) |
| 28 | +}) |
| 29 | +
|
| 30 | +const scoreLevel = computed(() => { |
| 31 | + const s = overallScore.value |
| 32 | + if (s >= 80) return { label: '硬核观众', color: 'var(--primary-bright)' } |
| 33 | + if (s >= 60) return { label: '热情观众', color: 'var(--primary-bright)' } |
| 34 | + if (s >= 40) return { label: '休闲观众', color: 'var(--text-2)' } |
| 35 | + return { label: '轻度观众', color: 'var(--text-3)' } |
| 36 | +}) |
| 37 | +
|
| 38 | +function barColor(value) { |
| 39 | + if (value >= 75) return 'linear-gradient(90deg, var(--primary), var(--primary-bright))' |
| 40 | + if (value >= 50) return 'linear-gradient(90deg, var(--primary-dim), var(--primary))' |
| 41 | + if (value >= 25) return 'linear-gradient(90deg, var(--text-dim), var(--primary-dim))' |
| 42 | + return 'linear-gradient(90deg, var(--text-dim), var(--text-dim))' |
| 43 | +} |
| 44 | +
|
| 45 | +onMounted(() => { |
| 46 | + const obs = new IntersectionObserver((entries) => { |
| 47 | + entries.forEach(e => { if (e.isIntersecting) visible.value = true }) |
| 48 | + }, { threshold: 0.15 }) |
| 49 | + if (sectionRef.value) obs.observe(sectionRef.value) |
| 50 | +}) |
| 51 | +</script> |
| 52 | +
|
| 53 | +<template> |
| 54 | + <section ref="sectionRef" class="dim-section" v-if="persona"> |
| 55 | + <div class="section-content"> |
| 56 | + <p class="section-label reveal-up" :class="{ visible }">📊 观影维度测评</p> |
| 57 | +
|
| 58 | + <!-- 综合评分 --> |
| 59 | + <div class="overall-card bean-card reveal-scale" :class="{ visible }"> |
| 60 | + <div class="overall-ring" :style="{ '--ring-color': scoreLevel.color }"> |
| 61 | + <svg viewBox="0 0 120 120" class="ring-svg"> |
| 62 | + <circle cx="60" cy="60" r="52" class="ring-track" /> |
| 63 | + <circle |
| 64 | + cx="60" cy="60" r="52" |
| 65 | + class="ring-fill" |
| 66 | + :stroke-dasharray="2 * Math.PI * 52" |
| 67 | + :stroke-dashoffset="2 * Math.PI * 52 * (1 - overallScore / 100)" |
| 68 | + /> |
| 69 | + </svg> |
| 70 | + <div class="ring-inner"> |
| 71 | + <span class="ring-num">{{ overallScore }}</span> |
| 72 | + <span class="ring-unit">/100</span> |
| 73 | + </div> |
| 74 | + </div> |
| 75 | + <div class="overall-info"> |
| 76 | + <span class="overall-label">综合观影指数</span> |
| 77 | + <span class="overall-tag" :style="{ color: scoreLevel.color }">{{ scoreLevel.label }}</span> |
| 78 | + <span class="overall-archetype" v-if="persona.archetype">{{ persona.archetype }}</span> |
| 79 | + </div> |
| 80 | + </div> |
| 81 | +
|
| 82 | + <!-- 维度列表 --> |
| 83 | + <div class="dim-list stagger" :class="{ visible }"> |
| 84 | + <div v-for="(d, i) in dimensions" :key="d.key" class="dim-item bean-card"> |
| 85 | + <div class="dim-header"> |
| 86 | + <span class="dim-icon">{{ d.icon }}</span> |
| 87 | + <span class="dim-name">{{ d.name }}</span> |
| 88 | + <span class="dim-value" :style="{ color: d.value >= 60 ? 'var(--primary-bright)' : 'var(--text-2)' }">{{ d.value }}</span> |
| 89 | + </div> |
| 90 | + <div class="dim-bar-bg"> |
| 91 | + <div |
| 92 | + class="dim-bar-fill" |
| 93 | + :style="{ width: visible ? d.value + '%' : '0%', background: barColor(d.value) }" |
| 94 | + ></div> |
| 95 | + </div> |
| 96 | + <p class="dim-desc">{{ d.desc }}</p> |
| 97 | + </div> |
| 98 | + </div> |
| 99 | + </div> |
| 100 | + </section> |
| 101 | +</template> |
| 102 | +
|
| 103 | +<style scoped> |
| 104 | +.dim-section { |
| 105 | + min-height: 100vh; display: flex; align-items: center; justify-content: center; |
| 106 | + padding: var(--section-gap) var(--page-margin); |
| 107 | +} |
| 108 | +.section-content { max-width: 680px; width: 100%; position: relative; z-index: 1; } |
| 109 | +
|
| 110 | +/* ── 综合评分卡 ── */ |
| 111 | +.overall-card { |
| 112 | + display: flex; align-items: center; gap: 28px; |
| 113 | + padding: var(--space-lg); margin-bottom: var(--space-lg); |
| 114 | +} |
| 115 | +.overall-ring { |
| 116 | + position: relative; width: 120px; height: 120px; flex-shrink: 0; |
| 117 | +} |
| 118 | +.ring-svg { width: 100%; height: 100%; transform: rotate(-90deg); } |
| 119 | +.ring-track { |
| 120 | + fill: none; stroke: rgba(255,255,255,0.06); stroke-width: 6; |
| 121 | +} |
| 122 | +.ring-fill { |
| 123 | + fill: none; stroke: var(--ring-color, var(--primary)); stroke-width: 6; |
| 124 | + stroke-linecap: round; |
| 125 | + transition: stroke-dashoffset 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.3s; |
| 126 | +} |
| 127 | +.ring-inner { |
| 128 | + position: absolute; inset: 0; display: flex; flex-direction: column; |
| 129 | + align-items: center; justify-content: center; |
| 130 | +} |
| 131 | +.ring-num { |
| 132 | + font-size: 2.2rem; font-weight: 800; color: var(--text-1); |
| 133 | + line-height: 1; font-variant-numeric: tabular-nums; |
| 134 | +} |
| 135 | +.ring-unit { font-size: 0.78rem; color: var(--text-3); } |
| 136 | +
|
| 137 | +.overall-info { |
| 138 | + display: flex; flex-direction: column; gap: 6px; |
| 139 | +} |
| 140 | +.overall-label { font-size: 0.82rem; color: var(--text-3); letter-spacing: 1px; } |
| 141 | +.overall-tag { font-size: 1.3rem; font-weight: 700; } |
| 142 | +.overall-archetype { |
| 143 | + font-size: 0.92rem; color: var(--primary); font-weight: 600; |
| 144 | +} |
| 145 | +
|
| 146 | +/* ── 维度列表 ── */ |
| 147 | +.dim-list { |
| 148 | + display: grid; grid-template-columns: 1fr 1fr; gap: 14px; |
| 149 | +} |
| 150 | +.dim-item { |
| 151 | + padding: 18px 20px; display: flex; flex-direction: column; gap: 10px; |
| 152 | +} |
| 153 | +.dim-header { |
| 154 | + display: flex; align-items: center; gap: 8px; |
| 155 | +} |
| 156 | +.dim-icon { font-size: 1.1rem; } |
| 157 | +.dim-name { |
| 158 | + font-size: 0.92rem; font-weight: 700; color: var(--text-1); |
| 159 | + flex: 1; |
| 160 | +} |
| 161 | +.dim-value { |
| 162 | + font-size: 1.4rem; font-weight: 800; font-variant-numeric: tabular-nums; |
| 163 | + line-height: 1; |
| 164 | +} |
| 165 | +.dim-bar-bg { |
| 166 | + height: 6px; background: rgba(255,255,255,0.05); border-radius: 3px; |
| 167 | + overflow: hidden; |
| 168 | +} |
| 169 | +.dim-bar-fill { |
| 170 | + height: 100%; border-radius: 3px; |
| 171 | + transition: width 1s cubic-bezier(0.4, 0, 0.2, 1); |
| 172 | + transition-delay: 0.2s; |
| 173 | +} |
| 174 | +.dim-desc { |
| 175 | + font-size: 0.76rem; color: var(--text-3); line-height: 1.5; |
| 176 | +} |
| 177 | +
|
| 178 | +@media (max-width: 768px) { |
| 179 | + .dim-list { grid-template-columns: 1fr; } |
| 180 | + .overall-card { flex-direction: column; text-align: center; gap: 16px; padding: var(--space-md); } |
| 181 | + .overall-info { align-items: center; } |
| 182 | + .ring-num { font-size: 1.8rem; } |
| 183 | + .dim-value { font-size: 1.2rem; } |
| 184 | +} |
| 185 | +</style> |
0 commit comments