Files
WeChatDataAnalysis/frontend/components/wrapped/shared/WrappedCardShell.vue
2977094657 980f15d0a4 feat(wrapped-ui): 新增 Win98 主题与桌面化外观
- 主题系统新增 win98(显示名 Windows 98,快捷键扩展到 F1-F4),并区分 retro(pixel/CRT) 与桌面 GUI 主题
- 年度总结页新增 Win98 桌面背景与底部任务栏(背景色/视口高度适配)
- 封面与卡片 slide 形态支持 Win98 窗口外观(title bar/icon/controls)
- 主题切换器补充 Win98 选项并新增 Win98 专属切换器
- 新增 Win98 图标资源(Start + 桌面图标)
2026-02-02 00:07:09 +08:00

163 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="variant === 'panel'" class="window bg-white rounded-2xl border border-[#EDEDED] overflow-hidden">
<div class="px-6 py-5 border-b border-[#F3F3F3]">
<div class="flex items-start justify-between gap-4">
<div>
<h2 class="wrapped-title text-xl text-[#000000e6]">{{ title }}</h2>
<slot name="narrative">
<p v-if="narrative" class="mt-2 wrapped-body text-sm text-[#7F7F7F] whitespace-pre-wrap">
{{ narrative }}
</p>
</slot>
</div>
<slot name="badge" />
</div>
</div>
<div class="px-6 py-6">
<slot />
</div>
</div>
<!-- Slide 模式单张卡片占据全页面背景由外层年度总结统一控制 -->
<section v-else class="relative h-full w-full overflow-hidden">
<div :class="slideContainerClass">
<!-- Win98把整页内容包进一个窗口 -->
<div v-if="isWin98" class="window w-full flex-1 flex flex-col overflow-hidden">
<div class="title-bar">
<div class="title-bar-text">
<img class="title-bar-icon" src="/assets/images/windows-0.png" alt="" aria-hidden="true" />
<span>{{ title }}</span>
</div>
<div class="title-bar-controls" aria-hidden="true">
<button type="button" aria-label="Minimize" tabindex="-1"></button>
<button type="button" aria-label="Maximize" tabindex="-1"></button>
<button type="button" aria-label="Close" tabindex="-1"></button>
</div>
</div>
<div class="window-body flex-1 flex flex-col min-h-0">
<slot name="narrative">
<p v-if="narrative" class="wrapped-body text-sm sm:text-base whitespace-pre-wrap">
{{ narrative }}
</p>
</slot>
<div class="mt-4 flex-1 min-h-0 overflow-auto">
<div class="w-full">
<slot />
</div>
</div>
</div>
</div>
<!-- 其他主题保持原样 -->
<template v-else>
<div class="flex items-start justify-between gap-4">
<div>
<h2 class="wrapped-title text-2xl sm:text-3xl text-[#000000e6]">{{ title }}</h2>
<slot name="narrative">
<p v-if="narrative" class="mt-3 wrapped-body text-sm sm:text-base text-[#7F7F7F] max-w-2xl whitespace-pre-wrap">
{{ narrative }}
</p>
</slot>
</div>
<slot name="badge" />
</div>
<div class="mt-6 sm:mt-8 flex-1 flex items-center">
<div class="w-full">
<slot />
</div>
</div>
</template>
</div>
</section>
</template>
<script setup>
defineProps({
cardId: { type: Number, required: true },
title: { type: String, required: true },
narrative: { type: String, default: '' },
variant: { type: String, default: 'panel' } // 'panel' | 'slide'
})
const { theme } = useWrappedTheme()
const isWin98 = computed(() => theme.value === 'win98')
const slideContainerClass = computed(() => (
isWin98.value
? 'relative h-full max-w-5xl mx-auto px-6 pt-2 pb-4 sm:px-8 sm:pt-3 sm:pb-6 flex flex-col'
: 'relative h-full max-w-5xl mx-auto px-6 py-10 sm:px-8 sm:py-12 flex flex-col'
))
</script>
<style>
/* ========== Game Boy 主题 ========== */
/* 卡片背景 */
.wrapped-theme-gameboy .bg-white {
background: #9bbc0f !important;
border-color: #306230 !important;
}
/* 标题 */
.wrapped-theme-gameboy .wrapped-title {
color: #0f380f !important;
font-family: var(--font-pixel-10), 'Courier New', monospace;
}
/* 描述文字 */
.wrapped-theme-gameboy .wrapped-body {
color: #306230 !important;
}
/* 数字高亮 */
.wrapped-theme-gameboy .wrapped-number {
color: #0f380f !important;
font-family: var(--font-pixel-10), 'Courier New', monospace;
}
/* 边框 */
.wrapped-theme-gameboy .border-\[\#EDEDED\],
.wrapped-theme-gameboy .border-\[\#F3F3F3\] {
border-color: #306230 !important;
}
/* ========== DOS 主题 ========== */
/* 卡片背景 */
.wrapped-theme-dos .bg-white {
background: #0a0a0a !important;
border-color: #33ff33 !important;
box-shadow: 0 0 10px rgba(51, 255, 51, 0.3);
}
/* 标题 */
.wrapped-theme-dos .wrapped-title {
color: #33ff33 !important;
text-shadow: 0 0 5px #33ff33;
font-family: 'Courier New', monospace;
}
/* 描述文字 */
.wrapped-theme-dos .wrapped-body {
color: #22aa22 !important;
text-shadow: 0 0 3px #22aa22;
font-family: 'Courier New', monospace;
}
/* 数字高亮 */
.wrapped-theme-dos .wrapped-number {
color: #33ff33 !important;
text-shadow: 0 0 8px #33ff33;
font-family: 'Courier New', monospace;
}
/* 边框 */
.wrapped-theme-dos .border-\[\#EDEDED\],
.wrapped-theme-dos .border-\[\#F3F3F3\] {
border-color: #33ff33 !important;
}
</style>