mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-20 06:40:49 +08:00
- wrapped 页面改为:先拉 meta/年份列表,再按页请求单张卡片,首屏更快 - 新增 Card#0 全局概览页(含图表) - 新增 Card#2 消息字数页(含键盘敲击统计与图表) - 新增复古模式:像素字体资源 + CRT Overlay,支持一键开关 - 调整 shared 组件、types/useApi,更新前端依赖与 lock
208 lines
9.9 KiB
Vue
208 lines
9.9 KiB
Vue
<template>
|
||
<div :class="rootClass">
|
||
<div v-if="variant !== 'slide'" class="absolute inset-0 pointer-events-none">
|
||
<div class="absolute -top-24 -left-24 w-80 h-80 bg-[#07C160] opacity-[0.08] rounded-full blur-3xl"></div>
|
||
<div class="absolute -top-20 -right-20 w-96 h-96 bg-[#F2AA00] opacity-[0.07] rounded-full blur-3xl"></div>
|
||
<div class="absolute -bottom-24 left-40 w-96 h-96 bg-[#10AEEF] opacity-[0.07] rounded-full blur-3xl"></div>
|
||
<div class="absolute inset-0 bg-[linear-gradient(rgba(7,193,96,0.05)_1px,transparent_1px),linear-gradient(90deg,rgba(7,193,96,0.05)_1px,transparent_1px)] bg-[size:52px_52px] opacity-[0.35]"></div>
|
||
</div>
|
||
|
||
<div :class="innerClass">
|
||
<template v-if="variant === 'slide'">
|
||
<div class="h-full flex flex-col justify-between">
|
||
<div class="flex items-start justify-between gap-4">
|
||
<div class="wrapped-label text-xs text-[#00000080]">
|
||
WECHAT WRAPPED
|
||
</div>
|
||
<div class="wrapped-body text-xs text-[#00000055]">
|
||
年度回望
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-10 sm:mt-14">
|
||
<h1 class="wrapped-title text-4xl sm:text-6xl text-[#000000e6] leading-[1.05]">
|
||
{{ randomTitle.main }}
|
||
<span class="block mt-3 text-[#07C160]">
|
||
{{ randomTitle.highlight }}
|
||
</span>
|
||
</h1>
|
||
|
||
<div class="mt-7 sm:mt-9 max-w-2xl">
|
||
<p class="wrapped-body text-base sm:text-lg text-[#00000080]">
|
||
{{ randomSubtitle }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pb-1">
|
||
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-[#00000066]">
|
||
<!-- Intentionally left blank (avoid "feature bullet list" tone on the cover). -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<div class="flex items-start justify-between gap-4">
|
||
<div class="wrapped-label text-xs text-[#00000080]">
|
||
WECHAT WRAPPED
|
||
</div>
|
||
<!-- 年份放到右上角(分享视图不包含账号信息) -->
|
||
<span
|
||
class="wrapped-label inline-flex items-center px-3 py-1 rounded-full text-xs bg-[#00000008] text-[#00000099] border border-[#00000010]"
|
||
>
|
||
{{ yearText }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="mt-5 sm:mt-7 flex flex-col gap-2">
|
||
<h1 class="wrapped-title text-3xl sm:text-4xl text-[#000000e6] leading-tight">
|
||
聊天年度总结
|
||
</h1>
|
||
<p class="wrapped-body text-sm sm:text-base text-[#7F7F7F] max-w-2xl">
|
||
从时间里回看你的聊天节奏。第一张卡:年度赛博作息表(24H x 7Days)。
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Badges intentionally removed: keep the hero more human and less "feature list". -->
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
// 50 个主标题(主句 + 高亮句)
|
||
const TITLES = [
|
||
{ main: '把这一年的聊天', highlight: '轻轻翻一翻' },
|
||
{ main: '这一年', highlight: '谁陪你说了最多的话' },
|
||
{ main: '那些深夜的消息', highlight: '都去哪儿了' },
|
||
{ main: '一年的对话', highlight: '值得被温柔记住' },
|
||
{ main: '你的聊天记录里', highlight: '藏着这一年' },
|
||
{ main: '有些人', highlight: '一直在消息列表里陪着你' },
|
||
{ main: '翻开这一年的', highlight: '对话框' },
|
||
{ main: '这一年的问候', highlight: '都在这里了' },
|
||
{ main: '一年又一年', highlight: '聊天框里的人还在吗' },
|
||
{ main: '回头看看', highlight: '这一年你和谁聊得最多' },
|
||
{ main: '你今年说得最多的', highlight: '那个人是谁' },
|
||
{ main: '这一年', highlight: '你在深夜回复过谁' },
|
||
{ main: '你的消息', highlight: '都发给了谁' },
|
||
{ main: '谁在等你的消息', highlight: '你又在等谁的' },
|
||
{ main: '你有多久', highlight: '没和 TA 聊天了' },
|
||
{ main: '那个秒回你的人', highlight: '还在吗' },
|
||
{ main: '你置顶的人', highlight: '这一年变过吗' },
|
||
{ main: '最后一条消息', highlight: '是你发的还是 TA 发的' },
|
||
{ main: '你的「在吗」', highlight: '都发给了谁' },
|
||
{ main: '有没有一个人', highlight: '你想聊却没聊' },
|
||
{ main: '对话框里的', highlight: '四季' },
|
||
{ main: '字里行间', highlight: '这一年' },
|
||
{ main: '消息如潮水', highlight: '来了又退' },
|
||
{ main: '屏幕那头', highlight: '有人亮着灯' },
|
||
{ main: '打字的手指', highlight: '记得这一年' },
|
||
{ main: '时间会走', highlight: '对话会留下来' },
|
||
{ main: '每一条消息', highlight: '都是一次想起' },
|
||
{ main: '文字落下的地方', highlight: '有人在等' },
|
||
{ main: '那些发出去的字', highlight: '都有回响吗' },
|
||
{ main: '对话框亮起的', highlight: '瞬间' },
|
||
{ main: '这一年的', highlight: '「在吗」和「晚安」' },
|
||
{ main: '聊着聊着', highlight: '一年就过去了' },
|
||
{ main: '发出去的消息', highlight: '收到的回复' },
|
||
{ main: '那些秒回你的人', highlight: '和你秒回的人' },
|
||
{ main: '置顶的人', highlight: '还是那几个吗' },
|
||
{ main: '深夜的消息', highlight: '清晨的问候' },
|
||
{ main: '群聊里的热闹', highlight: '私聊里的安静' },
|
||
{ main: '表情包发了多少', highlight: '真心话说了几句' },
|
||
{ main: '已读不回的', highlight: '和秒回的' },
|
||
{ main: '消息免打扰的', highlight: '和置顶的' },
|
||
{ main: '总有人', highlight: '在消息那头' },
|
||
{ main: '每条消息背后', highlight: '都有一个想你的人' },
|
||
{ main: '感谢这一年', highlight: '愿意听你说话的人' },
|
||
{ main: '有人找你聊天', highlight: '是件幸运的事' },
|
||
{ main: '被回复的感觉', highlight: '叫做被在乎' },
|
||
{ main: '有些陪伴', highlight: '藏在对话框里' },
|
||
{ main: '谢谢那些', highlight: '愿意等你回复的人' },
|
||
{ main: '聊天这件小事', highlight: '其实是大事' },
|
||
{ main: '能说话的人', highlight: '都是重要的人' },
|
||
{ main: '这一年', highlight: '谢谢你们陪我聊天' },
|
||
]
|
||
|
||
// 50 个副标题
|
||
const SUBTITLES = [
|
||
'有些问候写在对话框里,有些陪伴藏在深夜里。',
|
||
'有些陪伴不说出口,但聊天记录都记得。',
|
||
'凌晨三点的消息、周末的闲聊、节日的祝福——都在这里。',
|
||
'一年的对话,浓缩成几张卡片,轻轻回看。',
|
||
'有些人聊着聊着就淡了,有些人聊着聊着就近了。',
|
||
'消息可以删除,但陪伴的时间删不掉。',
|
||
'那些打出来又删掉的字,也算说过了。',
|
||
'每一次「在吗」,都是一次想念。',
|
||
'深夜的对话,往往最真。',
|
||
'感谢每一个愿意听你说话的人。',
|
||
'一年的时间,几张卡片,一些数字,一点回忆。',
|
||
'深夜、清晨、周末、假期——你的聊天节奏,藏着生活的样子。',
|
||
'数字不会说谎,时间不会忘记。',
|
||
'365 天的对话,整理成几个瞬间。',
|
||
'时间知道你和谁聊得最多。',
|
||
'从时间维度,回看你的聊天节奏。',
|
||
'把一年的对话,整理成可以回望的样子。',
|
||
'时间会告诉你,谁一直都在。',
|
||
'这一年的时间,都花在了谁身上。',
|
||
'日历翻过去了,对话还留着。',
|
||
'不读内容,只看时间。让数字告诉你,谁一直都在。',
|
||
'我们只整理时间,不窥探秘密。这是属于你的一年。',
|
||
'不翻聊天记录,只看时间留下的痕迹。',
|
||
'这不是监控,是回望。这不是窥探,是整理。',
|
||
'我们只看时间,不看内容。你的秘密,依然是秘密。',
|
||
'不读取内容,只呈现时间的痕迹。',
|
||
'你的对话内容我们不碰,只帮你数数时间。',
|
||
'隐私是你的,回忆也是你的。',
|
||
'内容属于你,我们只借用时间。',
|
||
'安全地回望,温柔地整理。',
|
||
'从时间里回看你的聊天节奏。',
|
||
'一些数字,一点回忆。',
|
||
'简单整理,安静回看。',
|
||
'不多说,你自己看。',
|
||
'数字背后,是你的生活。',
|
||
'几张卡片,一年时光。',
|
||
'安静地看看这一年。',
|
||
'让数据说话。',
|
||
'你的一年,你的节奏。',
|
||
'回望,不打扰。',
|
||
'早安、晚安、在吗、好的——这些小词,撑起了一整年。',
|
||
'工作日的忙碌,周末的闲聊,都在这里了。',
|
||
'有些群天天响,有些人很少聊,但都是生活的一部分。',
|
||
'秒回的、已读不回的、消息免打扰的——都是你的选择。',
|
||
'置顶的那几个人,大概就是最重要的人吧。',
|
||
'表情包、语音、文字——你更喜欢哪种聊天方式?',
|
||
'深夜还在聊的,大概都是真朋友。',
|
||
'节日的群发祝福,和单独发的那条,不一样。',
|
||
'有些对话很长,有些只有一个表情包,但都算聊过。',
|
||
'聊天记录里,藏着你这一年的喜怒哀乐。',
|
||
]
|
||
|
||
// 随机选择 - 使用 useState 确保 SSR/CSR 一致,避免 hydration mismatch
|
||
const titleIndex = useState('wrapped-title-index', () => Math.floor(Math.random() * TITLES.length))
|
||
const subtitleIndex = useState('wrapped-subtitle-index', () => Math.floor(Math.random() * SUBTITLES.length))
|
||
const randomTitle = computed(() => TITLES[titleIndex.value])
|
||
const randomSubtitle = computed(() => SUBTITLES[subtitleIndex.value])
|
||
|
||
const props = defineProps({
|
||
year: { type: Number, required: true },
|
||
variant: { type: String, default: 'panel' } // 'panel' | 'slide'
|
||
})
|
||
|
||
const yearText = computed(() => `${props.year}年`)
|
||
|
||
const rootClass = computed(() => {
|
||
const base = 'relative overflow-hidden'
|
||
return props.variant === 'slide'
|
||
? `${base} h-full w-full`
|
||
: `${base} rounded-2xl border border-[#EDEDED] bg-white`
|
||
})
|
||
|
||
const innerClass = computed(() => (
|
||
props.variant === 'slide'
|
||
? 'relative h-full max-w-5xl mx-auto px-6 py-10 sm:px-8 sm:py-12'
|
||
: 'relative px-6 py-7 sm:px-8 sm:py-9'
|
||
))
|
||
</script>
|