Skip to content

Commit fc46fd4

Browse files
author
zhoumian
committed
feat: 用观影维度测评替换搜索打分功能,移除 TMDB 前端依赖
1 parent 39eb84e commit fc46fd4

6 files changed

Lines changed: 189 additions & 981 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ jobs:
7070
npm install
7171
npm run build
7272
working-directory: web/
73-
env:
74-
VITE_TMDB_API_KEY: ${{ secrets.TMDB_API_KEY }}
7573

7674
# ⑤ 上传 Pages 产物
7775
- name: 上传 Pages 产物

web/src/App.vue

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import PersonaProfile from '@/components/PersonaProfile.vue'
1212
import AnnualRankings from '@/components/AnnualRankings.vue'
1313
import SharePoster from '@/components/SharePoster.vue'
1414
import YearArchive from '@/components/YearArchive.vue'
15-
import SearchScore from '@/components/SearchScore.vue'
15+
import DimensionAssessment from '@/components/DimensionAssessment.vue'
1616
1717
const {
1818
summary, mediaList, topMedia, persona, recentMeta, loading, error,
@@ -25,14 +25,8 @@ const {
2525
fetchRecentMonth, recentMonthCache,
2626
} = useTraktData()
2727
28-
// 用户画像数据(用于打分
28+
// 用户画像数据(保留 provide 以防其他组件需要
2929
const userProfile = shallowRef(null)
30-
onMounted(async () => {
31-
try {
32-
const resp = await fetch('data/profile.json')
33-
if (resp.ok) userProfile.value = await resp.json()
34-
} catch { /* 画像未生成,静默跳过 */ }
35-
})
3630
provide('profile', userProfile)
3731
3832
provide('summary', summary)
@@ -86,7 +80,7 @@ const sections = [
8680
{ id: 'rankings', label: '榜单' },
8781
{ id: 'poster', label: '海报' },
8882
{ id: 'archive', label: '历年' },
89-
{ id: 'search-score', label: '打分' },
83+
{ id: 'search-score', label: '测评' },
9084
]
9185
9286
const sectionBackgrounds = computed(() => {
@@ -241,7 +235,7 @@ onUnmounted(() => window.removeEventListener('scroll', handleScroll))
241235
<div :id="`section-7`"><AnnualRankings /></div>
242236
<div :id="`section-8`"><SharePoster /></div>
243237
<div :id="`section-9`"><YearArchive /></div>
244-
<div :id="`section-10`"><SearchScore /></div>
238+
<div :id="`section-10`"><DimensionAssessment /></div>
245239
</template>
246240
</div>
247241
</div>
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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

Comments
 (0)