improvement(wrapped): 年度总结仅保留 Modern 主题

- 移除复古主题切换入口(控制面板/左上角按钮)与 Win98/CRT 相关 UI

- 简化 useWrappedTheme:仅保留 off(Modern),历史主题值自动回退

- Modern 下也展示 LuckyBlock 占位图,并同步更新 README 说明
This commit is contained in:
2977094657
2026-02-18 19:11:47 +08:00
parent b281d016be
commit c68e4fffeb
5 changed files with 22 additions and 227 deletions

View File

@@ -115,7 +115,7 @@
@error="onShownAvatarError"
/>
<img
v-else-if="isGameboy && phase === 'idle'"
v-else-if="(isGameboy || isModern) && phase === 'idle'"
src="/assets/images/LuckyBlock.png"
class="w-full h-full object-contain"
alt="Lucky Block"
@@ -258,6 +258,7 @@ const props = defineProps({
const { theme } = useWrappedTheme()
const isGameboy = computed(() => theme.value === 'gameboy')
const isModern = computed(() => theme.value === 'off')
const isRetro = computed(() => isGameboy.value)
const nfInt = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 0 })

View File

@@ -39,7 +39,6 @@
</div>
<div class="flex gap-2 items-end">
<WrappedThemeSwitcher />
<button
class="inline-flex items-center justify-center px-4 py-2 rounded-lg bg-[#07C160] text-white text-sm wrapped-label hover:bg-[#06AD56] disabled:opacity-60 disabled:cursor-not-allowed transition controls-btn"
:disabled="loading"
@@ -83,73 +82,3 @@ const yearOptions = computed(() => {
return years
})
</script>
<style scoped>
/* 复古模式 - 控制面板样式 */
.wrapped-retro .controls-panel {
background-color: var(--wrapped-card-bg);
border-color: var(--wrapped-border);
}
.wrapped-retro .controls-label {
color: var(--wrapped-text-secondary);
}
.wrapped-retro .controls-select {
background-color: var(--wrapped-bg);
border-color: var(--wrapped-border);
color: var(--wrapped-text);
}
.wrapped-retro .controls-select:focus {
--tw-ring-color: var(--wrapped-accent);
}
.wrapped-retro .controls-checkbox {
border-color: var(--wrapped-border);
color: var(--wrapped-accent);
}
.wrapped-retro .controls-checkbox:focus {
--tw-ring-color: var(--wrapped-accent);
}
.wrapped-retro .controls-hint {
color: var(--wrapped-text-secondary);
}
.wrapped-retro .controls-warning {
color: var(--wrapped-warning);
}
.wrapped-retro .controls-btn {
background-color: var(--wrapped-accent);
color: var(--wrapped-bg);
}
.wrapped-retro .controls-btn:hover:not(:disabled) {
filter: brightness(1.1);
}
/* Win98 特殊样式 */
.wrapped-theme-win98 .controls-panel {
border-radius: 0;
border: 1px solid #808080;
background: #c0c0c0;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
.wrapped-theme-win98 .controls-select {
border-radius: 0;
}
.wrapped-theme-win98 .controls-btn {
border-radius: 0;
}
.wrapped-theme-win98 .controls-warning {
color: #800000;
}
</style>

View File

@@ -1,109 +1,31 @@
/**
* 年度总结页面主题管理 composable
* 支持三种主题:modern现代、gameboyGame Boy、win98Windows 98
* 仅保留 modern现代主题
*/
const STORAGE_KEY = 'wrapped-theme'
const VALID_THEMES = ['off', 'gameboy', 'win98']
const RETRO_THEMES = new Set(['gameboy'])
// 全局响应式状态(跨组件共享)
const theme = ref('off')
let initialized = false
let keyboardInitialized = false
// Note: 历史上曾尝试过 gameboy / win98 等主题,但目前已移除,仅保留 Modern。
const theme = ref('off') // off === Modern
export function useWrappedTheme() {
// 初始化:从 localStorage 读取(仅执行一次)
const initTheme = () => {
if (initialized || !import.meta.client) return
const saved = localStorage.getItem(STORAGE_KEY)
if (saved && VALID_THEMES.includes(saved)) {
theme.value = saved
}
initialized = true
}
// 设置主题
const setTheme = (newTheme) => {
if (!VALID_THEMES.includes(newTheme)) {
console.warn(`Invalid theme: ${newTheme}`)
return
}
theme.value = newTheme
if (import.meta.client) {
localStorage.setItem(STORAGE_KEY, newTheme)
// Only keep Modern.
if (newTheme !== 'off') {
console.warn(`Wrapped theme '${newTheme}' has been removed; falling back to Modern.`)
}
theme.value = 'off'
}
// 切换到下一个主题(循环)
const cycleTheme = () => {
const currentIndex = VALID_THEMES.indexOf(theme.value)
const nextIndex = (currentIndex + 1) % VALID_THEMES.length
setTheme(VALID_THEMES[nextIndex])
}
const cycleTheme = () => setTheme('off')
// 计算属性:是否为复古模式(非 off
const isRetro = computed(() => theme.value !== 'off')
// 计算属性:当前主题的 CSS 类名
const themeClass = computed(() => {
if (theme.value === 'off') return ''
// Note: not every non-modern theme is "retro pixel/CRT".
// Keep wrapped-retro for themes that rely on pixel/CRT shared styles.
const base = RETRO_THEMES.has(theme.value) ? 'wrapped-retro ' : ''
return `${base}wrapped-theme-${theme.value}`
})
// 计算属性:主题显示名称
const themeName = computed(() => {
const names = {
off: 'Modern',
gameboy: 'Game Boy',
win98: 'Windows 98'
}
return names[theme.value] || 'Modern'
})
// 全局 F1-F3 快捷键切换主题(仅初始化一次)
const initKeyboardShortcuts = () => {
if (keyboardInitialized || !import.meta.client) return
keyboardInitialized = true
const handleKeydown = (e) => {
// 检查是否在可编辑元素中
const el = e.target
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || el.isContentEditable)) {
return
}
if (e.key === 'F1') {
e.preventDefault()
setTheme('off')
} else if (e.key === 'F2') {
e.preventDefault()
setTheme('gameboy')
} else if (e.key === 'F3') {
e.preventDefault()
setTheme('win98')
}
}
window.addEventListener('keydown', handleKeydown)
}
// 客户端挂载后再初始化:避免 SSR 与首帧 hydration 不一致
onMounted(() => {
initTheme()
initKeyboardShortcuts()
})
const isRetro = computed(() => false)
const themeClass = computed(() => '')
return {
theme: readonly(theme),
setTheme,
cycleTheme,
isRetro,
themeClass,
themeName,
VALID_THEMES
themeClass
}
}

