mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-19 14:20:51 +08:00
improvement(wrapped-ui): 封面接入卡片预览并按主题展示
- WrappedHero 新增卡片预览区,按 GameBoy/Modern 主题切换不同预览动效 - 基于 cardManifests 动态映射标题与提问文案,并补充 fallback 逻辑 - wrapped 首页透传 report cards,确保封面预览与报告内容一致
This commit is contained in:
@@ -90,6 +90,84 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="previewQuestions.length > 0 && (isGameboy || isModern)"
|
||||||
|
class="pointer-events-none absolute bottom-0 right-0 hidden xl:flex items-end"
|
||||||
|
>
|
||||||
|
<div class="pointer-events-auto relative" :class="previewStageClass">
|
||||||
|
<div class="relative" :class="previewViewportClass">
|
||||||
|
<template v-if="isGameboy">
|
||||||
|
<BitsCardSwap
|
||||||
|
:width="previewCardWidth"
|
||||||
|
:height="previewCardHeight"
|
||||||
|
:delay="previewSwapDelay"
|
||||||
|
:card-count="previewQuestions.length"
|
||||||
|
:card-distance="previewCardDistance"
|
||||||
|
:vertical-distance="previewVerticalDistance"
|
||||||
|
:skew-amount="4"
|
||||||
|
easing="elastic"
|
||||||
|
:pause-on-hover="true"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="(previewItem, previewIndex) in previewQuestions"
|
||||||
|
:key="`preview-${previewItem.order}-${previewIndex}`"
|
||||||
|
v-slot:[`card-${previewIndex}`]
|
||||||
|
>
|
||||||
|
<WrappedCardShell
|
||||||
|
:card-id="previewItem.order"
|
||||||
|
:title="previewItem.title"
|
||||||
|
variant="panel"
|
||||||
|
class="h-full w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex h-[168px] items-center justify-center rounded-xl border border-dashed px-5"
|
||||||
|
:class="previewQuestionPanelClass"
|
||||||
|
>
|
||||||
|
<p class="wrapped-body text-lg leading-relaxed text-center" :class="previewQuestionClass">
|
||||||
|
{{ previewItem.question }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</WrappedCardShell>
|
||||||
|
</template>
|
||||||
|
</BitsCardSwap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<BitsGridMotion
|
||||||
|
:items="modernPreviewItems"
|
||||||
|
gradient-color="rgba(7, 193, 96, 0.24)"
|
||||||
|
:row-count="7"
|
||||||
|
:column-count="8"
|
||||||
|
:scroll-speed="42"
|
||||||
|
:base-offset-x="46"
|
||||||
|
>
|
||||||
|
<template #item="{ item }">
|
||||||
|
<WrappedCardShell
|
||||||
|
:card-id="Number(item?.order || 0)"
|
||||||
|
:title="String(item?.title || '年度卡片')"
|
||||||
|
variant="panel"
|
||||||
|
class="h-full w-full preview-grid-shell"
|
||||||
|
>
|
||||||
|
<div class="preview-grid-body">
|
||||||
|
<div class="preview-grid-summary">
|
||||||
|
{{ String(item?.summary || '年度线索') }}
|
||||||
|
</div>
|
||||||
|
<p class="preview-grid-question">
|
||||||
|
{{ String(item?.question || '这一页会揭晓你的聊天答案。') }}
|
||||||
|
</p>
|
||||||
|
<div class="preview-grid-lines" aria-hidden="true">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WrappedCardShell>
|
||||||
|
</template>
|
||||||
|
</BitsGridMotion>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -235,13 +313,126 @@ const subtitleIndex = useState('wrapped-subtitle-index', () => Math.floor(Math.r
|
|||||||
const randomTitle = computed(() => TITLES[titleIndex.value])
|
const randomTitle = computed(() => TITLES[titleIndex.value])
|
||||||
const randomSubtitle = computed(() => SUBTITLES[subtitleIndex.value])
|
const randomSubtitle = computed(() => SUBTITLES[subtitleIndex.value])
|
||||||
|
|
||||||
|
const PREVIEW_BY_KIND = {
|
||||||
|
'global/overview': {
|
||||||
|
summary: '年度全景',
|
||||||
|
question: '这一年你最常把消息发给谁?'
|
||||||
|
},
|
||||||
|
'time/weekday_hour_heatmap': {
|
||||||
|
summary: '聊天作息',
|
||||||
|
question: '你是早八型还是夜猫子型聊天选手?'
|
||||||
|
},
|
||||||
|
'text/message_chars': {
|
||||||
|
summary: '表达强度',
|
||||||
|
question: '你这一年打出的字,能拼成几段故事?'
|
||||||
|
},
|
||||||
|
'chat/reply_speed': {
|
||||||
|
summary: '回复速度',
|
||||||
|
question: '谁是你愿意秒回的那个人?'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PREVIEW_FALLBACK_SUMMARY = '年度线索'
|
||||||
|
const PREVIEW_FALLBACK_QUESTION = '这一页会揭晓你的哪段聊天答案?'
|
||||||
|
const PREVIEW_BOOTSTRAP_ITEMS = [
|
||||||
|
{ summary: '年度全景', question: '这一年你最常把消息发给谁?' },
|
||||||
|
{ summary: '聊天作息', question: '你是「早八人」还是「夜猫子」?' },
|
||||||
|
{ summary: '表达强度', question: '你这一年打了多少字?' },
|
||||||
|
{ summary: '回复速度', question: '谁是你愿意秒回的那个人?' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const resolvePreviewMeta = (kind, idx) => {
|
||||||
|
const key = String(kind || '').trim()
|
||||||
|
if (PREVIEW_BY_KIND[key]) return PREVIEW_BY_KIND[key]
|
||||||
|
return {
|
||||||
|
summary: PREVIEW_FALLBACK_SUMMARY,
|
||||||
|
question: idx % 2 === 0
|
||||||
|
? '这一页会揭晓你聊天里的哪种习惯?'
|
||||||
|
: '你猜这页的答案会指向谁和哪段时光?'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
year: { type: Number, required: true },
|
year: { type: Number, required: true },
|
||||||
variant: { type: String, default: 'panel' } // 'panel' | 'slide'
|
variant: { type: String, default: 'panel' }, // 'panel' | 'slide'
|
||||||
|
cardManifests: { type: Array, default: () => [] }
|
||||||
})
|
})
|
||||||
|
|
||||||
const { theme } = useWrappedTheme()
|
const { theme } = useWrappedTheme()
|
||||||
const isWin98 = computed(() => theme.value === 'win98')
|
const isWin98 = computed(() => theme.value === 'win98')
|
||||||
|
const isGameboy = computed(() => theme.value === 'gameboy')
|
||||||
|
const isModern = computed(() => theme.value === 'off')
|
||||||
|
|
||||||
|
const previewQuestions = computed(() => {
|
||||||
|
const manifests = Array.isArray(props.cardManifests) ? props.cardManifests : []
|
||||||
|
if (!manifests.length) {
|
||||||
|
return Array.from({ length: 8 }, (_, idx) => {
|
||||||
|
const fallback = PREVIEW_BOOTSTRAP_ITEMS[idx % PREVIEW_BOOTSTRAP_ITEMS.length]
|
||||||
|
return {
|
||||||
|
order: idx + 1,
|
||||||
|
title: `第 ${idx + 1} 张卡片`,
|
||||||
|
summary: fallback.summary,
|
||||||
|
question: fallback.question
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifests.map((item, idx) => {
|
||||||
|
const meta = resolvePreviewMeta(item?.kind, idx)
|
||||||
|
return {
|
||||||
|
order: idx + 1,
|
||||||
|
title: String(item?.title || `第 ${idx + 1} 张卡片`),
|
||||||
|
summary: meta.summary,
|
||||||
|
question: meta.question
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const previewSwapDelay = 4200
|
||||||
|
const previewCardWidth = 420
|
||||||
|
const previewCardHeight = 280
|
||||||
|
|
||||||
|
const modernPreviewItems = computed(() => {
|
||||||
|
if (!previewQuestions.value.length) return []
|
||||||
|
return previewQuestions.value.map((item) => ({
|
||||||
|
order: item.order,
|
||||||
|
title: item.title,
|
||||||
|
summary: item.summary,
|
||||||
|
question: item.question
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const previewStageClass = computed(() => (
|
||||||
|
isGameboy.value
|
||||||
|
? 'w-[500px] h-[360px] translate-x-12 -translate-y-8'
|
||||||
|
: 'w-[620px] h-[420px] translate-x-20 -translate-y-10'
|
||||||
|
))
|
||||||
|
|
||||||
|
const previewViewportClass = computed(() => (
|
||||||
|
isGameboy.value
|
||||||
|
? 'h-[340px] w-[460px]'
|
||||||
|
: 'h-[390px] w-[580px]'
|
||||||
|
))
|
||||||
|
|
||||||
|
const previewCardDistance = computed(() => {
|
||||||
|
const total = previewQuestions.value.length
|
||||||
|
return total >= 9 ? 9 : total >= 7 ? 11 : total >= 5 ? 13 : 18
|
||||||
|
})
|
||||||
|
|
||||||
|
const previewVerticalDistance = computed(() => {
|
||||||
|
const total = previewQuestions.value.length
|
||||||
|
return total >= 9 ? 10 : total >= 7 ? 11 : total >= 5 ? 14 : 18
|
||||||
|
})
|
||||||
|
|
||||||
|
const previewQuestionClass = computed(() => {
|
||||||
|
if (isWin98.value) return 'text-[#111111]'
|
||||||
|
return 'text-[#1F2937]'
|
||||||
|
})
|
||||||
|
|
||||||
|
const previewQuestionPanelClass = computed(() => {
|
||||||
|
if (isWin98.value) return 'border-[#B7B7B7] bg-[#FFFFFF]'
|
||||||
|
return 'border-[#07C160]/30 bg-[#F7FFFB]'
|
||||||
|
})
|
||||||
|
|
||||||
const yearText = computed(() => `${props.year}年`)
|
const yearText = computed(() => `${props.year}年`)
|
||||||
|
|
||||||
@@ -267,4 +458,64 @@ const innerClass = computed(() => {
|
|||||||
background: #000080;
|
background: #000080;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-grid-shell {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 24px rgba(7, 193, 96, 0.14);
|
||||||
|
background: #f3fff8 !important;
|
||||||
|
border-color: rgba(7, 193, 96, 0.24) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-shell :deep(.wrapped-title) {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-body {
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(7, 193, 96, 0.2);
|
||||||
|
background: rgba(243, 255, 248, 0.88);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-summary {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #07c160;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-question {
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.35;
|
||||||
|
font-weight: 600;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-lines {
|
||||||
|
margin-top: 6px;
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-lines span {
|
||||||
|
display: block;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, rgba(7, 193, 96, 0.18), rgba(7, 193, 96, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-grid-lines span:last-child {
|
||||||
|
width: 68%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -87,7 +87,12 @@
|
|||||||
<!-- Cover -->
|
<!-- Cover -->
|
||||||
<section class="w-full" :style="slideStyle">
|
<section class="w-full" :style="slideStyle">
|
||||||
<div class="h-full w-full relative">
|
<div class="h-full w-full relative">
|
||||||
<WrappedHero :year="year" variant="slide" class="h-full w-full" />
|
<WrappedHero
|
||||||
|
:year="year"
|
||||||
|
:card-manifests="report?.cards || []"
|
||||||
|
variant="slide"
|
||||||
|
class="h-full w-full"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user