diff --git a/REFACTORING_GUIDE.md b/REFACTORING_GUIDE.md
new file mode 100644
index 0000000..76fcdb8
--- /dev/null
+++ b/REFACTORING_GUIDE.md
@@ -0,0 +1,1874 @@
+# macOS-web 纯 Vue 重构手册
+
+> **目标**:去除原生 JS 思路和实现,去掉 `window` 全局调用,将项目改造为纯 Vue 3 项目。
+> **范围**:全量重构,不考虑成本。
+
+---
+
+## 目录
+
+1. [现状诊断](#1-现状诊断)
+2. [目标架构](#2-目标架构)
+3. [前端架构设计](#3-前端架构设计)
+4. [重构总路线](#4-重构总路线)
+5. [阶段一:基础设施层](#5-阶段一基础设施层)
+6. [阶段二:系统 UI 组件化](#6-阶段二系统-ui-组件化)
+7. [阶段三:窗口管理器现代化](#7-阶段三窗口管理器现代化)
+8. [阶段四:应用层优化](#8-阶段四应用层优化)
+9. [阶段五:清理与收尾](#9-阶段五清理与收尾)
+10. [附录:新旧对照表](#10-附录新旧对照表)
+
+---
+
+## 1. 现状诊断
+
+### 1.1 双渲染系统并存(核心问题)
+
+| 层 | 当前渲染方式 | 问题 |
+|---|---|---|
+| **系统 UI 层**(菜单栏/Dock/对话框/菜单/通知/Spotlight/控制中心/锁屏/屏保/启动界面) | `el()` 命令式 DOM 构建 + `$()` 直接 DOM 查询 | 非 Vue,绕过虚拟 DOM |
+| **窗口 chrome 层**(traffic lights/标题栏/resize handles) | `el()` 命令式 DOM 构建 | 非 Vue |
+| **应用层**(Finder/Calculator/Safari 等 33 个应用) | Vue 3 SFC + `h()`/`render()` 手动挂载 | 部分合理 |
+| **容器层**(DesktopArea/MenuBarArea 等) | Vue 模板(仅作为 DOM 挂载锚点) | 合理 |
+
+### 1.2 原生 JS 使用分布
+
+| 模块 | 原生 API | 严重度 |
+|---|---|---|
+| `useSys.ts` (~800 行) | `$`, `$$`, `el`, `addEventListener`, `matchMedia`, `MutationObserver`, `requestAnimationFrame`, `setInterval`, `Image()`, `document.activeElement`, `getBoundingClientRect` | 🔴 最重度 |
+| `useWM.ts` (~400 行) | `el()`, `addEventListener('pointermove/up')`, `document.getElementById`, `document.body.classList`, `setPointerCapture`, `getBoundingClientRect` | 🔴 重度 |
+| `useUI.ts` (~200 行) | `document.createElement/addEventListener/body.append/remove` + `window.innerWidth/innerHeight` | 🔴 全部原生 |
+| `useNotify.ts` (~200 行) | `$`, `$$`, `el`, `setTimeout`, `querySelector` | 🔴 重度 |
+| `useSpotlight.ts` (~150 行) | `$`, `$$`, `el`, `addEventListener`, `navigator.clipboard`, `scrollIntoView` | 🔴 重度 |
+| `useApps.ts` (~120 行) | `document.activeElement`, `document.execCommand`, `navigator.clipboard` | 🟡 中度 |
+| `system/index.ts` (~160 行) | `$`, `$$`, `el`, `document.addEventListener`, 14 个 `window` 全局暴露 | 🔴 重度 |
+| `utils/index.ts` (~100 行) | `document.querySelector/querySelectorAll/createElement` | 🔴 基础依赖 |
+
+### 1.3 关键反模式
+
+```mermaid
+graph TD
+ A["window 全局暴露
14 个全局变量"] --> B["隐式依赖,模块封装破坏"]
+ C["懒引用 lazy ref
let _Sys: any = null"] --> D["循环依赖 workaround
类型不安全"]
+ E["el() 命令式 DOM
微型虚拟 DOM 替代品"] --> F["绕过 Vue 响应式
6+ 模块重度依赖"]
+ G["$('') 直接 DOM 查询"] --> H["绕过 Vue ref 系统"]
+ I["bus.on/emit"] --> J["类型不安全
非 Vue 标准模式"]
+ K["33 个副作用 import"] --> L["无懒加载
全量打包"]
+```
+
+### 1.4 核心循环依赖
+
+```
+useSys ←→ useWM ←→ useApps ←→ useFS
+```
+通过 `setSysRef()` / `setSysForNotify()` / `setAppsRef()` 等 lazy setter 打破。
+
+---
+
+## 2. 目标架构
+
+### 2.1 总览
+
+```mermaid
+graph TD
+ subgraph "Vue 应用入口"
+ main.ts --> App.vue
+ end
+
+ subgraph "状态管理 Pinia"
+ settingsStore["settingsStore
(原 useSettings + useStore)"]
+ fsStore["fsStore
(原 useFS)"]
+ appStore["appStore
(原 useApps 注册表部分)"]
+ wmStore["wmStore
(原 useWM 状态部分)"]
+ notifyStore["notifyStore
(原 useNotify 数据部分)"]
+ sysStore["sysStore
(原 useSys 设置/电源部分)"]
+ end
+
+ subgraph "系统 UI 组件(纯 Vue SFC)"
+ MenuBar["<MenuBar />"]
+ Dock["<Dock />"]
+ DesktopIcons["<DesktopIcons />"]
+ Spotlight["<Spotlight />"]
+ NotificationCenter["<NotificationCenter />"]
+ ControlCenter["<ControlCenter />"]
+ LockScreen["<LockScreen />"]
+ Screensaver["<Screensaver />"]
+ BootScreen["<BootScreen />"]
+ ContextMenu["<ContextMenu />"]
+ DialogLayer["<DialogLayer />"]
+ end
+
+ subgraph "窗口系统"
+ WindowLayer["<WindowLayer />"]
+ WindowFrame["<WindowFrame />
(chrome: 标题栏/traffic lights/resize)"]
+ end
+
+ subgraph "应用层(懒加载)"
+ AppLoader["<AppLoader />
defineAsyncComponent"]
+ Finder["Finder.vue"]
+ Safari["Safari.vue"]
+ TxtEdit["...33 个应用"]
+ end
+
+ subgraph "组合式函数(纯逻辑,无 DOM)"
+ useKeyboard["useKeyboard(全局快捷键)"]
+ useIdleWatch["useIdleWatch(屏保计时)"]
+ useTheme["useTheme(暗色模式/壁纸)"]
+ useVolume["useVolume(音量控制)"]
+ useEventBus["useEventBus(mitt 替代 bus)"]
+ end
+
+ App.vue --> MenuBar
+ App.vue --> Dock
+ App.vue --> DesktopIcons
+ App.vue --> WindowLayer
+ App.vue --> Spotlight
+ App.vue --> NotificationCenter
+ App.vue --> ControlCenter
+ App.vue --> LockScreen
+ App.vue --> Screensaver
+ App.vue --> BootScreen
+ App.vue --> ContextMenu
+ App.vue --> DialogLayer
+
+ WindowLayer --> WindowFrame
+ WindowLayer --> AppLoader
+```
+
+### 2.2 核心原则
+
+1. **一切 UI 皆 Vue 组件**:不再使用 `el()` 创建任何 DOM 元素
+2. **状态归 Pinia**:跨模块共享状态走 Pinia store,不挂 `window`
+3. **通信走 provide/inject + mitt**:父子用 provide/inject,跨层级用 mitt(类型安全的事件总线)
+4. **无直接 DOM 查询**:用 Vue `ref` / `template ref` 替代 `$()` / `$$()`
+5. **应用懒加载**:`defineAsyncComponent` + 动态 import
+6. **仅保留必要的原生 API**:`pointer events` 拖拽、`localStorage`、`navigator.clipboard` 等不可避免的部分,封装在专用 composable 中
+
+---
+
+## 3. 前端架构设计
+
+### 3.1 组件层级树
+
+```mermaid
+graph TD
+ subgraph "App.vue(根组件)"
+ direction TB
+ A[App.vue]
+ end
+
+ subgraph "Layer 1:背景层 z-0"
+ B1["<OverlayBrightness />"]
+ B2["<OverlayNightShift />"]
+ B3["<DesktopArea />"]
+ end
+
+ subgraph "Layer 2:内容层 z-1~99"
+ C1["<WindowLayer />"]
+ end
+
+ subgraph "Layer 3:系统 UI 层 z-100~499"
+ D1["<MenuBar />"]
+ D2["<Dock />"]
+ end
+
+ subgraph "Layer 4:浮层 z-500~899"
+ E1["<Spotlight />"]
+ E2["<ControlCenter />"]
+ E3["<NotificationCenter />"]
+ E4["<ContextMenu />"]
+ E5["<DialogLayer />"]
+ E6["<BannerContainer />"]
+ end
+
+ subgraph "Layer 5:全屏覆盖层 z-900+"
+ F1["<LockScreen />"]
+ F2["<Screensaver />"]
+ F3["<BootScreen />"]
+ F4["<PowerOff />"]
+ end
+
+ A --> B1 & B2 & B3
+ A --> C1
+ A --> D1 & D2
+ A --> E1 & E2 & E3 & E4 & E5 & E6
+ A --> F1 & F2 & F3 & F4
+
+ C1 --> C1A["<WindowFrame /> ×N"]
+ C1A --> C1A1["<TrafficLights />"]
+ C1A --> C1A2["<AppLoader />"]
+ C1A2 --> C1A2A["应用 SFC
(Finder/Safari/...)"]
+```
+
+**z-index 分层规范**:
+
+| 层 | z-index 范围 | 组件 | 说明 |
+|---|---|---|---|
+| 背景层 | 0 | OverlayBrightness, OverlayNightShift, DesktopArea | 静态背景,无交互 |
+| 内容层 | 1–99 | WindowLayer → WindowFrame ×N | 窗口动态分配 z-index |
+| 系统 UI 层 | 100–499 | MenuBar(100), Dock(200) | 始终在窗口之上 |
+| 浮层 | 500–899 | Spotlight(500), ControlCenter(510), NotificationCenter(510), ContextMenu(600), DialogLayer(700), BannerContainer(800) | 临时弹出内容 |
+| 全屏覆盖层 | 900+ | LockScreen(900), Screensaver(950), BootScreen(999), PowerOff(998) | 独占式全屏状态 |
+
+### 3.2 数据流架构
+
+```mermaid
+flowchart LR
+ subgraph "数据源"
+ LS[localStorage]
+ UA[用户操作]
+ SYS[系统事件
resize/keyboard/idle]
+ end
+
+ subgraph "Pinia Stores(单一数据源)"
+ direction TB
+ SS["settingsStore
设置/外观/音量/壁纸"]
+ FS["fsStore
虚拟文件系统"]
+ WS["wmStore
窗口状态"]
+ AS["appStore
应用注册表"]
+ NS["notifyStore
通知数据"]
+ US["uiStore
菜单/对话框状态"]
+ end
+
+ subgraph "Composables(纯逻辑)"
+ UK[useKeyboard]
+ UI[useIdleWatch]
+ UT[useTheme]
+ UD[useDrag / useResize]
+ UC[useClipboard]
+ end
+
+ subgraph "Vue 组件(视图层)"
+ direction TB
+ MB["<MenuBar />"]
+ DK["<Dock />"]
+ SP["<Spotlight />"]
+ CC["<ControlCenter />"]
+ NC["<NotificationCenter />"]
+ WF["<WindowFrame />"]
+ APPS["33 个应用组件"]
+ end
+
+ subgraph "跨组件通信"
+ MITT["mitt 事件总线
(wm:focus, fs:changed,
volume:changed, apps:ready)"]
+ PI["provide / inject
(窗口级上下文)"]
+ end
+
+ LS -->|"watch 持久化"| SS
+ LS --> FS
+ LS --> NS
+
+ UA -->|"@click / v-model"| SS
+ UA --> WS
+ UA --> AS
+ UA --> US
+
+ SYS --> UK
+ SYS --> UI
+ SYS --> UT
+
+ SS -->|"reactive state"| MB
+ SS --> DK
+ SS --> CC
+ SS --> NC
+ SS --> WF
+ SS --> APPS
+
+ WS -->|"reactive state"| WF
+ WS --> DK
+
+ FS -->|"reactive state"| SP
+ FS --> APPS
+
+ NS -->|"reactive state"| NC
+ NS --> DK
+
+ AS -->|"registry lookup"| SP
+ AS --> WF
+ AS --> DK
+
+ US -->|"reactive state"| MB
+ US --> SP
+
+ UK -->|"快捷键路由"| SS
+ UK --> WS
+ UK --> AS
+
+ UI -->|"空闲超时"| SS
+
+ UT -->|"外观变更"| SS
+
+ MITT -->|"事件订阅"| WS
+ MITT --> FS
+ MITT --> DK
+ MITT --> APPS
+
+ PI -->|"窗口上下文"| APPS
+```
+
+**核心数据流原则**:
+
+1. **单向数据流**:`用户操作 → Store 更新 → 组件响应式重渲染`
+2. **Store 是唯一真相源**:所有共享状态必须位于 Pinia store 中
+3. **组件不直接修改其他组件的状态**:通过 store action 或事件总线
+4. **事件总线仅用于通知,不用于状态传递**:`emitter.emit('fs:changed')` 是"文件变了,你们自己去看",而非"文件变了,这是新数据"
+
+### 3.3 路由与懒加载策略
+
+本项目是**桌面模拟器**,不使用 vue-router(无需 URL 路由)。应用加载使用以下策略:
+
+```typescript
+// src/apps/loaders.ts
+import { defineAsyncComponent, type AsyncComponentLoader } from 'vue'
+
+/**
+ * 应用懒加载映射表
+ *
+ * 策略:
+ * - 核心应用(Finder)→ 立即加载(Eager)
+ * - 系统应用(前 7 个)→ 空闲时预加载(Idle Preload)
+ * - 其他应用 → 按需加载(Lazy)
+ */
+
+// 加载策略枚举
+enum LoadStrategy {
+ Eager, // 立即加载
+ IdlePreload, // requestIdleCallback 预加载
+ Lazy, // 首次打开时加载
+}
+
+export const appLoaders: Record = {
+ // === 核心应用(Eager:启动即加载) ===
+ finder: {
+ loader: () => import('./finder/Finder.vue'),
+ strategy: LoadStrategy.Eager,
+ },
+
+ // === 系统应用(IdlePreload:空闲时后台加载) ===
+ settings: {
+ loader: () => import('./settings/Settings.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+ safari: {
+ loader: () => import('./safari/Safari.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+ mail: {
+ loader: () => import('./mail/Mail.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+ messages: {
+ loader: () => import('./messages/Messages.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+ music: {
+ loader: () => import('./music/Music.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+ photos: {
+ loader: () => import('./photos/Photos.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+ notes: {
+ loader: () => import('./notes/Notes.vue'),
+ strategy: LoadStrategy.IdlePreload,
+ },
+
+ // === 其他应用(Lazy:按需加载) ===
+ calculator: {
+ loader: () => import('./calculator/Calculator.vue'),
+ strategy: LoadStrategy.Lazy,
+ },
+ calendar: {
+ loader: () => import('./calendar/Calendar.vue'),
+ strategy: LoadStrategy.Lazy,
+ },
+ // ... 其余 24 个应用
+}
+
+/**
+ * 在 appStore.init() 之后调用,利用浏览器空闲时间预加载常用应用
+ */
+export function initPreload() {
+ const idleLoaders = Object.values(appLoaders)
+ .filter(a => a.strategy === LoadStrategy.IdlePreload)
+
+ idleLoaders.forEach(({ loader }) => {
+ requestIdleCallback(() => {
+ // 触发异步加载但不等待结果
+ ;(loader as () => Promise)()
+ }, { timeout: 3000 })
+ })
+}
+```
+
+**加载状态处理**:
+
+```vue
+
+
+
+
+
+
+
![]()
+
正在加载 {{ win.title }}…
+
+
+
+
+
+
+```
+
+### 3.4 状态管理设计
+
+#### 3.4.1 Store 职责矩阵
+
+| Store | 拥有的状态 | 不拥有的状态 | 持久化 |
+|---|---|---|---|
+| `settingsStore` | 用户设置、外观、音量、壁纸、登录项 | 窗口状态、通知数据 | ✅ localStorage |
+| `fsStore` | 虚拟文件系统树、当前目录 | 应用注册信息 | ✅ localStorage |
+| `wmStore` | 窗口列表、activeWin、z-index、cascade | 窗口 DOM 元素 | ❌ 仅会话 |
+| `appStore` | 应用注册表、打开队列 | 应用内部状态 | ❌ 仅会话 |
+| `notifyStore` | 通知列表、已读状态 | 通知 DOM、横幅定时器 | ✅ localStorage |
+| `uiStore` | 菜单/对话框显隐、位置、回调 | 菜单/对话框 DOM | ❌ 仅会话 |
+
+#### 3.4.2 Store 间依赖规则
+
+```
+settingsStore ← 无依赖(叶子 store)
+fsStore ← 无依赖
+notifyStore ← 依赖 settingsStore(权限检查)
+appStore ← 依赖 wmStore(open 时创建窗口)
+wmStore ← 依赖 settingsStore(可用区域计算)、appStore(窗口应用信息)
+uiStore ← 无依赖
+```
+
+规则:**禁止 Store 间循环依赖**。如果 A 需要 B 的数据而 B 也需要 A 的数据,应提取到第三个 Store C 或使用事件总线解耦。
+
+#### 3.4.3 Pinia 插件:持久化
+
+```typescript
+// src/stores/plugins/persist.ts
+import type { PiniaPluginContext } from 'pinia'
+
+const PERSISTED_STORES = ['settings', 'fs', 'notify']
+
+export function persistPlugin({ store }: PiniaPluginContext) {
+ if (!PERSISTED_STORES.includes(store.$id)) return
+
+ // 初始化:从 localStorage 恢复
+ const saved = localStorage.getItem(`macos-web:${store.$id}`)
+ if (saved) {
+ try { store.$patch(JSON.parse(saved)) } catch { /* ignore */ }
+ }
+
+ // 自动持久化
+ store.$subscribe((_, state) => {
+ localStorage.setItem(`macos-web:${store.$id}`, JSON.stringify(state))
+ }, { detached: true })
+}
+```
+
+### 3.5 事件通信设计
+
+#### 3.5.1 通信方式选型
+
+| 场景 | 方式 | 示例 |
+|---|---|---|
+| 父子组件通信 | `props` + `emits` | `` |
+| 祖先→后代(跨层级) | `provide` / `inject` | 窗口级上下文(win 对象、clipboard 等) |
+| 兄弟/任意组件(通知类) | `mitt` 事件总线 | `emitter.emit('fs:changed', { paths })` |
+| 全局状态共享 | Pinia store | `settingsStore.settings.darkMode` |
+| 浏览器级事件 | `composable` 封装 | `useKeyboard()` 监听 keydown |
+
+#### 3.5.2 mitt 事件契约
+
+```typescript
+// src/composables/useEventBus.ts
+import mitt from 'mitt'
+
+// ⚠️ 所有事件必须在此声明类型,保证端到端类型安全
+type Events = {
+ // 窗口事件
+ 'wm:focus': { winId: string; appId: string }
+ 'wm:changed': void
+ 'wm:closed': { winId: string }
+
+ // 文件系统事件
+ 'fs:changed': { paths: string[] }
+ 'trash:changed': void
+
+ // 系统事件
+ 'apps:ready': void
+ 'volume:changed': number
+ 'unlocked': void
+ 'locked': void
+
+ // 应用间事件
+ 'spotlight:open': void
+ 'spotlight:close': void
+}
+
+export const emitter = mitt()
+```
+
+#### 3.5.3 provide / inject 设计
+
+```typescript
+// 窗口级上下文 —— 由 WindowFrame 提供,所有应用组件注入
+
+// src/composables/useWindowContext.ts
+import { provide, inject, type InjectionKey, type Ref } from 'vue'
+
+interface WindowContext {
+ winId: string
+ appId: string
+ clipboard: Ref // 应用内剪贴板
+ isActive: Ref // 窗口是否聚焦
+ closeWindow: () => void
+ setTitle: (title: string) => void
+}
+
+export const WIN_CTX_KEY: InjectionKey = Symbol('winCtx')
+
+export function provideWindowContext(ctx: WindowContext) {
+ provide(WIN_CTX_KEY, ctx)
+}
+
+export function useWindowContext(): WindowContext {
+ const ctx = inject(WIN_CTX_KEY)
+ if (!ctx) throw new Error('useWindowContext() must be used inside a WindowFrame')
+ return ctx
+}
+```
+
+### 3.6 组件分类与职责
+
+```mermaid
+quadrantChart
+ title 组件分类矩阵
+ x-axis "有状态" --> "无状态"
+ y-axis "有 DOM" --> "无 DOM"
+ quadrant-1 "容器组件"
+ quadrant-2 "展示组件"
+ quadrant-3 "逻辑 composable"
+ quadrant-4 "纯工具函数"
+ "WindowFrame": [0.2, 0.85]
+ "MenuBar": [0.25, 0.9]
+ "Dock": [0.3, 0.9]
+ "Spotlight": [0.25, 0.85]
+ "ControlCenter": [0.2, 0.85]
+ "Finder": [0.35, 0.8]
+ "TrafficLights": [0.85, 0.55]
+ "DockIcon": [0.75, 0.6]
+ "Banner": [0.9, 0.5]
+ "CCToggle": [0.85, 0.55]
+ "useDrag": [0.95, 0.15]
+ "useKeyboard": [0.9, 0.1]
+ "useTheme": [0.85, 0.1]
+ "useClipboard": [0.95, 0.05]
+ "emitter": [0.98, 0.02]
+ "stdMenus": [0.98, 0.02]
+```
+
+| 分类 | 特征 | 示例 | 规则 |
+|---|---|---|---|
+| **容器组件** | 连接 Store,管理子组件状态,有 DOM 输出 | `WindowFrame`, `MenuBar`, `Dock`, `Spotlight` | 可访问 Pinia store,可 emit 事件 |
+| **展示组件** | 纯 props 输入 + emits 输出,无 Store 依赖 | `TrafficLights`, `DockIcon`, `Banner`, `CCToggle` | 禁止直接访问 Pinia store |
+| **逻辑 composable** | 封装有状态逻辑,无 DOM 输出 | `useDrag`, `useKeyboard`, `useTheme`, `useClipboard` | 可访问 Pinia store,不输出 DOM |
+| **纯工具函数** | 纯函数,无副作用,无状态 | `stdMenus`, `fmtDateCN`, `clamp` | 禁止访问任何 store 或 DOM |
+
+### 3.7 目录结构设计
+
+```
+src/
+├── main.ts # 入口:createApp + Pinia + 全局样式
+├── App.vue # 根布局组件
+│
+├── stores/ # Pinia 状态管理
+│ ├── index.ts # createPinia + 插件注册
+│ ├── plugins/
+│ │ └── persist.ts # 持久化插件
+│ ├── settings.ts # useSettingsStore
+│ ├── fs.ts # useFSStore
+│ ├── wm.ts # useWMStore
+│ ├── apps.ts # useAppStore
+│ ├── notify.ts # useNotifyStore
+│ └── ui.ts # useUIStore
+│
+├── composables/ # 纯逻辑组合式函数(无 DOM)
+│ ├── useEventBus.ts # mitt 实例 + 类型定义
+│ ├── useWindowContext.ts # provide/inject 窗口上下文
+│ ├── useKeyboard.ts # 全局快捷键
+│ ├── useIdleWatch.ts # 空闲计时 & 屏保触发
+│ ├── useTheme.ts # 暗色模式 & 壁纸
+│ ├── useVolume.ts # 音量 & 媒体注册
+│ ├── useDrag.ts # 窗口拖拽(pointer events 封装)
+│ ├── useResize.ts # 窗口缩放(pointer events 封装)
+│ ├── useClipboard.ts # 剪贴板操作封装
+│ ├── useClickOutside.ts # 外部点击检测
+│ ├── useStdMenus.ts # 标准菜单模板(纯函数)
+│ └── useDockMag.ts # Dock 放大效果
+│
+├── components/ # Vue 组件
+│ ├── system/ # 系统 UI 组件
+│ │ ├── MenuBar.vue # 菜单栏(容器)
+│ │ ├── MenuBarItem.vue # 菜单栏单项(展示)
+│ │ ├── Dock.vue # 程序坞(容器)
+│ │ ├── DockIcon.vue # 程序坞图标(展示)
+│ │ ├── DesktopIcons.vue # 桌面图标区(容器)
+│ │ ├── DesktopIcon.vue # 单个桌面图标(展示)
+│ │ ├── Spotlight.vue # 聚焦搜索(容器)
+│ │ ├── SpotlightItem.vue # 搜索结果项(展示)
+│ │ ├── ControlCenter.vue # 控制中心(容器)
+│ │ ├── CCToggle.vue # 控制中心开关(展示)
+│ │ ├── CCSlider.vue # 控制中心滑块(展示)
+│ │ ├── NotificationCenter.vue # 通知中心(容器)
+│ │ ├── BannerContainer.vue # 横幅容器(容器)
+│ │ ├── Banner.vue # 单个横幅(展示)
+│ │ ├── LockScreen.vue # 锁屏界面(容器)
+│ │ ├── Screensaver.vue # 屏保(容器)
+│ │ ├── BootScreen.vue # 启动动画(展示)
+│ │ └── PowerOff.vue # 关机提示(展示)
+│ ├── wm/ # 窗口管理组件
+│ │ ├── WindowLayer.vue # 窗口层容器(容器)
+│ │ ├── WindowFrame.vue # 窗口框架(容器)
+│ │ ├── TrafficLights.vue # 红绿灯按钮(展示)
+│ │ └── AppLoader.vue # 应用加载器(容器)
+│ └── ui/ # 通用 UI 组件
+│ ├── ContextMenu.vue # 右键/下拉菜单(容器)
+│ ├── ContextMenuItem.vue # 菜单项(展示)
+│ ├── DialogLayer.vue # 对话框层(容器)
+│ ├── AlertDialog.vue # 警告框(展示)
+│ ├── ConfirmDialog.vue # 确认框(展示)
+│ └── PromptDialog.vue # 输入框(展示)
+│
+├── apps/ # 应用层
+│ ├── loaders.ts # 懒加载映射 + 预加载策略
+│ ├── finder/
+│ │ ├── index.ts # AppDefinition(纯数据)
+│ │ └── Finder.vue # Finder SFC
+│ ├── safari/
+│ │ ├── index.ts
+│ │ └── Safari.vue
+│ └── ...(其余 31 个应用,结构相同)
+│
+├── styles/ # 样式(不变)
+│ ├── base.css
+│ ├── desktop.css
+│ ├── window.css
+│ ├── apps.css
+│ └── apps2.css
+│
+├── types/ # 全局类型定义
+│ ├── app.ts # AppDefinition, MenuItem
+│ ├── window.ts # WinState, Rect
+│ ├── fs.ts # FSNode, FSEntry
+│ └── settings.ts # Settings, Wallpaper
+│
+└── utils/ # 纯工具函数(无 DOM)
+ ├── format.ts # fmtDateCN, fmtTime, fmtBytes
+ ├── math.ts # clamp
+ ├── misc.ts # uid, debounce, esc
+ └── index.ts # 统一导出
+```
+
+### 3.8 关键技术决策
+
+| 决策点 | 选择 | 理由 |
+|---|---|---|
+| 路由方案 | **不使用 vue-router** | 桌面模拟器无 URL 路由需求,应用切换由 `wmStore` 窗口管理 + `AppLoader` 动态组件实现 |
+| 状态管理 | **Pinia**(非 Vuex) | Vue 3 官方推荐,完整 TS 支持,模块化设计 |
+| 跨组件通信 | **mitt + provide/inject** | mitt 用于全局通知,provide/inject 用于窗口级上下文,避免过度使用全局事件 |
+| CSS 方案 | **纯 CSS(不变)** | 项目样式已成熟,无需引入 Tailwind/UnoCSS,减少重构范围 |
+| 构建工具 | **Vite(不变)** | 当前已使用 Vite,保留配置 |
+| 测试框架 | **Vitest(不变)** | 当前已使用 Vitest |
+| 窗口拖拽/缩放 | **原生 Pointer Events + composable 封装** | 这是性能关键路径,Vue 事件系统不适合高频 pointermove |
+| 持久化 | **Pinia watch + localStorage** | 简单可靠,无需引入额外的持久化库 |
+| 应用加载 | **defineAsyncComponent + 分级策略** | Eager/IdlePreload/Lazy 三级策略兼顾首屏速度与体验 |
+| 类型安全 | **全局禁止 `any`**(eslint rule) | 强制所有接口、store、事件使用精确类型 |
+
+### 3.9 性能策略
+
+```mermaid
+flowchart TB
+ subgraph "首屏加载"
+ A1["仅加载 Eager 应用(Finder)"]
+ A2["系统 UI 组件同步渲染"]
+ A3["requestIdleCallback 预加载 7 个常用应用"]
+ end
+
+ subgraph "窗口操作"
+ B1["打开应用 → 检查是否已加载"]
+ B2["未加载 → Suspense + loading 占位"]
+ B3["已加载 → 立即显示 <component :is=>"]
+ end
+
+ subgraph "关闭优化"
+ C1["关闭窗口 → 组件 v-if 卸载"]
+ C2["已加载的异步 chunk 保留在浏览器缓存"]
+ C3["再次打开 → 不重新下载 JS"]
+ end
+
+ subgraph "内存管理"
+ D1["窗口关闭 → clearInterval/timeout"]
+ D2["onUnmounted → 移除事件监听"]
+ D3["Pinia store 按需持久化,不存储 DOM 引用"]
+ end
+
+ A1 --> A2 --> A3
+ B1 --> B2
+ B1 --> B3
+ C1 --> C2 --> C3
+```
+
+---
+
+## 4. 重构总路线
+
+```
+Phase 1: 基础设施层 Phase 2: 系统 UI 组件化 Phase 3: WM 现代化
+───────────────────── ──────────────────────── ────────────────
+Pinia stores MenuBar → SFC WindowFrame → SFC
+mitt 事件总线 Dock → SFC TrafficLights → SFC
+消除 window 全局暴露 DesktopIcons → SFC 拖拽/缩放 composable
+消除 lazy ref Spotlight → SFC resize handles → SFC
+ NotificationCenter → SFC
+ ControlCenter → SFC Phase 5: 清理
+ LockScreen → SFC ───────────
+ Screensaver → SFC 删除 utils/index.ts
+ BootScreen → SFC 删除 system/index.ts
+ ContextMenu → SFC 删除 el() 函数
+ Dialog/Alert/Confirm → SFC 删除 $/$$
+ 全局类型检查
+
+ Phase 4: 应用层优化
+ ─────────────────────
+ defineAsyncComponent
+ 移除 (window as any).Sys
+ 移除 h()/render() 手动挂载
+ stdMenus 纯函数化
+```
+
+---
+
+## 5. 阶段一:基础设施层
+
+### 4.1 建立 Pinia Stores
+
+#### 4.1.1 `settingsStore` — 替代 useSettings + useStore
+
+```typescript
+// src/stores/settings.ts
+import { defineStore } from 'pinia'
+import { reactive, watch } from 'vue'
+
+export const WALLPAPERS = [ /* 原数据 */ ]
+export const DEFAULT_SETTINGS = { /* 原数据 */ }
+
+export const useSettingsStore = defineStore('settings', () => {
+ const settings = reactive(structuredClone(DEFAULT_SETTINGS))
+
+ // 持久化 watch(替代 store.set/load)
+ watch(settings, (v) => {
+ localStorage.setItem('macos-web:settings', JSON.stringify(v))
+ }, { deep: true })
+
+ // 计算属性
+ const isDark = computed(() => { /* 原 Sys.isDark() */ })
+ const wallpaperSrc = computed(() => { /* 原 Sys.wallpaperSrc() */ })
+
+ // 方法
+ function applyAppearance() { /* 操作 document.body.classList */ }
+ function applyWallpaper() { /* ... */ }
+
+ return { settings, isDark, wallpaperSrc, applyAppearance, applyWallpaper }
+})
+```
+
+**改动要点**:
+- `store.get/set` → Pinia `$state` + watch 持久化
+- `Sys.settings.xxx` → `settingsStore.settings.xxx`
+- `Sys.isDark()` → `settingsStore.isDark` (computed)
+- `Sys.save()` → 自动 watch 持久化
+
+#### 4.1.2 `fsStore` — 替代 useFS
+
+```typescript
+// src/stores/fs.ts
+export const useFSStore = defineStore('fs', () => {
+ const root = ref(null)
+ const HOME = '/Users/guest'
+ const TRASH = computed(() => HOME + '/.Trash')
+
+ function init() { /* 原 fs.init() */ }
+ function list(path: string) { /* ... */ }
+ function node(path: string) { /* ... */ }
+ function mkdir(path: string) { /* ... */ }
+ function rename(path: string, newName: string) { /* ... */ }
+ function trash(path: string) { /* ... */ }
+ function emptyTrash() { /* ... */ }
+
+ // 用 mitt 替代 bus.emit('fs:changed')
+ function notifyChange(paths: string[]) {
+ emitter.emit('fs:changed', { paths })
+ }
+
+ return { root, HOME, TRASH, init, list, node, mkdir, rename, trash, emptyTrash }
+})
+```
+
+**改动要点**:
+- `fs.node(path)` → `fsStore.node(path)`,响应式数据
+- `bus.emit('fs:changed')` → `emitter.emit('fs:changed')`(mitt)
+- 文件系统树用 `ref` / `reactive` 保持响应式
+
+#### 4.1.3 `wmStore` — 替代 useWM 的状态部分
+
+```typescript
+// src/stores/wm.ts
+export const useWMStore = defineStore('wm', () => {
+ const windows = reactive([])
+ const activeWin = ref(null)
+ const zTop = ref(100)
+ const cascadeCount = ref(0)
+
+ function addWindow(win: WinState) { windows.push(win) }
+ function removeWindow(id: string) { /* splice */ }
+ function focus(win: WinState) { /* 更新 z-index + activeWin */ }
+
+ return { windows, activeWin, zTop, cascadeCount, addWindow, removeWindow, focus }
+})
+```
+
+**改动要点**:
+- WM `windows`/`activeWin`/`zTop` → Pinia store
+- 窗口 DOM 创建逻辑分离到 `` 组件
+
+#### 4.1.4 `appStore` — 替代 useApps 注册表
+
+```typescript
+// src/stores/apps.ts
+export const useAppStore = defineStore('apps', () => {
+ const registry = reactive>({})
+
+ function register(def: AppDefinition) { registry[def.id] = def }
+ function get(id: string) { return registry[id] }
+
+ // open 方法:不再手动调用 wm.openWindow + h() + render()
+ // 而是设置一个"待打开"信号,由 组件消费
+ const pendingOpen = ref<{ id: string; args?: any } | null>(null)
+
+ function open(id: string, args?: any) {
+ pendingOpen.value = { id, args }
+ }
+
+ return { registry, register, get, pendingOpen, open }
+})
+```
+
+#### 4.1.5 `notifyStore` — 替代 useNotify 数据部分
+
+```typescript
+// src/stores/notify.ts
+export const useNotifyStore = defineStore('notify', () => {
+ const notifications = ref([])
+
+ function send(n: Notification) { /* push + 持久化 */ }
+ function markRead(id: string) { /* ... */ }
+ function remove(id: string) { /* ... */ }
+ function clearAll() { /* ... */ }
+ function badgeCount(appId: string) { /* computed */ }
+
+ return { notifications, send, markRead, remove, clearAll, badgeCount }
+})
+```
+
+### 4.2 引入 mitt 替代 useBus
+
+```bash
+pnpm add mitt
+```
+
+```typescript
+// src/composables/useEventBus.ts
+import mitt from 'mitt'
+
+type Events = {
+ 'wm:focus': WinState
+ 'wm:changed': void
+ 'fs:changed': { paths: string[] }
+ 'apps:ready': void
+ 'volume:changed': number
+ 'trash:changed': void
+ // ... 所有事件类型
+}
+
+export const emitter = mitt()
+```
+
+**替代映射**:
+| 原 bus 用法 | 新 mitt 用法 |
+|---|---|
+| `bus.on('wm:focus', fn)` | `emitter.on('wm:focus', fn)` |
+| `bus.emit('wm:focus', win)` | `emitter.emit('wm:focus', win)` |
+| `bus.off('wm:focus', fn)` | `emitter.off('wm:focus', fn)` |
+
+### 4.3 消除 `window` 全局暴露
+
+**删除 `system/index.ts` 中的所有 `(window as any)` 赋值**。
+
+替代方案:
+- 模块间引用 → Pinia store / provide-inject
+- App 组件引用 → 通过 `useAppStore().get(id)` 获取
+- 工具函数引用 → 直接 import
+
+### 4.4 消除 lazy ref
+
+**删除所有 `let _Xxx: any = null` + `setXxxRef()` 模式**。
+
+原懒引用的用途和替代方案:
+| 原用途 | 替代 |
+|---|---|
+| `setSysRef(wm)` — WM 需要 Sys.settings | WM 直接 import `useSettingsStore` |
+| `setSysForNotify(sys)` — Notify 需要 Sys.settings | Notify 直接 import `useSettingsStore` |
+| `setSysForSpotlight(sys)` — Spotlight 需要 Sys | Spotlight 直接 import 对应 store |
+| `setAppsRef(apps)` — FS 需要 Apps 注册表 | FS 直接 import `useAppStore` |
+| `setAppStoreRef(a)` — Sys 需要 AppStore | 延迟访问 `useAppStore().get('appstore')` |
+
+解决循环依赖的根本方法:**拆分模块**。例如 `useSys.ts` 应该拆分为:
+- `stores/settings.ts` — 设置管理
+- `composables/useKeyboard.ts` — 全局快捷键
+- `composables/useIdleWatch.ts` — 空闲计时/屏保
+- `composables/useTheme.ts` — 外观/壁纸应用
+- `` / `` / `` 等 Vue 组件
+
+---
+
+## 6. 阶段二:系统 UI 组件化
+
+### 5.1 `` — 替代 Sys.buildMenubar()
+
+**原实现**:`useSys.ts` 中 `buildMenubar()` 用 `el()` 构建整个菜单栏 DOM,操作 `#menubar-left` / `#menubar-right`。
+
+**目标实现**:
+
+```vue
+
+
+
+
+
+
+```
+
+**改动要点**:
+- `el()` 构建 DOM → Vue 模板循环
+- `(this as any)._mbClock` → `ref` + `setInterval`
+- `addEventListener('click')` → `@click`
+- 菜单栏左侧应用菜单 → 根据 `wmStore.activeWin` 计算
+
+### 5.2 `` — 替代 Sys.renderDock()
+
+```vue
+
+
+
+
+
+```
+
+**改动要点**:
+- `dock.innerHTML = ''` + `el()` 循环构建 → `v-for` 组件
+- `ic.addEventListener('click')` → `@click` emit
+- `ic.addEventListener('contextmenu')` → `@contextmenu` emit
+- Dock 放大动画用 CSS + 少量 pointer 事件(封装 composable)
+- `updateDockDots()` → 基于 `wmStore.windows` 的 computed
+
+### 5.3 `` — 替代 Sys.renderDesktopIcons()
+
+```vue
+
+
+
+
+
+
+```
+
+**改动要点**:
+- 每个桌面图标是一个独立的 `` SFC
+- 拖拽移动用 pointer events(保留原生,封装在 composable 中)
+- 双击/右键 → emit 到父组件处理
+
+### 5.4 `` / `` — 替代 useUI
+
+这是最关键的变革之一:**将动态创建的弹出菜单和对话框改为 Vue 组件 + Teleport**。
+
+```vue
+
+
+
+
+
+
+```
+
+**全局菜单/对话框管理**:
+
+```typescript
+// src/stores/ui.ts
+export const useUIStore = defineStore('ui', () => {
+ // 弹出菜单
+ const menuVisible = ref(false)
+ const menuItems = ref