View File

@@ -7,10 +7,8 @@
>
<!-- PPT 风格单张卡片占据全页面鼠标滚轮切换 -->
<WrappedDeckBackground />
<!-- CRT 叠加层仅用于像素屏类主题Win98 等桌面 GUI 主题不应开启 -->
<WrappedCRTOverlay v-if="theme === 'gameboy'" />
<!-- 左上角刷新 + 复古模式开关 -->
<!-- 左上角返回 + 刷新 -->
<div class="absolute top-6 left-6 z-20 select-none">
<div class="flex items-center gap-3">
<button
@@ -59,24 +57,6 @@
</svg>
</button>
<button
type="button"
class="pointer-events-auto inline-flex items-center justify-center w-9 h-9 rounded-full bg-transparent transition disabled:opacity-60 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-[#07C160]/30"
:class="isRetro ? 'text-[#07C160] hover:bg-[#07C160]/10' : 'text-[#00000055] hover:bg-[#000000]/5'"
:aria-pressed="isRetro ? 'true' : 'false'"
:aria-label="`复古模式当前${theme === 'off' ? 'Modern' : theme.toUpperCase()}`"
:title="`复古模式:${theme === 'off' ? 'Modern' : theme.toUpperCase()}(点击切换)`"
@click="cycleTheme"
>
<img
src="/assets/images/wechat-audio-dark.png"
class="w-4 h-4 transition"
:style="{ filter: isRetro ? 'none' : 'grayscale(1)', opacity: isRetro ? '1' : '0.55' }"
alt=""
aria-hidden="true"
draggable="false"
/>
</button>
</div>
<div v-if="error" class="mt-2 pointer-events-auto bg-white/90 backdrop-blur rounded-xl border border-red-200 px-3 py-2">
@@ -205,11 +185,6 @@
</section>
</div>
<!-- Win98底部任务栏 -->
<WrappedWin98Taskbar
v-if="theme === 'win98'"
:title="taskbarTitle"
/>
</div>
</template>
@@ -229,8 +204,8 @@ const year = ref(Number(route.query?.year) || new Date().getFullYear())
// 分享视图不展示账号信息:默认让后端自动选择;需要指定时可用 query ?account=wxid_xxx
const account = ref(typeof route.query?.account === 'string' ? route.query.account : '')
// 主题管理modern / gameboy / win98
const { theme, cycleTheme, isRetro, themeClass } = useWrappedTheme()
// 主题:仅保留 Modern
const { isRetro, themeClass } = useWrappedTheme()
const accounts = ref([])
const accountsLoading = ref(true)
@@ -262,13 +237,6 @@ const wheelAcc = ref(0)
let navUnlockTimer = null
let deckResizeObserver = null
// 各主题的背景颜色
const THEME_BG = {
off: '#F3FFF8', // Modern: 浅绿
gameboy: '#9bbc0f', // Game Boy: 亮绿
win98: '#008080' // Win98: 经典桌面青色
}
const slides = computed(() => {
const cards = Array.isArray(report.value?.cards) ? report.value.cards : []
const out = [{ key: 'cover' }]
@@ -276,15 +244,7 @@ const slides = computed(() => {
return out
})
const taskbarTitle = computed(() => {
if (theme.value !== 'win98') return ''
if (activeIndex.value === 0) return `${year.value} WeChat Wrapped`
const idx = activeIndex.value - 1
const c = report.value?.cards?.[idx]
return String(c?.title || 'WeChat Wrapped')
})
const currentBg = computed(() => THEME_BG[theme.value] || THEME_BG.off)
const currentBg = computed(() => '#F3FFF8')
const deckTrackClass = computed(() => 'z-10')
const applyViewportBg = () => {
@@ -423,11 +383,8 @@ const onTouchEnd = (e) => {
const updateViewport = () => {
const h = Math.round(deckEl.value?.getBoundingClientRect?.().height || deckEl.value?.clientHeight || window.innerHeight || 0)
if (!h) return
// Reserve space for the Win98 taskbar at the bottom.
const offset = theme.value === 'win98' ? 40 : 0
const effective = Math.max(0, h - offset)
// Avoid endless reflows from 1px rounding errors (especially in Electron).
if (Math.abs(viewportHeight.value - effective) > 1) viewportHeight.value = effective
if (Math.abs(viewportHeight.value - h) > 1) viewportHeight.value = h
}
const loadAccounts = async () => {
@@ -592,12 +549,6 @@ onMounted(async () => {
}
})
// Theme switch may change reserved UI space (e.g., Win98 taskbar)
watch(theme, () => {
applyViewportBg()
updateViewport()
})
onBeforeUnmount(() => {
if (import.meta.client) {
document.documentElement.style.backgroundColor = ''