Skip to content

Commit a5b90e0

Browse files
committed
improvement(wrapped-ui): 重构常用语词云卡交互与视觉表现
- Card06 文案改为年度常用语,展示候选句数、独特短句数与 topKeyword。 - 移除隐私开关、跳过按钮和粒子爆炸层,简化动效流程与交互路径。 - 词云布局改为百分比坐标 + 自适应缩放,点击词后的例句面板改为 Teleport 固定定位。 - 优化空态文案与再看一遍入口,提升移动端可读性与操作一致性。
1 parent 236d0ae commit a5b90e0

2 files changed

Lines changed: 258 additions & 285 deletions

File tree

frontend/components/wrapped/cards/Card06KeywordsWordCloud.vue

Lines changed: 12 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,13 @@
99
:style="{ zIndex: 9999 }"
1010
@pointerdown="onStagePointerDown"
1111
>
12-
<!-- 控制按钮 -->
13-
<div class="absolute top-3 right-3 z-40 flex items-center gap-2" data-no-accel>
14-
<button
15-
type="button"
16-
class="kw-chip"
17-
:class="privacyMode ? 'kw-chip--on' : ''"
18-
@click="privacyMode = !privacyMode"
19-
>
20-
{{ privacyMode ? '隐私:开' : '隐私:关' }}
21-
</button>
22-
<button type="button" class="kw-chip" @click="skipToCloud">跳过</button>
23-
<button type="button" class="kw-chip" @click="replay">重播</button>
24-
</div>
25-
2612
<!-- 提示(accelerated 默认开启,此提示基本不显示) -->
2713
<div
2814
v-if="showHint"
2915
class="absolute bottom-3 right-3 z-30 wrapped-label text-[10px] text-[#00000055] bg-white/55 backdrop-blur rounded-lg px-2 py-1 border border-[#0000000a]"
3016
data-no-accel
3117
>
32-
点击空白处加速 · 右上角可重播
18+
点击空白处加速
3319
</div>
3420

3521
<!-- 气泡层 -->
@@ -44,7 +30,6 @@
4430
>
4531
<div
4632
class="px-3 py-2 text-sm max-w-sm relative msg-bubble whitespace-pre-wrap break-words leading-relaxed bg-[#95EC69] text-black bubble-tail-r"
47-
:class="privacyMode ? 'privacy-blur' : ''"
4833
>
4934
<span v-if="Array.isArray(b.segments) && b.segments.length > 0">
5035
<span v-for="(seg, idx) in b.segments" :key="`${b.id}-${idx}`">
@@ -57,12 +42,6 @@
5742
</div>
5843
</div>
5944

60-
<!-- 粒子 (burst) -->
61-
<canvas
62-
v-show="showParticles"
63-
ref="particleCanvas"
64-
class="absolute inset-0 z-20 pointer-events-none"
65-
/>
6645
</div>
6746
</Teleport>
6847

@@ -75,7 +54,11 @@
7554
你的话,正在涌来。
7655
</template>
7756
<template v-else>
78-
每一句话都是一个气泡,最终爆开成你的年度关键词词云。点击关键词,回看它出现的瞬间。
57+
这一年,你一共发出了 <span class="font-medium text-[#07C160]">{{ card.data?.meta?.matchedCandidates || 0 }}</span> 句简短的表达,其中 <span class="font-medium text-[#07C160]">{{ card.data?.meta?.uniquePhrases || 0 }}</span> 句话成了你的专属口头禅。
58+
<template v-if="card.data?.topKeyword">
59+
「<span class="font-medium text-[#07C160]">{{ card.data.topKeyword.word }}</span>」是你最常说的话,足足被你重复了 <span class="font-medium text-[#07C160]">{{ card.data.topKeyword.count }}</span> 次。
60+
</template>
61+
点击气泡,找回当时的心情。
7962
</template>
8063
</p>
8164
</div>
@@ -87,32 +70,22 @@
8770
class="kw-stage relative w-full h-[56vh] min-h-[360px] max-h-[680px] rounded-[28px] overflow-hidden"
8871
>
8972

