From e9c81caa120297bb35d8c03656ef07b30b483d88 Mon Sep 17 00:00:00 2001
From: 2977094657 <2977094657@qq.com>
Date: Sat, 7 Feb 2026 20:59:03 +0800
Subject: [PATCH] =?UTF-8?q?improvement(wrapped-ui):=20=E4=B8=8B=E7=BA=BF?=
=?UTF-8?q?=20DOS=20=E4=B8=BB=E9=A2=98=E5=B9=B6=E4=BC=98=E5=8C=96=20Wrappe?=
=?UTF-8?q?d=20=E5=A4=9A=E4=B8=BB=E9=A2=98=E4=BD=93=E9=AA=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 移除 DOS 主题入口、切换器组件与相关样式逻辑,统一主题为 Modern / GameBoy / Win98。
- 新增 WrappedGameboyDither 组件,并在背景与 CRT 叠加层中引入 GameBoy 噪点效果。
- 优化 wrapped 页面视口高度与背景同步逻辑(含 ResizeObserver 与 100dvh 适配),提升桌面容器显示稳定性。
- 调整封面标题与预览位移、回复速度卡片滚动行为等细节,提升主题下视觉与交互一致性。
---
.../wrapped/cards/Card03ReplySpeed.vue | 15 ++-
.../wrapped/shared/WrappedCRTOverlay.vue | 33 ++----
.../wrapped/shared/WrappedCardShell.vue | 38 +------
.../wrapped/shared/WrappedControls.vue | 24 -----
.../wrapped/shared/WrappedDeckBackground.vue | 15 ++-
.../wrapped/shared/WrappedGameboyDither.vue | 98 +++++++++++++++++
.../components/wrapped/shared/WrappedHero.vue | 8 +-
.../wrapped/shared/WrappedThemeSwitcher.vue | 1 -
.../shared/WrappedThemeSwitcherDos.vue | 100 ------------------
.../shared/WrappedThemeSwitcherGameboy.vue | 1 -
.../shared/WrappedThemeSwitcherModern.vue | 1 -
.../shared/WrappedThemeSwitcherWin98.vue | 1 -
.../wrapped/shared/WrappedYearSelector.vue | 70 ------------
frontend/composables/useWrappedTheme.js | 34 +++---
frontend/pages/wrapped/index.vue | 49 +++++++--
15 files changed, 192 insertions(+), 296 deletions(-)
create mode 100644 frontend/components/wrapped/shared/WrappedGameboyDither.vue
delete mode 100644 frontend/components/wrapped/shared/WrappedThemeSwitcherDos.vue
diff --git a/frontend/components/wrapped/cards/Card03ReplySpeed.vue b/frontend/components/wrapped/cards/Card03ReplySpeed.vue
index b71252d..66ae36c 100644
--- a/frontend/components/wrapped/cards/Card03ReplySpeed.vue
+++ b/frontend/components/wrapped/cards/Card03ReplySpeed.vue
@@ -186,7 +186,7 @@
暂无可展示的排行榜数据。
-
+
-
+
{{ randomTitle.main }}
{{ randomTitle.highlight }}
@@ -70,7 +70,7 @@
-
+
{{ randomTitle.main }}
{{ randomTitle.highlight }}
@@ -404,8 +404,8 @@ const modernPreviewItems = computed(() => {
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'
+ ? 'w-[500px] h-[360px] translate-x-24 -translate-y-8'
+ : 'w-[620px] h-[420px] translate-x-32 -translate-y-10'
))
const previewViewportClass = computed(() => (
diff --git a/frontend/components/wrapped/shared/WrappedThemeSwitcher.vue b/frontend/components/wrapped/shared/WrappedThemeSwitcher.vue
index 89adb15..2ccec08 100644
--- a/frontend/components/wrapped/shared/WrappedThemeSwitcher.vue
+++ b/frontend/components/wrapped/shared/WrappedThemeSwitcher.vue
@@ -10,7 +10,6 @@ const themeSwitcherComponent = computed(() => {
const map = {
off: resolveComponent('WrappedThemeSwitcherModern'),
gameboy: resolveComponent('WrappedThemeSwitcherGameboy'),
- dos: resolveComponent('WrappedThemeSwitcherDos'),
win98: resolveComponent('WrappedThemeSwitcherWin98')
}
return map[theme.value] || map.off
diff --git a/frontend/components/wrapped/shared/WrappedThemeSwitcherDos.vue b/frontend/components/wrapped/shared/WrappedThemeSwitcherDos.vue
deleted file mode 100644
index 754debd..0000000
--- a/frontend/components/wrapped/shared/WrappedThemeSwitcherDos.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-
-
-
-
diff --git a/frontend/components/wrapped/shared/WrappedThemeSwitcherGameboy.vue b/frontend/components/wrapped/shared/WrappedThemeSwitcherGameboy.vue
index de24aa6..50d22a9 100644
--- a/frontend/components/wrapped/shared/WrappedThemeSwitcherGameboy.vue
+++ b/frontend/components/wrapped/shared/WrappedThemeSwitcherGameboy.vue
@@ -25,7 +25,6 @@ const { theme, setTheme } = useWrappedTheme()
const themes = [
{ value: 'off', label: 'MODERN' },
{ value: 'gameboy', label: 'GAME BOY' },
- { value: 'dos', label: 'DOS' },
{ value: 'win98', label: 'WIN98' }
]
diff --git a/frontend/components/wrapped/shared/WrappedThemeSwitcherModern.vue b/frontend/components/wrapped/shared/WrappedThemeSwitcherModern.vue
index cd2ebcc..cd2e6e3 100644
--- a/frontend/components/wrapped/shared/WrappedThemeSwitcherModern.vue
+++ b/frontend/components/wrapped/shared/WrappedThemeSwitcherModern.vue
@@ -25,7 +25,6 @@ const { theme, setTheme } = useWrappedTheme()
const themes = [
{ value: 'off', label: 'Modern' },
{ value: 'gameboy', label: 'Game Boy' },
- { value: 'dos', label: 'DOS' },
{ value: 'win98', label: 'Win98' }
]
diff --git a/frontend/components/wrapped/shared/WrappedThemeSwitcherWin98.vue b/frontend/components/wrapped/shared/WrappedThemeSwitcherWin98.vue
index b3e2e88..3ebc484 100644
--- a/frontend/components/wrapped/shared/WrappedThemeSwitcherWin98.vue
+++ b/frontend/components/wrapped/shared/WrappedThemeSwitcherWin98.vue
@@ -23,7 +23,6 @@ const { theme, setTheme } = useWrappedTheme()
const themes = [
{ value: 'off', label: 'Modern' },
{ value: 'gameboy', label: 'Game Boy' },
- { value: 'dos', label: 'DOS' },
{ value: 'win98', label: 'Win98' }
]
diff --git a/frontend/components/wrapped/shared/WrappedYearSelector.vue b/frontend/components/wrapped/shared/WrappedYearSelector.vue
index d5425bd..a217e62 100644
--- a/frontend/components/wrapped/shared/WrappedYearSelector.vue
+++ b/frontend/components/wrapped/shared/WrappedYearSelector.vue
@@ -19,25 +19,6 @@
-
-
- C:\WRAPPED>
- YEAR:
-
- {{ modelValue }}
-
-
-
@@ -209,57 +190,6 @@ onBeforeUnmount(() => {
letter-spacing: 2px;
}
-/* ========== DOS 风格 ========== */
-.year-dos {
- font-family: 'Courier New', 'Consolas', monospace;
- font-size: 11px;
- display: flex;
- align-items: center;
- gap: 4px;
- color: #33ff33;
- text-shadow: 0 0 5px #33ff33;
-}
-
-.dos-prompt {
- color: #1a5c1a;
-}
-
-.dos-label {
- color: #33ff33;
-}
-
-.dos-arrow {
- background: transparent;
- border: none;
- color: #33ff33;
- font-family: inherit;
- font-size: inherit;
- cursor: pointer;
- padding: 0 2px;
- text-shadow: 0 0 5px #33ff33;
- transition: color 0.1s;
-}
-
-.dos-arrow:hover:not(:disabled) {
- color: #66ff66;
- text-shadow: 0 0 8px #66ff66;
-}
-
-.dos-arrow:disabled {
- color: #1a5c1a;
- cursor: not-allowed;
- text-shadow: none;
-}
-
-.dos-value {
- background: #0a1a0a;
- padding: 2px 6px;
- border: 1px solid #1a5c1a;
- letter-spacing: 1px;
- min-width: 50px;
- text-align: center;
-}
-
/* ========== Win98 风格 ========== */
.year-win98 {
font-family: "MS Sans Serif", Tahoma, "Microsoft Sans Serif", "Segoe UI", sans-serif;
diff --git a/frontend/composables/useWrappedTheme.js b/frontend/composables/useWrappedTheme.js
index e56be6b..cdd2f24 100644
--- a/frontend/composables/useWrappedTheme.js
+++ b/frontend/composables/useWrappedTheme.js
@@ -1,11 +1,11 @@
/**
* 年度总结页面主题管理 composable
- * 支持三种主题:modern(现代)、gameboy(Game Boy)、dos(DOS终端)
+ * 支持三种主题:modern(现代)、gameboy(Game Boy)、win98(Windows 98)
*/
const STORAGE_KEY = 'wrapped-theme'
-const VALID_THEMES = ['off', 'gameboy', 'dos', 'win98']
-const RETRO_THEMES = new Set(['gameboy', 'dos'])
+const VALID_THEMES = ['off', 'gameboy', 'win98']
+const RETRO_THEMES = new Set(['gameboy'])
// 全局响应式状态(跨组件共享)
const theme = ref('off')
@@ -15,19 +15,12 @@ let keyboardInitialized = false
export function useWrappedTheme() {
// 初始化:从 localStorage 读取(仅执行一次)
const initTheme = () => {
- if (initialized) return
- if (import.meta.client) {
- const saved = localStorage.getItem(STORAGE_KEY)
- if (saved && VALID_THEMES.includes(saved)) {
- theme.value = saved
- }
- initialized = true
+ if (initialized || !import.meta.client) return
+ const saved = localStorage.getItem(STORAGE_KEY)
+ if (saved && VALID_THEMES.includes(saved)) {
+ theme.value = saved
}
- }
-
- // 立即初始化(客户端)
- if (import.meta.client) {
- initTheme()
+ initialized = true
}
// 设置主题
@@ -66,7 +59,6 @@ export function useWrappedTheme() {
const names = {
off: 'Modern',
gameboy: 'Game Boy',
- dos: 'DOS Terminal',
win98: 'Windows 98'
}
return names[theme.value] || 'Modern'
@@ -91,9 +83,6 @@ export function useWrappedTheme() {
e.preventDefault()
setTheme('gameboy')
} else if (e.key === 'F3') {
- e.preventDefault()
- setTheme('dos')
- } else if (e.key === 'F4') {
e.preventDefault()
setTheme('win98')
}
@@ -102,10 +91,11 @@ export function useWrappedTheme() {
window.addEventListener('keydown', handleKeydown)
}
- // 自动初始化键盘快捷键
- if (import.meta.client) {
+ // 客户端挂载后再初始化:避免 SSR 与首帧 hydration 不一致
+ onMounted(() => {
+ initTheme()
initKeyboardShortcuts()
- }
+ })
return {
theme: readonly(theme),
diff --git a/frontend/pages/wrapped/index.vue b/frontend/pages/wrapped/index.vue
index 4ba38b1..17cedad 100644
--- a/frontend/pages/wrapped/index.vue
+++ b/frontend/pages/wrapped/index.vue
@@ -1,14 +1,14 @@
-
-
+
+
@@ -81,7 +81,8 @@
@@ -201,7 +202,7 @@ 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 / dos
+// 主题管理:modern / gameboy / win98
const { theme, cycleTheme, isRetro, themeClass } = useWrappedTheme()
const accounts = ref([])
@@ -232,12 +233,12 @@ const activeIndex = ref(0)
const navLocked = ref(false)
const wheelAcc = ref(0)
let navUnlockTimer = null
+let deckResizeObserver = null
// 各主题的背景颜色
const THEME_BG = {
off: '#F3FFF8', // Modern: 浅绿
gameboy: '#9bbc0f', // Game Boy: 亮绿
- dos: '#0a0a0a', // DOS: 黑色
win98: '#008080' // Win98: 经典桌面青色
}
@@ -257,6 +258,14 @@ const taskbarTitle = computed(() => {
})
const currentBg = computed(() => THEME_BG[theme.value] || THEME_BG.off)
+const deckTrackClass = computed(() => 'z-10')
+
+const applyViewportBg = () => {
+ if (!import.meta.client) return
+ const bg = currentBg.value
+ document.documentElement.style.backgroundColor = bg
+ document.body.style.backgroundColor = bg
+}
const slideStyle = computed(() => (
viewportHeight.value > 0 ? { height: `${viewportHeight.value}px` } : { height: '100%' }
@@ -381,7 +390,7 @@ const onTouchEnd = (e) => {
}
const updateViewport = () => {
- const h = deckEl.value?.clientHeight || window.innerHeight || 0
+ 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
@@ -530,7 +539,14 @@ watch(activeIndex, (i) => {
})
onMounted(async () => {
+ applyViewportBg()
updateViewport()
+ if (import.meta.client && typeof ResizeObserver !== 'undefined' && deckEl.value) {
+ deckResizeObserver = new ResizeObserver(() => {
+ updateViewport()
+ })
+ deckResizeObserver.observe(deckEl.value)
+ }
window.addEventListener('resize', updateViewport)
// passive:false 才能 preventDefault,避免外层容器产生滚动/回弹
deckEl.value?.addEventListener('wheel', onWheel, { passive: false })
@@ -547,10 +563,17 @@ 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 = ''
+ document.body.style.backgroundColor = ''
+ }
+ deckResizeObserver?.disconnect()
+ deckResizeObserver = null
window.removeEventListener('resize', updateViewport)
deckEl.value?.removeEventListener('wheel', onWheel)
window.removeEventListener('keydown', onKeydown)
@@ -578,3 +601,15 @@ watch(year, async (newYear, oldYear) => {
await reload()
})
+
+