90-
<!-- cloud 阶段的控制按钮 -->
91-
<div v-if="phase === 'cloud'" class="absolute top-3 right-3 z-40 flex items-center gap-2">
92-
<button
93-
type="button"
94-
class="kw-chip"
95-
:class="privacyMode ? 'kw-chip--on' : ''"
96-
@click="privacyMode = !privacyMode"
97-
>
98-
{{ privacyMode ? '隐私:开' : '隐私:关' }}
99-
</button>
100-
<button type="button" class="kw-chip" @click="replay">重播</button>
101-
</div>
102-
10373
<!-- 词云 -->
10474
<transition name="cloud-fade">
10575
<div v-if="phase === 'cloud'" class="absolute inset-0 z-30 p-3 sm:p-5">
10676
<KeywordWordCloud
10777
:keywords="keywords"
10878
:examples="examples"
109-
:privacy-mode="privacyMode"
11079
:animate="true"
11180
:reduced-motion="reducedMotion"
11281
/>
11382
</div>
11483
</transition>
11584
</div>
85+
86+
<div v-if="phase === 'cloud'" class="mt-3 flex justify-center">
87+
<button type="button" class="kw-chip" @click="replay">再看一遍</button>
88+
</div>
11689
</div>
11790
</WrappedCardShell>
11891
</div>
@@ -132,13 +105,10 @@ const props = defineProps({
132105
const cardRoot = ref(null)
133106
const stageEl = ref(null)
134107
const overlayEl = ref(null)
135-
const particleCanvas = ref(null)
136108
137109
const phase = ref('idle') // 'idle' | 'storm' | 'packed' | 'merge' | 'burst' | 'cloud'
138110
const hasPlayed = ref(false)
139-
const privacyMode = ref(false)
140111
const accelerated = ref(true) // 默认加速
141-
const showParticles = ref(false)
142112
143113
// 通知父级 deck 隐藏顶部 UI
144114
const deckChromeHidden = inject('deckChromeHidden', ref(false))
@@ -271,7 +241,6 @@ const bubbleSizeForText = (text, compact = false) => {
271241
let stormTimer = null
272242
let packedTimer = null
273243
let mainTl = null
274-
let particleRaf = null
275244
let hardStopTimer = null
276245
let animationStartedAt = 0
277246
let animationDeadlineAt = 0
@@ -299,18 +268,7 @@ const armHardStop = () => {
299268
}, remain + 8)
300269
}
301270
302-
const stopParticles = () => {
303-
showParticles.value = false
304-
if (particleRaf) cancelAnimationFrame(particleRaf)
305-
particleRaf = null
306-
const c = particleCanvas.value
307-
if (c) {
308-
try {
309-
const ctx = c.getContext('2d')
310-
ctx?.clearRect?.(0, 0, c.width, c.height)
311-
} catch {}
312-
}
313-
}
271+
const stopParticles = () => {}
314272
315273
const killTimeline = () => {
316274
if (mainTl) {
@@ -362,71 +320,6 @@ const isVisible = ref(false)
362320
let io = null
363321
const updateVisibility = (v) => { isVisible.value = !!v }
364322
365-
const startParticles = (rng, centerX, centerY) => {
366-
if (!import.meta.client) return
367-
const canvas = particleCanvas.value
368-
if (!canvas || !curViewW || !curViewH) return
369-
370-
const dpr = Math.max(1, Math.min(3, Number(window.devicePixelRatio || 1)))
371-
canvas.width = Math.max(1, Math.round(curViewW * dpr))
372-
canvas.height = Math.max(1, Math.round(curViewH * dpr))
373-
canvas.style.width = `${curViewW}px`
374-
canvas.style.height = `${curViewH}px`
375-
const ctx = canvas.getContext('2d')
376-
if (!ctx) return
377-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
378-
379-
const N = 80
380-
const particles = []
381-
for (let i = 0; i < N; i += 1) {
382-
const ang = rng() * Math.PI * 2
383-
const sp = 120 + rng() * 320
384-
particles.push({
385-
x: centerX,
386-
y: centerY,
387-
vx: Math.cos(ang) * sp,
388-
vy: Math.sin(ang) * sp,
389-
size: 0.8 + rng() * 2.4,
390-
life: 1,
391-
color: rng() < 0.66 ? 'rgba(7,193,96,0.65)' : (rng() < 0.5 ? 'rgba(242,170,0,0.55)' : 'rgba(14,165,233,0.55)')
392-
})
393-
}
394-
395-
showParticles.value = true
396-
const duration = 600
397-
let last = 0
398-
const started = performance.now()
399-
400-
const tick = (now) => {
401-
const t = now - started
402-
const dt = last > 0 ? Math.min((now - last) / 1000, 0.05) : 0.016
403-
last = now
404-
405-
ctx.clearRect(0, 0, curViewW, curViewH)
406-
const p = clamp(t / duration, 0, 1)
407-
const alpha = 1 - p
408-
for (const it of particles) {
409-
it.x += it.vx * dt
410-
it.y += it.vy * dt
411-
it.vx *= 0.86
412-
it.vy *= 0.86
413-
const a = alpha * 0.9
414-
ctx.fillStyle = it.color.replace(/[\d.]+\)$/g, `${a})`)
415-
ctx.beginPath()
416-
ctx.arc(it.x, it.y, it.size, 0, Math.PI * 2)
417-
ctx.fill()
418-
}
419-
420-
if (t < duration) {
421-
particleRaf = requestAnimationFrame(tick)
422-
} else {
423-
stopParticles()
424-
}
425-
}
426-
427-
particleRaf = requestAnimationFrame(tick)
428-
}
429-
430323
const maybeStart = () => {
431324
if (!import.meta.client) return
432325
detectReducedMotion()
@@ -849,10 +742,7 @@ const runMergeBurst = (rng, centerX, centerY) => {
849742
opacity: 0,
850743
scale: 0.92,
851744
ease: 'power3.out',
852-
stagger: staggerBurst,
853-
onStart: () => {
854-
startParticles(rng, centerX, centerY)
855-
}
745+
stagger: staggerBurst
856746
})
857747
858748
const tlTotal = mainTl.totalDuration()
@@ -941,12 +831,6 @@ onBeforeUnmount(() => {
941831
transform: translateY(-1px);
942832
}
943833
944-
.kw-chip--on {
945-
background: rgba(7, 193, 96, 0.12);
946-
border-color: rgba(7, 193, 96, 0.22);
947-
color: rgba(0, 0, 0, 0.75);
948-
}
949-
950834
.kw-bubble {
951835
will-change: transform, opacity;
952836
transform: translate3d(0, 0, 0);

0 commit comments

Comments
 (0)