diff --git a/.vscode/settings.json b/.vscode/settings.json
index 7e74831..0d09930 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
 {
     "[typescript]": {
-        "editor.defaultFormatter": "vscode.typescript-language-features"
+        "editor.defaultFormatter": "esbenp.prettier-vscode"
     },
     "[javascript]": {
         "editor.defaultFormatter": "esbenp.prettier-vscode"
diff --git a/config/index.ts b/config/index.ts
index c191c59..f6a2eb2 100644
--- a/config/index.ts
+++ b/config/index.ts
@@ -18,7 +18,7 @@ interface IConfig {
     }
 }
 export default {
-    app_title: "ada",
+    app_title: "zephyr", // 和风
     default_config: {
         storagePath: "$storagePath$",
         language: "zh",
diff --git a/electron.vite.config.ts b/electron.vite.config.ts
index 1870ac4..5327a38 100644
--- a/electron.vite.config.ts
+++ b/electron.vite.config.ts
@@ -20,7 +20,24 @@ export default defineConfig({
     renderer: {
         resolve: {
             alias: {
+                config: resolve("config"),
                 "@renderer": resolve("src/renderer/src"),
+                "@res": resolve("resources"),
+            },
+        },
+        css: {
+            preprocessorOptions: {
+                scss: {
+                    additionalData: `@use "@renderer/assets/style/global" as *;\n`,
+                },
+            },
+        },
+        build: {
+            rollupOptions: {
+                input: {
+                    main: resolve(__dirname, "./src/renderer/index.html"),
+                    about: resolve(__dirname, "src/renderer/about.html"),
+                },
             },
         },
         plugins: [UnoCSS(), vue()],
diff --git a/package.json b/package.json
index dfd8e39..858836b 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,8 @@
         "typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
         "typecheck": "npm run typecheck:node && npm run typecheck:web",
         "start": "electron-vite preview",
-        "dev": "electron-vite dev",
-        "dev:watch": "electron-vite dev --watch",
+        "dev": "chcp 65001 && set DEBUG=app:*&& electron-vite dev",
+        "dev:watch": "chcp 65001 & set DEBUG=app:*& electron-vite dev --watch",
         "build": "npm run typecheck && electron-vite build",
         "postinstall": "electron-builder install-app-deps",
         "build:unpack": "npm run build && electron-builder --dir",
@@ -25,22 +25,25 @@
     "dependencies": {
         "@electron-toolkit/preload": "^3.0.0",
         "@electron-toolkit/utils": "^3.0.0",
+        "@types/debug": "^4.1.12",
         "@unocss/reset": "^0.64.1",
         "electron-updater": "^6.1.7",
         "inversify": "^6.1.4",
         "lowdb": "^7.0.1",
-        "reflect-metadata": "^0.2.2"
+        "reflect-metadata": "^0.2.2",
+        "sass": "^1.81.0"
     },
     "devDependencies": {
         "@electron-toolkit/eslint-config": "^1.0.2",
         "@electron-toolkit/eslint-config-ts": "^2.0.0",
         "@electron-toolkit/tsconfig": "^1.0.1",
         "@rushstack/eslint-patch": "^1.10.3",
-        "@types/node": "^20.14.8",
+        "@types/node": "^20.15.0",
         "@unocss/preset-rem-to-px": "^0.64.1",
         "@vitejs/plugin-vue": "^5.0.5",
         "@vue/eslint-config-prettier": "^9.0.0",
         "@vue/eslint-config-typescript": "^13.0.0",
+        "debug": "^4.4.0",
         "electron": "^31.0.2",
         "electron-builder": "^24.13.3",
         "electron-vite": "^2.3.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e72f3e0..39e3dda 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,6 +14,9 @@ importers:
       '@electron-toolkit/utils':
         specifier: ^3.0.0
         version: 3.0.0(electron@31.7.4)
+      '@types/debug':
+        specifier: ^4.1.12
+        version: 4.1.12
       '@unocss/reset':
         specifier: ^0.64.1
         version: 0.64.1
@@ -29,6 +32,9 @@ importers:
       reflect-metadata:
         specifier: ^0.2.2
         version: 0.2.2
+      sass:
+        specifier: ^1.81.0
+        version: 1.81.0
     devDependencies:
       '@electron-toolkit/eslint-config':
         specifier: ^1.0.2
@@ -38,25 +44,28 @@ importers:
         version: 2.0.0(eslint@8.57.1)(typescript@5.6.3)
       '@electron-toolkit/tsconfig':
         specifier: ^1.0.1
-        version: 1.0.1(@types/node@20.17.6)
+        version: 1.0.1(@types/node@20.15.0)
       '@rushstack/eslint-patch':
         specifier: ^1.10.3
         version: 1.10.4
       '@types/node':
-        specifier: ^20.14.8
-        version: 20.17.6
+        specifier: ^20.15.0
+        version: 20.15.0
       '@unocss/preset-rem-to-px':
         specifier: ^0.64.1
         version: 0.64.1
       '@vitejs/plugin-vue':
         specifier: ^5.0.5
-        version: 5.2.0(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))
+        version: 5.2.0(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^9.0.0
         version: 9.0.0(eslint@8.57.1)(prettier@3.3.3)
       '@vue/eslint-config-typescript':
         specifier: ^13.0.0
         version: 13.0.0(eslint-plugin-vue@9.31.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.6.3)
+      debug:
+        specifier: ^4.4.0
+        version: 4.4.0
       electron:
         specifier: ^31.0.2
         version: 31.7.4
@@ -65,7 +74,7 @@ importers:
         version: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
       electron-vite:
         specifier: ^2.3.0
-        version: 2.3.0(vite@5.4.11(@types/node@20.17.6))
+        version: 2.3.0(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))
       eslint:
         specifier: ^8.57.0
         version: 8.57.1
@@ -80,10 +89,10 @@ importers:
         version: 5.6.3
       unocss:
         specifier: ^0.64.1
-        version: 0.64.1(postcss@8.4.49)(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))
+        version: 0.64.1(postcss@8.4.49)(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))
       vite:
         specifier: ^5.3.1
-        version: 5.4.11(@types/node@20.17.6)
+        version: 5.4.11(@types/node@20.15.0)(sass@1.81.0)
       vue:
         specifier: ^3.4.30
         version: 3.5.12(typescript@5.6.3)
@@ -606,6 +615,94 @@ packages:
     resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
     engines: {node: '>= 8'}
 
+  '@parcel/watcher-android-arm64@2.5.0':
+    resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [android]
+
+  '@parcel/watcher-darwin-arm64@2.5.0':
+    resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@parcel/watcher-darwin-x64@2.5.0':
+    resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@parcel/watcher-freebsd-x64@2.5.0':
+    resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@parcel/watcher-linux-arm-glibc@2.5.0':
+    resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-arm-musl@2.5.0':
+    resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-linux-arm64-glibc@2.5.0':
+    resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-arm64-musl@2.5.0':
+    resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-linux-x64-glibc@2.5.0':
+    resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-x64-musl@2.5.0':
+    resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-win32-arm64@2.5.0':
+    resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@parcel/watcher-win32-ia32@2.5.0':
+    resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@parcel/watcher-win32-x64@2.5.0':
+    resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [win32]
+
+  '@parcel/watcher@2.5.0':
+    resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==}
+    engines: {node: '>= 10.0.0'}
+
   '@pkgjs/parseargs@0.11.0':
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
@@ -761,8 +858,11 @@ packages:
   '@types/ms@0.7.34':
     resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
 
-  '@types/node@20.17.6':
-    resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==}
+  '@types/node@20.15.0':
+    resolution: {integrity: sha512-eQf4OkH6gA9v1W0iEpht/neozCsZKMTK+C4cU6/fv7wtJCCL8LEQ4hie2Ln8ZP/0YYM2xGj7//f8xyqItkJ6QA==}
+
+  '@types/node@22.10.1':
+    resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
 
   '@types/plist@3.0.5':
     resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==}
@@ -1191,6 +1291,10 @@ packages:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
 
+  chokidar@4.0.1:
+    resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==}
+    engines: {node: '>= 14.16.0'}
+
   chownr@2.0.0:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
     engines: {node: '>=10'}
@@ -1292,8 +1396,8 @@ packages:
   de-indent@1.0.2:
     resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
 
-  debug@4.3.7:
-    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+  debug@4.4.0:
+    resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
     engines: {node: '>=6.0'}
     peerDependencies:
       supports-color: '*'
@@ -1330,6 +1434,11 @@ packages:
   destr@2.0.3:
     resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==}
 
+  detect-libc@1.0.3:
+    resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+    engines: {node: '>=0.10'}
+    hasBin: true
+
   detect-node@2.1.0:
     resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
 
@@ -1745,6 +1854,9 @@ packages:
     resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
     engines: {node: '>= 4'}
 
+  immutable@5.0.2:
+    resolution: {integrity: sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==}
+
   import-fresh@3.3.0:
     resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
     engines: {node: '>=6'}
@@ -2031,6 +2143,9 @@ packages:
   node-addon-api@1.7.2:
     resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
 
+  node-addon-api@7.1.1:
+    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
   node-fetch-native@1.6.4:
     resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==}
 
@@ -2198,6 +2313,10 @@ packages:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
 
+  readdirp@4.0.2:
+    resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==}
+    engines: {node: '>= 14.16.0'}
+
   reflect-metadata@0.2.2:
     resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
 
@@ -2255,6 +2374,11 @@ packages:
   sanitize-filename@1.6.3:
     resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==}
 
+  sass@1.81.0:
+    resolution: {integrity: sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
   sax@1.4.1:
     resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
 
@@ -2445,8 +2569,11 @@ packages:
   unconfig@0.5.5:
     resolution: {integrity: sha512-VQZ5PT9HDX+qag0XdgQi8tJepPhXiR/yVOkn707gJDKo31lGjRilPREiQJ9Z6zd/Ugpv6ZvO5VxVIcatldYcNQ==}
 
-  undici-types@6.19.8:
-    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+  undici-types@6.13.0:
+    resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==}
+
+  undici-types@6.20.0:
+    resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
 
   universalify@0.1.2:
     resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
@@ -2640,7 +2767,7 @@ snapshots:
       '@babel/traverse': 7.25.9
       '@babel/types': 7.26.0
       convert-source-map: 2.0.0
-      debug: 4.3.7
+      debug: 4.4.0
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -2714,7 +2841,7 @@ snapshots:
       '@babel/parser': 7.26.2
       '@babel/template': 7.25.9
       '@babel/types': 7.26.0
-      debug: 4.3.7
+      debug: 4.4.0
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -2747,9 +2874,9 @@ snapshots:
     dependencies:
       electron: 31.7.4
 
-  '@electron-toolkit/tsconfig@1.0.1(@types/node@20.17.6)':
+  '@electron-toolkit/tsconfig@1.0.1(@types/node@20.15.0)':
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 20.15.0
 
   '@electron-toolkit/utils@3.0.0(electron@31.7.4)':
     dependencies:
@@ -2763,7 +2890,7 @@ snapshots:
 
   '@electron/get@2.0.3':
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       env-paths: 2.2.1
       fs-extra: 8.1.0
       got: 11.8.6
@@ -2777,7 +2904,7 @@ snapshots:
 
   '@electron/notarize@2.2.1':
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       fs-extra: 9.1.0
       promise-retry: 2.0.1
     transitivePeerDependencies:
@@ -2786,7 +2913,7 @@ snapshots:
   '@electron/osx-sign@1.0.5':
     dependencies:
       compare-version: 0.1.2
-      debug: 4.3.7
+      debug: 4.4.0
       fs-extra: 10.1.0
       isbinaryfile: 4.0.10
       minimist: 1.2.8
@@ -2798,7 +2925,7 @@ snapshots:
     dependencies:
       '@electron/asar': 3.2.17
       '@malept/cross-spawn-promise': 1.1.1
-      debug: 4.3.7
+      debug: 4.4.0
       dir-compare: 3.3.0
       fs-extra: 9.1.0
       minimatch: 3.1.2
@@ -2957,7 +3084,7 @@ snapshots:
   '@eslint/eslintrc@2.1.4':
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.7
+      debug: 4.4.0
       espree: 9.6.1
       globals: 13.24.0
       ignore: 5.3.2
@@ -2973,7 +3100,7 @@ snapshots:
   '@humanwhocodes/config-array@0.13.0':
     dependencies:
       '@humanwhocodes/object-schema': 2.0.3
-      debug: 4.3.7
+      debug: 4.4.0
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -2989,7 +3116,7 @@ snapshots:
       '@antfu/install-pkg': 0.4.1
       '@antfu/utils': 0.7.10
       '@iconify/types': 2.0.0
-      debug: 4.3.7
+      debug: 4.4.0
       kolorist: 1.8.0
       local-pkg: 0.5.0
       mlly: 1.7.3
@@ -3041,7 +3168,7 @@ snapshots:
 
   '@malept/flatpak-bundler@0.4.0':
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       fs-extra: 9.1.0
       lodash: 4.17.21
       tmp-promise: 3.0.3
@@ -3060,6 +3187,67 @@ snapshots:
       '@nodelib/fs.scandir': 2.1.5
       fastq: 1.17.1
 
+  '@parcel/watcher-android-arm64@2.5.0':
+    optional: true
+
+  '@parcel/watcher-darwin-arm64@2.5.0':
+    optional: true
+
+  '@parcel/watcher-darwin-x64@2.5.0':
+    optional: true
+
+  '@parcel/watcher-freebsd-x64@2.5.0':
+    optional: true
+
+  '@parcel/watcher-linux-arm-glibc@2.5.0':
+    optional: true
+
+  '@parcel/watcher-linux-arm-musl@2.5.0':
+    optional: true
+
+  '@parcel/watcher-linux-arm64-glibc@2.5.0':
+    optional: true
+
+  '@parcel/watcher-linux-arm64-musl@2.5.0':
+    optional: true
+
+  '@parcel/watcher-linux-x64-glibc@2.5.0':
+    optional: true
+
+  '@parcel/watcher-linux-x64-musl@2.5.0':
+    optional: true
+
+  '@parcel/watcher-win32-arm64@2.5.0':
+    optional: true
+
+  '@parcel/watcher-win32-ia32@2.5.0':
+    optional: true
+
+  '@parcel/watcher-win32-x64@2.5.0':
+    optional: true
+
+  '@parcel/watcher@2.5.0':
+    dependencies:
+      detect-libc: 1.0.3
+      is-glob: 4.0.3
+      micromatch: 4.0.8
+      node-addon-api: 7.1.1
+    optionalDependencies:
+      '@parcel/watcher-android-arm64': 2.5.0
+      '@parcel/watcher-darwin-arm64': 2.5.0
+      '@parcel/watcher-darwin-x64': 2.5.0
+      '@parcel/watcher-freebsd-x64': 2.5.0
+      '@parcel/watcher-linux-arm-glibc': 2.5.0
+      '@parcel/watcher-linux-arm-musl': 2.5.0
+      '@parcel/watcher-linux-arm64-glibc': 2.5.0
+      '@parcel/watcher-linux-arm64-musl': 2.5.0
+      '@parcel/watcher-linux-x64-glibc': 2.5.0
+      '@parcel/watcher-linux-x64-musl': 2.5.0
+      '@parcel/watcher-win32-arm64': 2.5.0
+      '@parcel/watcher-win32-ia32': 2.5.0
+      '@parcel/watcher-win32-x64': 2.5.0
+    optional: true
+
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
@@ -3143,7 +3331,7 @@ snapshots:
     dependencies:
       '@types/http-cache-semantics': 4.0.4
       '@types/keyv': 3.1.4
-      '@types/node': 20.17.6
+      '@types/node': 22.10.1
       '@types/responselike': 1.0.3
 
   '@types/debug@4.1.12':
@@ -3154,36 +3342,40 @@ snapshots:
 
   '@types/fs-extra@9.0.13':
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 22.10.1
 
   '@types/http-cache-semantics@4.0.4': {}
 
   '@types/keyv@3.1.4':
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 22.10.1
 
   '@types/ms@0.7.34': {}
 
-  '@types/node@20.17.6':
+  '@types/node@20.15.0':
     dependencies:
-      undici-types: 6.19.8
+      undici-types: 6.13.0
+
+  '@types/node@22.10.1':
+    dependencies:
+      undici-types: 6.20.0
 
   '@types/plist@3.0.5':
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 22.10.1
       xmlbuilder: 15.1.1
     optional: true
 
   '@types/responselike@1.0.3':
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 22.10.1
 
   '@types/verror@1.10.10':
     optional: true
 
   '@types/yauzl@2.10.3':
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 22.10.1
     optional: true
 
   '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)':
@@ -3210,7 +3402,7 @@ snapshots:
       '@typescript-eslint/types': 7.18.0
       '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
       '@typescript-eslint/visitor-keys': 7.18.0
-      debug: 4.3.7
+      debug: 4.4.0
       eslint: 8.57.1
     optionalDependencies:
       typescript: 5.6.3
@@ -3226,7 +3418,7 @@ snapshots:
     dependencies:
       '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
       '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3)
-      debug: 4.3.7
+      debug: 4.4.0
       eslint: 8.57.1
       ts-api-utils: 1.4.0(typescript@5.6.3)
     optionalDependencies:
@@ -3240,7 +3432,7 @@ snapshots:
     dependencies:
       '@typescript-eslint/types': 7.18.0
       '@typescript-eslint/visitor-keys': 7.18.0
-      debug: 4.3.7
+      debug: 4.4.0
       globby: 11.1.0
       is-glob: 4.0.3
       minimatch: 9.0.5
@@ -3269,13 +3461,13 @@ snapshots:
 
   '@ungap/structured-clone@1.2.0': {}
 
-  '@unocss/astro@0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))':
+  '@unocss/astro@0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))':
     dependencies:
       '@unocss/core': 0.64.1
       '@unocss/reset': 0.64.1
-      '@unocss/vite': 0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))
+      '@unocss/vite': 0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))
     optionalDependencies:
-      vite: 5.4.11(@types/node@20.17.6)
+      vite: 5.4.11(@types/node@20.15.0)(sass@1.81.0)
     transitivePeerDependencies:
       - rollup
       - supports-color
@@ -3408,7 +3600,7 @@ snapshots:
     dependencies:
       '@unocss/core': 0.64.1
 
-  '@unocss/vite@0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))':
+  '@unocss/vite@0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@rollup/pluginutils': 5.1.3(rollup@4.26.0)
@@ -3418,15 +3610,15 @@ snapshots:
       chokidar: 3.6.0
       magic-string: 0.30.12
       tinyglobby: 0.2.10
-      vite: 5.4.11(@types/node@20.17.6)
+      vite: 5.4.11(@types/node@20.15.0)(sass@1.81.0)
     transitivePeerDependencies:
       - rollup
       - supports-color
       - vue
 
-  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@20.17.6)
+      vite: 5.4.11(@types/node@20.15.0)(sass@1.81.0)
       vue: 3.5.12(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -3544,7 +3736,7 @@ snapshots:
 
   agent-base@6.0.2:
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
     transitivePeerDependencies:
       - supports-color
 
@@ -3591,7 +3783,7 @@ snapshots:
       builder-util: 24.13.1
       builder-util-runtime: 9.2.4
       chromium-pickle-js: 0.2.0
-      debug: 4.3.7
+      debug: 4.4.0
       dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
       ejs: 3.1.10
       electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3)
@@ -3722,14 +3914,14 @@ snapshots:
 
   builder-util-runtime@9.2.10:
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       sax: 1.4.1
     transitivePeerDependencies:
       - supports-color
 
   builder-util-runtime@9.2.4:
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       sax: 1.4.1
     transitivePeerDependencies:
       - supports-color
@@ -3743,7 +3935,7 @@ snapshots:
       builder-util-runtime: 9.2.4
       chalk: 4.1.2
       cross-spawn: 7.0.5
-      debug: 4.3.7
+      debug: 4.4.0
       fs-extra: 10.1.0
       http-proxy-agent: 5.0.0
       https-proxy-agent: 5.0.1
@@ -3795,6 +3987,10 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
+  chokidar@4.0.1:
+    dependencies:
+      readdirp: 4.0.2
+
   chownr@2.0.0: {}
 
   chromium-pickle-js@0.2.0: {}
@@ -3887,7 +4083,7 @@ snapshots:
 
   de-indent@1.0.2: {}
 
-  debug@4.3.7:
+  debug@4.4.0:
     dependencies:
       ms: 2.1.3
 
@@ -3919,6 +4115,9 @@ snapshots:
 
   destr@2.0.3: {}
 
+  detect-libc@1.0.3:
+    optional: true
+
   detect-node@2.1.0:
     optional: true
 
@@ -4027,7 +4226,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  electron-vite@2.3.0(vite@5.4.11(@types/node@20.17.6)):
+  electron-vite@2.3.0(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0)
@@ -4035,14 +4234,14 @@ snapshots:
       esbuild: 0.21.5
       magic-string: 0.30.12
       picocolors: 1.1.1
-      vite: 5.4.11(@types/node@20.17.6)
+      vite: 5.4.11(@types/node@20.15.0)(sass@1.81.0)
     transitivePeerDependencies:
       - supports-color
 
   electron@31.7.4:
     dependencies:
       '@electron/get': 2.0.3
-      '@types/node': 20.17.6
+      '@types/node': 20.15.0
       extract-zip: 2.0.1
     transitivePeerDependencies:
       - supports-color
@@ -4176,7 +4375,7 @@ snapshots:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.5
-      debug: 4.3.7
+      debug: 4.4.0
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -4228,7 +4427,7 @@ snapshots:
 
   extract-zip@2.0.1:
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       get-stream: 5.2.0
       yauzl: 2.10.0
     optionalDependencies:
@@ -4471,7 +4670,7 @@ snapshots:
     dependencies:
       '@tootallnate/once': 2.0.0
       agent-base: 6.0.2
-      debug: 4.3.7
+      debug: 4.4.0
     transitivePeerDependencies:
       - supports-color
 
@@ -4483,7 +4682,7 @@ snapshots:
   https-proxy-agent@5.0.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.7
+      debug: 4.4.0
     transitivePeerDependencies:
       - supports-color
 
@@ -4501,6 +4700,8 @@ snapshots:
 
   ignore@5.3.2: {}
 
+  immutable@5.0.2: {}
+
   import-fresh@3.3.0:
     dependencies:
       parent-module: 1.0.1
@@ -4509,7 +4710,7 @@ snapshots:
   importx@0.4.4:
     dependencies:
       bundle-require: 5.0.0(esbuild@0.21.5)
-      debug: 4.3.7
+      debug: 4.4.0
       esbuild: 0.21.5
       jiti: 2.0.0-beta.3
       jiti-v1: jiti@1.21.6
@@ -4749,6 +4950,9 @@ snapshots:
   node-addon-api@1.7.2:
     optional: true
 
+  node-addon-api@7.1.1:
+    optional: true
+
   node-fetch-native@1.6.4: {}
 
   node-releases@2.0.18: {}
@@ -4912,6 +5116,8 @@ snapshots:
     dependencies:
       picomatch: 2.3.1
 
+  readdirp@4.0.2: {}
+
   reflect-metadata@0.2.2: {}
 
   require-directory@2.1.1: {}
@@ -4982,6 +5188,14 @@ snapshots:
     dependencies:
       truncate-utf8-bytes: 1.0.2
 
+  sass@1.81.0:
+    dependencies:
+      chokidar: 4.0.1
+      immutable: 5.0.2
+      source-map-js: 1.2.1
+    optionalDependencies:
+      '@parcel/watcher': 2.5.0
+
   sax@1.4.1: {}
 
   semver-compare@1.0.0:
@@ -5074,7 +5288,7 @@ snapshots:
 
   sumchecker@3.0.1:
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
     transitivePeerDependencies:
       - supports-color
 
@@ -5170,15 +5384,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  undici-types@6.19.8: {}
+  undici-types@6.13.0: {}
+
+  undici-types@6.20.0: {}
 
   universalify@0.1.2: {}
 
   universalify@2.0.1: {}
 
-  unocss@0.64.1(postcss@8.4.49)(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3)):
+  unocss@0.64.1(postcss@8.4.49)(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3)):
     dependencies:
-      '@unocss/astro': 0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))
+      '@unocss/astro': 0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))
       '@unocss/cli': 0.64.1(rollup@4.26.0)
       '@unocss/core': 0.64.1
       '@unocss/postcss': 0.64.1(postcss@8.4.49)
@@ -5194,9 +5410,9 @@ snapshots:
       '@unocss/transformer-compile-class': 0.64.1
       '@unocss/transformer-directives': 0.64.1
       '@unocss/transformer-variant-group': 0.64.1
-      '@unocss/vite': 0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.17.6))(vue@3.5.12(typescript@5.6.3))
+      '@unocss/vite': 0.64.1(rollup@4.26.0)(vite@5.4.11(@types/node@20.15.0)(sass@1.81.0))(vue@3.5.12(typescript@5.6.3))
     optionalDependencies:
-      vite: 5.4.11(@types/node@20.17.6)
+      vite: 5.4.11(@types/node@20.15.0)(sass@1.81.0)
     transitivePeerDependencies:
       - postcss
       - rollup
@@ -5224,20 +5440,21 @@ snapshots:
       extsprintf: 1.4.1
     optional: true
 
-  vite@5.4.11(@types/node@20.17.6):
+  vite@5.4.11(@types/node@20.15.0)(sass@1.81.0):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
       rollup: 4.26.0
     optionalDependencies:
-      '@types/node': 20.17.6
+      '@types/node': 20.15.0
       fsevents: 2.3.3
+      sass: 1.81.0
 
   vscode-uri@3.0.8: {}
 
   vue-eslint-parser@9.4.3(eslint@8.57.1):
     dependencies:
-      debug: 4.3.7
+      debug: 4.4.0
       eslint: 8.57.1
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
diff --git a/resources/fuck.html b/resources/fuck.html
new file mode 100644
index 0000000..b941bb8
--- /dev/null
+++ b/resources/fuck.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    dsada
+    <a href="https://baidu.com" target="_blank">百度</a>
+</body>
+</html>
diff --git a/src/main/App copy.ts b/src/main/App copy.ts
new file mode 100644
index 0000000..bba0a02
--- /dev/null
+++ b/src/main/App copy.ts	
@@ -0,0 +1,165 @@
+import { inject, injectable } from "inversify"
+// import Setting from "./modules/setting"
+// import DB from "./modules/db"
+import Api from "./modules/api"
+import WindowManager from "./modules/window-manager"
+import { app, nativeTheme, protocol, WebContentsView } from "electron"
+import { electronApp } from "@electron-toolkit/utils"
+import Tabs from "./modules/tabs/Tabs"
+import { getFileUrl } from "./utils"
+import BaseClass from "./base/base"
+
+protocol.registerSchemesAsPrivileged([
+    // {
+    //     scheme: "http",
+    //     privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true },
+    // },
+    // {
+    //     scheme: "https",
+    //     privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true },
+    // },
+    // { scheme: "mailto", privileges: { standard: true } },
+    {
+        scheme: "api",
+        privileges: {
+            standard: true,
+            secure: true,
+            supportFetchAPI: true,
+        },
+    },
+])
+
+@injectable()
+class App extends BaseClass {
+    destroy() {
+        // destroyAll()
+        // 这里是应用正常退出
+    }
+    // private _setting: Setting
+    // private _db: DB
+    private _Api: Api
+    private _windowManager: WindowManager
+    private _tabs: Tabs
+
+    constructor(
+        // @inject(Setting) setting: Setting,
+        // @inject(DB) db: DB,
+        @inject(Api) Api: Api,
+        @inject(WindowManager) windowManager: WindowManager,
+        @inject(Tabs) tabs: Tabs,
+    ) {
+        super()
+        // this._setting = setting
+        // this._db = db
+        this._Api = Api
+        this._windowManager = windowManager
+        this._tabs = tabs
+    }
+
+    async init() {
+        this._windowManager.init()
+        app.whenReady().then(() => {
+            electronApp.setAppUserModelId("top.xieyaxin")
+            this.create()
+            this._Api.init()
+        })
+        app.on("window-all-closed", () => {
+            if (process.platform !== "darwin") {
+                app.quit()
+            }
+        })
+        app.on("will-quit", () => {
+            this.destroy()
+        })
+    }
+
+    create() {
+        this._windowManager.showMainWindow()
+        const mainWindow = this._windowManager.getMainWindow()
+        if (mainWindow) {
+            nativeTheme.themeSource = "light"
+            mainWindow.setTitleBarOverlay({
+                height: 29, // the smallest size of the title bar on windows accounting for the border on windows 11
+                color: "#F8F8F8",
+                symbolColor: "#000000",
+            })
+            this._windowManager.showWindow("main-top")
+            const mainTopWindow = this._windowManager.get("main-top")
+            setTimeout(() => {
+                // console.log(mainWindow.getParentWindow());
+                setTimeout(() => {
+                    mainWindow.contentView.children.length = 0
+                    const view = new WebContentsView()
+                    view.addChildView(mainTopWindow!.contentView)
+                    view.webContents.loadURL(getFileUrl("about.html"))
+                    // mainTopWindow!.contentView.setBounds({ x: 0, y: 0, width: 100, height: 30 })
+                    // view.setBounds({ x: 0, y: 0, width: 100, height: 30 })
+                    mainWindow.contentView.addChildView(view)
+                    // mainWindow.contentView.children.sort()
+                    console.log(mainWindow.contentView.children)
+                }, 5000)
+                // mainWindow.webContents = mainTopWindow!.webContents
+                mainWindow.reload()
+                console.log(mainWindow.webContents.getURL())
+
+                // mainTopWindow?.destroy()
+                // mainWindow.contentView.addChildView(mainWindow.contentView)
+                console.log(`child count: `, mainWindow.contentView.children.length)
+            }, 2000)
+            // if (mainTopWindow) {
+            //     mainTopWindow.setParentWindow(mainWindow)
+            //     mainTopWindow.setIgnoreMouseEvents(true, { forward: false })
+            //     const listenMove = () => {
+            //         if (mainWindow && mainTopWindow) {
+            //             const pos = mainWindow.getPosition()
+            //             mainTopWindow.setPosition(pos[0], pos[1])
+            //         }
+            //     }
+            //     mainWindow?.on("move", listenMove)
+            //     const listenResize = () => {
+            //         if (mainWindow && mainTopWindow) {
+            //             const size = mainWindow.getSize()
+            //             console.log(size)
+            //             mainTopWindow.setSize(size[0], size[1])
+            //             const pos = mainWindow.getPosition()
+            //             mainTopWindow.setPosition(pos[0], pos[1])
+            //         }
+            //     }
+            //     listenResize()
+            //     mainWindow?.on("resize", listenResize)
+            // }
+        }
+        // 考虑双browserwindow模式
+        /**
+         * 因为browserwindow可以设置穿透,考虑将tab放在底层window上,其他组件放在上层window上。
+         */
+        // const webContentsView = new WebContentsView({
+        //     webPreferences: {
+        //         preload: join(__dirname, "../preload/index.mjs"),
+        //         transparent: true,
+        //         nodeIntegration: true,
+        //         spellcheck: false,
+        //         contextIsolation: true,
+        //     },
+        // })
+        // // mainWindow!.contentView = webContentsView
+        // // setTimeout(() => {
+        // mainWindow!.contentView.addChildView(webContentsView)
+        // // mainWindow?.setIgnoreMouseEvents(true, { forward: true })
+        // // }, 2000);
+        // webContentsView.webContents.loadURL(getFileUrl("index.html"))
+        // const listenResize = () => {
+        //     const size = mainWindow!.getSize()
+        //     webContentsView.setBounds({ x: 0, y: 0, width: size[0], height: size[1] })
+        // }
+        // listenResize()
+        // mainWindow!.addListener("resize", listenResize)
+
+        this._tabs.add("https://baidu.com", true)
+        this._tabs.add("https://zhihu.com")
+        return mainWindow
+    }
+}
+
+export default App
+export { App }
diff --git a/src/main/App.ts b/src/main/App.ts
index acde0e4..65a3d93 100644
--- a/src/main/App.ts
+++ b/src/main/App.ts
@@ -1,22 +1,77 @@
-import { inject } from "inversify"
-import Setting from "./modules/setting"
+import { inject, injectable } from "inversify"
+// import Setting from "./modules/setting"
+// import DB from "./modules/db"
+import Api from "./modules/api"
+import WindowManager from "./modules/window-manager"
+import { app, nativeTheme, protocol } from "electron"
+import { electronApp } from "@electron-toolkit/utils"
+import Tabs from "./modules/tabs"
+import Command from "./modules/commands"
+import BaseClass from "./base/base"
+import IOC from "./_ioc"
 import DB from "./modules/db"
 
-class App {
-    private _setting: Setting
-    private _db: DB
+protocol.registerSchemesAsPrivileged([
+    // {
+    //     scheme: "http",
+    //     privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true },
+    // },
+    // {
+    //     scheme: "https",
+    //     privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true },
+    // },
+    // { scheme: "mailto", privileges: { standard: true } },
+    {
+        scheme: "api",
+        privileges: {
+            standard: true,
+            secure: true,
+            supportFetchAPI: true,
+        },
+    },
+])
 
-    constructor(@inject(Setting) setting: Setting, @inject(DB) db: DB) {
-        console.log(`App inited`)
+@injectable()
+class App extends BaseClass {
+    destroy() {
+        this._IOC.destroy()
+        // 这里是应用正常退出, 可以检测应用是不是非正常退出,比如应用启动时记录一个启动时间并删除上一次结束时间和开始时间,结束时记录一个结束时间,
+        // 如果存在结束时间或者不存在开始时间则为正常启动
+    }
 
-        this._setting = setting
-        this._db = db
+    constructor(
+        @inject(IOC) private _IOC: IOC,
+        @inject(Api) private _Api: Api,
+        @inject(Command) private _Command: Command,
+        @inject(DB) private _DB: DB,
+        @inject(WindowManager) private _WindowManager: WindowManager,
+        @inject(Tabs) private _Tabs: Tabs,
+    ) {
+        super()
     }
 
     async init() {
-        console.log(this._setting.config())
-        this._db.saveData("aaa", { a: 123123 })
-        console.log(await this._db.getData("aaa"))
+        this._DB.init()
+        this._Command.init()
+        this._WindowManager.init()
+        app.whenReady().then(() => {
+            this._Api.init()
+            electronApp.setAppUserModelId("top.xieyaxin")
+            this._WindowManager.showMainWindow()
+            const mainWindow = this._WindowManager.getMainWindow()
+            this._Tabs.init(mainWindow)
+            if (mainWindow) {
+                nativeTheme.themeSource = "light"
+                mainWindow.setTitleBarOverlay({
+                    height: 29, // the smallest size of the title bar on windows accounting for the border on windows 11
+                    color: "#F8F8F8",
+                    symbolColor: "#000000",
+                })
+            }
+        })
+        app.on("will-quit", () => {
+            this.destroy()
+        })
     }
 }
 
diff --git a/src/main/_ioc.ts b/src/main/_ioc.ts
new file mode 100644
index 0000000..9f99c99
--- /dev/null
+++ b/src/main/_ioc.ts
@@ -0,0 +1,28 @@
+import IOC from "./_iocClass"
+import { Container } from "inversify"
+import iocModules, { destroyAllModules } from "./modules/_ioc"
+import iocController, { destroyAllController } from "./controller/_ioc"
+import iocCommand, { destroyAllCommand } from "./commands/_ioc"
+import App from "./App"
+
+async function destroyAll() {
+    await destroyAllModules(_ioc)
+    await destroyAllController(_ioc)
+    await destroyAllCommand(_ioc)
+}
+
+const _modulesIOC = new Container()
+_modulesIOC.load(iocModules)
+
+const _commandIOC = _modulesIOC.createChild()
+_commandIOC.load(iocCommand)
+
+const _controllerIOC = _commandIOC.createChild()
+_controllerIOC.load(iocController)
+
+const _ioc = _controllerIOC.createChild()
+_ioc.bind(IOC).toSelf().inSingletonScope()
+_ioc.bind(App).toSelf().inSingletonScope()
+
+export { IOC, destroyAll, _ioc }
+export default IOC
diff --git a/src/main/_iocClass.ts b/src/main/_iocClass.ts
new file mode 100644
index 0000000..ecd6e0a
--- /dev/null
+++ b/src/main/_iocClass.ts
@@ -0,0 +1,20 @@
+import { interfaces } from "inversify"
+import BaseClass from "./base/base"
+import { destroyAll, _ioc } from "./_ioc"
+
+class IOC extends BaseClass {
+    destroy() {
+        destroyAll()
+    }
+
+    get<T = unknown>(serviceIdentifier: interfaces.ServiceIdentifier<T>) {
+        return _ioc.get<T>(serviceIdentifier)
+    }
+
+    getAsync<T = unknown>(serviceIdentifier: interfaces.ServiceIdentifier<T>) {
+        return _ioc.getAsync<T>(serviceIdentifier)
+    }
+}
+
+export { IOC }
+export default IOC
diff --git a/src/main/base/base.ts b/src/main/base/base.ts
index 8e1c804..0976ae4 100644
--- a/src/main/base/base.ts
+++ b/src/main/base/base.ts
@@ -1,4 +1,10 @@
-abstract class Base {}
+import EventEmitter from "node:events"
 
-export { Base }
-export default Base
+abstract class BaseClass {
+    public _events = new EventEmitter()
+    abstract init(...argus: any[])
+    abstract destroy()
+}
+
+export { BaseClass }
+export default BaseClass
diff --git a/src/main/base/baseContainer.ts b/src/main/base/baseContainer.ts
new file mode 100644
index 0000000..840cf1e
--- /dev/null
+++ b/src/main/base/baseContainer.ts
@@ -0,0 +1,4 @@
+abstract class BaseContainer {}
+
+export { BaseContainer }
+export default BaseContainer
diff --git a/src/main/commands/BasicCommand.ts b/src/main/commands/BasicCommand.ts
new file mode 100644
index 0000000..a42fcdd
--- /dev/null
+++ b/src/main/commands/BasicCommand.ts
@@ -0,0 +1,7 @@
+export default class BasicCommand {
+    static name: string = "BasicCommand"
+
+    log() {
+        console.log("1231")
+    }
+}
diff --git a/src/main/commands/TabsCommand.ts b/src/main/commands/TabsCommand.ts
new file mode 100644
index 0000000..04d552f
--- /dev/null
+++ b/src/main/commands/TabsCommand.ts
@@ -0,0 +1,59 @@
+import { inject } from "inversify"
+import Tabs from "vc/modules/tabs"
+import WindowManager from "vc/modules/window-manager"
+import { broadcast } from "vc/utils"
+
+class TabsCommand {
+    static name: string = "TabsCommand"
+
+    constructor(
+        @inject(Tabs) private _Tabs: Tabs,
+        @inject(WindowManager) private _WindowManager: WindowManager,
+    ) {
+        this.listenerTabActive = this.listenerTabActive.bind(this)
+        this._Tabs.events.addListener("tab-active", this.listenerTabActive)
+    }
+
+    reload() {
+        this._WindowManager.getMainWindow()?.reload()
+    }
+
+    sync() {
+        this.listenerTabActive()
+    }
+
+    listenerTabActive() {
+        broadcast("TabsCommand.update", this.getAllTabs())
+    }
+
+    add(url) {
+        this._Tabs.add(url, true, this._WindowManager.getMainWindow()!)
+    }
+
+    nagivate(index: number, url: string) {
+        console.log(`跳转${index}:${url}`)
+
+        this._Tabs.navigate(+index, url)
+    }
+
+    setActive(index) {
+        this._Tabs.changeActive(index)
+    }
+
+    closeTab(e) {
+        this._Tabs.remove(e.body.active)
+    }
+
+    getAllTabs() {
+        return this._Tabs._tabs.map(v => ({
+            url: v.url,
+            showUrl: v.showUrl,
+            title: v.title,
+            favicons: v.favicons,
+            isActive: v.isActive,
+        }))
+    }
+}
+
+export { TabsCommand }
+export default TabsCommand
diff --git a/src/main/commands/_ioc.ts b/src/main/commands/_ioc.ts
new file mode 100644
index 0000000..0e5c874
--- /dev/null
+++ b/src/main/commands/_ioc.ts
@@ -0,0 +1,15 @@
+import { Container, ContainerModule } from "inversify"
+import BasicCommand from "./BasicCommand"
+import TabsCommand from "./TabsCommand"
+
+const modules = new ContainerModule(bind => {
+    bind(BasicCommand.name).to(BasicCommand).inSingletonScope()
+    bind(TabsCommand.name).to(TabsCommand).inSingletonScope()
+})
+
+async function destroyAllCommand(ioc: Container) {
+    await ioc.unloadAsync(modules)
+}
+
+export { modules, destroyAllCommand }
+export default modules
diff --git a/src/main/controller/BasicService.ts b/src/main/controller/BasicService.ts
new file mode 100644
index 0000000..38addf0
--- /dev/null
+++ b/src/main/controller/BasicService.ts
@@ -0,0 +1,31 @@
+import { inject, injectable } from "inversify"
+import BaseContainer from "vc/base/baseContainer"
+import Tabs from "vc/modules/tabs"
+import WindowManager from "vc/modules/window-manager"
+
+@injectable()
+class BasicService extends BaseContainer {
+    static name: string = "BasicService"
+
+    constructor(
+        @inject(WindowManager) private _WindowManager: WindowManager,
+        @inject(Tabs) private _Tabs: Tabs,
+    ) {
+        super()
+    }
+
+    showAbout() {
+        this._WindowManager.showWindow("about")
+        return {
+            a: "fuck",
+        }
+    }
+
+    openTabDevtool() {
+        // this._Tabs.reload(0)
+        this._Tabs.openDevtool(0)
+    }
+}
+
+export { BasicService }
+export default BasicService
diff --git a/src/main/controller/TabsService.ts b/src/main/controller/TabsService.ts
new file mode 100644
index 0000000..cd9e378
--- /dev/null
+++ b/src/main/controller/TabsService.ts
@@ -0,0 +1,45 @@
+import { inject, injectable } from "inversify"
+import BaseContainer from "vc/base/baseContainer"
+import Tabs from "vc/modules/tabs"
+import WindowManager from "vc/modules/window-manager"
+
+@injectable()
+class TabsService extends BaseContainer {
+    static name: string = "TabsService"
+
+    constructor(
+        @inject(Tabs) private _Tabs: Tabs,
+        @inject(WindowManager) private _WindowManager: WindowManager,
+    ) {
+        super()
+    }
+
+    add(e) {
+        this._Tabs.add(e.body.url, true, this._WindowManager.getMainWindow()!)
+    }
+
+    setActive(e) {
+        this._Tabs.changeActive(e.body.active)
+    }
+
+    closeTab(e) {
+        this._Tabs.remove(e.body.active)
+    }
+
+    closeTabAll(e) {
+        this._Tabs.removeAll(e.body.active)
+    }
+
+    getAllTabs() {
+        return this._Tabs._tabs.map(v => ({
+            url: v.url,
+            showUrl: v.showUrl,
+            title: v.title,
+            favicons: v.favicons,
+            isActive: v.isActive,
+        }))
+    }
+}
+
+export { TabsService }
+export default TabsService
diff --git a/src/main/controller/_ioc.ts b/src/main/controller/_ioc.ts
new file mode 100644
index 0000000..a8b0517
--- /dev/null
+++ b/src/main/controller/_ioc.ts
@@ -0,0 +1,15 @@
+import { Container, ContainerModule } from "inversify"
+import BasicService from "./BasicService"
+import TabsService from "./TabsService"
+
+const modules = new ContainerModule(bind => {
+    bind(BasicService.name).to(BasicService).inSingletonScope()
+    bind(TabsService.name).to(TabsService).inSingletonScope()
+})
+
+async function destroyAllController(ioc: Container) {
+    await ioc.unloadAsync(modules)
+}
+
+export { modules, destroyAllController }
+export default modules
diff --git a/src/main/index.ts b/src/main/index.ts
index 34beb5b..92794b5 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -1,79 +1,6 @@
 import "reflect-metadata"
-import { app, shell, BrowserWindow, ipcMain } from "electron"
-import { join } from "path"
-import { electronApp, optimizer, is } from "@electron-toolkit/utils"
-import icon from "res/icon.png?asset"
-import { container } from "vc/modules/ioc"
+import { _ioc } from "vc/_ioc"
 import { App } from "vc/App"
 
-container.get(App).init()
-
-function createWindow(): void {
-    // Create the browser window.
-    const mainWindow = new BrowserWindow({
-        width: 900,
-        height: 670,
-        show: false,
-        autoHideMenuBar: true,
-        ...(process.platform === "linux" ? { icon } : {}),
-        webPreferences: {
-            preload: join(__dirname, "../preload/index.mjs"),
-            sandbox: false,
-        },
-    })
-
-    mainWindow.on("ready-to-show", () => {
-        mainWindow.show()
-    })
-
-    mainWindow.webContents.setWindowOpenHandler(details => {
-        shell.openExternal(details.url)
-        return { action: "deny" }
-    })
-
-    // HMR for renderer base on electron-vite cli.
-    // Load the remote URL for development or the local html file for production.
-    if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
-        mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"])
-    } else {
-        mainWindow.loadFile(join(__dirname, "../renderer/index.html"))
-    }
-}
-
-// This method will be called when Electron has finished
-// initialization and is ready to create browser windows.
-// Some APIs can only be used after this event occurs.
-app.whenReady().then(() => {
-    // Set app user model id for windows
-    electronApp.setAppUserModelId("com.electron")
-
-    // Default open or close DevTools by F12 in development
-    // and ignore CommandOrControl + R in production.
-    // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
-    app.on("browser-window-created", (_, window) => {
-        optimizer.watchWindowShortcuts(window)
-    })
-
-    // IPC test
-    ipcMain.on("ping", () => console.log(icon))
-
-    createWindow()
-
-    app.on("activate", function () {
-        // On macOS it's common to re-create a window in the app when the
-        // dock icon is clicked and there are no other windows open.
-        if (BrowserWindow.getAllWindows().length === 0) createWindow()
-    })
-})
-
-// Quit when all windows are closed, except on macOS. There, it's common
-// for applications and their menu bar to stay active until the user quits
-// explicitly with Cmd + Q.
-app.on("window-all-closed", () => {
-    if (process.platform !== "darwin") {
-        app.quit()
-    }
-})
-
-// In this file you can include the rest of your app"s specific main process
-// code. You can also put them in separate files and require them here.
+const curApp = _ioc.get(App)
+curApp.init()
diff --git a/src/main/modules/_ioc.ts b/src/main/modules/_ioc.ts
new file mode 100644
index 0000000..1c6cbc5
--- /dev/null
+++ b/src/main/modules/_ioc.ts
@@ -0,0 +1,31 @@
+import { Container, ContainerModule } from "inversify"
+import { Setting } from "./setting"
+import { DB } from "./db"
+import { Api } from "./api"
+import { WindowManager } from "./window-manager"
+import { Tabs } from "./tabs"
+import Commands from "./commands"
+
+const modules = new ContainerModule(bind => {
+    bind(Setting).toConstantValue(new Setting())
+    bind(Api).toSelf().inSingletonScope()
+    bind(WindowManager).toSelf().inSingletonScope()
+    bind(Commands).toSelf().inSingletonScope()
+    bind(Tabs).toSelf().inSingletonScope()
+    bind(DB).toSelf().inSingletonScope()
+})
+
+async function destroyAllModules(ioc: Container) {
+    await Promise.all([
+        ioc.get(Setting).destroy(),
+        ioc.get(WindowManager).destroy(),
+        ioc.get(Commands).destroy(),
+        ioc.get(Tabs).destroy(),
+        ioc.get(Api).destroy(),
+        ioc.get(DB).destroy(),
+    ])
+    ioc.unloadAsync(modules)
+}
+
+export default modules
+export { modules, destroyAllModules }
diff --git a/src/main/modules/api/index.ts b/src/main/modules/api/index.ts
new file mode 100644
index 0000000..e4105dd
--- /dev/null
+++ b/src/main/modules/api/index.ts
@@ -0,0 +1,90 @@
+import { session, net } from "electron"
+import { inject, injectable } from "inversify"
+import IOC from "vc/_ioc"
+import BaseClass from "vc/base/base"
+
+@injectable()
+class Api extends BaseClass {
+    constructor(@inject(IOC) private _IOC: IOC) {
+        super()
+        this.interceptHandler = this.interceptHandler.bind(this)
+    }
+
+    destroy() {
+        // TODO
+    }
+    init(partition?: string) {
+        // const ses = partition ? session.fromPartition(partition) : session.defaultSession
+        const ses = partition ? session.fromPartition(partition) : session.defaultSession
+        ses.protocol.handle("api", this.interceptHandler)
+    }
+    async interceptHandler(request: Request) {
+        if (request.url.startsWith("api://fuck/")) {
+            let curUrl = request.url
+            const isScriteText = curUrl.endsWith("?script")
+            if (isScriteText) {
+                curUrl = curUrl.replace("?script", "")
+            }
+            const isPost = request.method.toLowerCase() === "post"
+            const file = curUrl.replace("api://fuck/", "")
+            const array = file.split("/")
+            const routePath = array.slice(0, -1).join("/")
+            const fnName = array[array.length - 1]
+            // https://vitejs.cn/vite5-cn/guide/features.html#dynamic-import
+            const module = await this._IOC.getAsync(routePath)
+            // const module = await import(`vc/controller/${routePath}.ts`)
+            const opts = { body: {}, query: {} }
+            if (isPost) {
+                opts.body = await request.json()
+            }
+            const headers: HeadersInit = {}
+            if (isScriteText) {
+                headers["content-type"] = "text/javascript"
+            }
+            if (isPost) {
+                headers["content-type"] = "application/json"
+            }
+            if (module && module[fnName]) {
+                if (typeof module[fnName] === "string") {
+                    const result = module[fnName]
+                    return new Response(result, {
+                        status: 200,
+                        headers: Object.keys(headers).length ? headers : undefined,
+                    })
+                }
+                if (typeof module[fnName] === "function") {
+                    let result = await module[fnName](opts)
+                    if (typeof result === "object") {
+                        result = JSON.stringify(result)
+                    }
+                    return new Response(result, {
+                        status: 200,
+                        headers: Object.keys(headers).length ? headers : undefined,
+                    })
+                }
+                if (typeof module[fnName] === "object") {
+                    let result = module[fnName]
+                    if (typeof result === "object") {
+                        result = JSON.stringify(result)
+                    }
+                    return new Response(result, {
+                        status: 200,
+                        headers: Object.keys(headers).length ? headers : undefined,
+                    })
+                }
+            }
+            return new Response("", {
+                status: 500,
+                headers: Object.keys(headers).length ? headers : undefined,
+            })
+        } else if (request.url.startsWith("api://")) {
+            return new Response("error", {
+                status: 500,
+            })
+        }
+        return net.fetch(request.url, request)
+    }
+}
+
+export default Api
+export { Api }
diff --git a/src/main/modules/api/readme.md b/src/main/modules/api/readme.md
new file mode 100644
index 0000000..2fac3e0
--- /dev/null
+++ b/src/main/modules/api/readme.md
@@ -0,0 +1,6 @@
+
+## 资源
+
+- https://juejin.cn/post/7311619723317657611#heading-6
+- https://juejin.cn/post/7208108117836873784
+- https://www.electronjs.org/zh/docs/latest/api/protocol#protocolregisterschemesasprivilegedcustomschemes
diff --git a/src/main/modules/api/test.ts b/src/main/modules/api/test.ts
new file mode 100644
index 0000000..4ce835e
--- /dev/null
+++ b/src/main/modules/api/test.ts
@@ -0,0 +1,72 @@
+// import https from "node:https"
+
+// const interceptRequestRemote = async (request, callback) => {
+//     const client = https.request(request.url, {
+//         method: request.method,
+//         headers: { ...request.headers },
+//     });
+//     if (request.uploadData) {
+//         for (const data of request.uploadData) {
+//             if (data.type === "rawData") {
+//                 // 直接创建Buffer对象
+//                 client.write(data.bytes);
+//                 // buffers.push(Buffer.from(data.bytes));
+//             } else if (data.type === "blob") {
+//                 // 通过blobUUID获取Buffer对象
+//                 const buffer = await sess.getBlobData(data.blobUUID);
+//                 client.write(buffer);
+//             }
+//         }
+//     }
+//     client.on("error", (err) => {
+//         console.error(`sess request error: ${request.url}`, err);
+//     });
+//     client.on("response", (response) => {
+//         let body = [];
+//         response.on("error", (err) => {
+//             console.error(`sess request response error: ${request.url}`, err);
+//         });
+//         response.on("data", (chunk) => {
+//             body.push(chunk);
+//         });
+//         response.on("end", () => {
+//             body = Buffer.concat(body);
+//             callback({
+//                 statusCode: response.statusCode,
+//                 headers: response.headers,
+//                 data: body,
+//             });
+//         });
+//     });
+//     console.log(`sess request: ${request.url}`);
+//     client.end();
+// };
+
+// const interceptHandler = (request, callback) => {
+//     const localPath = checkIsNeedLocal(request);
+//     if (localPath) {
+//         fs.readFile(localPath, (err, data) => {
+//             if (err) {
+//                 console.error("readFile error", err);
+//                 interceptRequestRemote(request, callback);
+//                 return;
+//             }
+//             const ext = path.extname(localPath);
+//             const mimeType =
+//                 ext === ".js"
+//                     ? "application/javascript"
+//                     : ext === ".css"
+//                         ? "text/css"
+//                         : "text/html";
+//             callback({
+//                 data,
+//                 mimeType,
+//             });
+//         });
+//     } else {
+//         interceptRequestRemote(request, callback);
+//     }
+// };
+
+// ses.protocol.interceptBufferProtocol("https", interceptHandler);
+
diff --git a/src/main/modules/commands/index.ts b/src/main/modules/commands/index.ts
new file mode 100644
index 0000000..33ccc5d
--- /dev/null
+++ b/src/main/modules/commands/index.ts
@@ -0,0 +1,93 @@
+import { IMenuItemOption, IPopupMenuOption } from "#"
+import { ipcMain, Menu, MenuItem } from "electron"
+import { inject } from "inversify"
+import IOC from "vc/_ioc"
+import BaseClass from "vc/base/base"
+import { isPromise } from "vc/utils"
+import WindowManager from "../window-manager"
+
+export default class Commands extends BaseClass {
+    destroy() {
+        // TODO
+    }
+
+    constructor(
+        @inject(IOC) private _IOC: IOC,
+        @inject(WindowManager) private _WindowManager: WindowManager,
+    ) {
+        super()
+    }
+
+    init() {
+        ipcMain.addListener("command", async (event, key, command: string, ...argus) => {
+            // console.log(event.sender);
+            try {
+                const splitClass = command.split(".")
+                const run = await this._IOC.getAsync<any>(splitClass[0])
+                if (run) {
+                    const result: Promise<any> | any = run[splitClass[1]](...argus)
+                    if (isPromise(result)) {
+                        result
+                            .then((res: any) => {
+                                event.reply(key, null, res ?? null)
+                                event.returnValue = res ?? null
+                            })
+                            .catch((err: Error) => {
+                                event.reply(key, err)
+                                event.returnValue = null
+                            })
+                    } else {
+                        event.reply(key, null, result ?? null)
+                        event.returnValue = result ?? null
+                    }
+                } else {
+                    event.reply(key, new Error(`不存在该命令:${command}`))
+                    event.returnValue = null
+                }
+            } catch (error) {
+                event.reply(key, error)
+                event.returnValue = null
+            }
+        })
+
+        ipcMain.on("x_popup_menu", (_, name: string, options: IPopupMenuOption) => {
+            const menu = new Menu()
+            const readMenu = (items: IMenuItemOption[]) => {
+                return items.map(opt => {
+                    if (typeof opt._click_evt === "string") {
+                        const evt: string = opt._click_evt
+                        opt.click = () => {
+                            // broadcast(evt)
+                            this.sendMessage(name, evt)
+                        }
+                    }
+                    if (opt.submenu && Array.isArray(opt.submenu)) {
+                        opt.submenu = readMenu(opt.submenu)
+                    }
+
+                    return opt
+                })
+            }
+            const arrays = readMenu(options.items)
+
+            arrays.forEach(v => {
+                const item = new MenuItem(v)
+                menu.append(item)
+            })
+
+            menu.on("menu-will-close", () => {
+                this.sendMessage(name, `popup_menu_close:${options.menu_id}`)
+                // broadcast(`popup_menu_close:${options.menu_id}`)
+            })
+
+            menu.popup()
+        })
+    }
+
+    sendMessage(name: string, evt: string, ...argu: any[]) {
+        const win = this._WindowManager.get(name)
+        if (win) {
+            win.webContents.send(evt, ...argu)
+        }
+    }
+}
diff --git a/src/main/modules/db/index.ts b/src/main/modules/db/index.ts
index f764847..80d071e 100644
--- a/src/main/modules/db/index.ts
+++ b/src/main/modules/db/index.ts
@@ -2,16 +2,24 @@ import { inject, injectable } from "inversify"
 import Setting from "../setting"
 import { CustomAdapter, CustomLow } from "./custom"
 import path from "node:path"
+import BaseClass from "vc/base/base"
+import _debug from "debug"
+
+const debug = _debug("app:db")
 
 @injectable()
-class DB {
-    private _setting: Setting
+class DB extends BaseClass {
+    destroy() {
+        debug(`DB destroy`)
+    }
     Modules: Record<string, CustomLow<any>> = {}
 
-    constructor(@inject(Setting) setting: Setting) {
-        console.log(`DB inited`)
+    constructor(@inject(Setting) private _setting: Setting) {
+        super()
+    }
 
-        this._setting = setting
+    init() {
+        console.log("DB Init")
     }
 
     create(filepath) {
diff --git a/src/main/modules/ioc.ts b/src/main/modules/ioc.ts
deleted file mode 100644
index b769180..0000000
--- a/src/main/modules/ioc.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Container } from "inversify"
-import { ContainerModule } from "inversify"
-import { Setting } from "./setting"
-import { DB } from "./db"
-import App from "../App"
-
-const module = new ContainerModule(bind => {
-    bind(Setting).toConstantValue(new Setting())
-    bind(DB).toSelf().inSingletonScope()
-    bind(App).toSelf().inSingletonScope()
-})
-
-const container = new Container()
-
-container.load(module)
-
-export default container
-export { container }
diff --git a/src/main/modules/setting/index.ts b/src/main/modules/setting/index.ts
index 915dd0c..d35d4c7 100644
--- a/src/main/modules/setting/index.ts
+++ b/src/main/modules/setting/index.ts
@@ -4,6 +4,10 @@ import path from "path"
 import { cloneDeep } from "lodash"
 import { injectable } from "inversify"
 import Config from "config"
+import _debug from "debug"
+import BaseClass from "vc/base/base"
+
+const debug = _debug("app:setting")
 
 type IConfig = typeof Config.default_config
 
@@ -55,11 +59,17 @@ function isEmptyDir(fPath: string) {
 }
 
 @injectable()
-class Setting {
+class Setting extends BaseClass {
     constructor() {
-        console.log(`Setting inited`)
-        this.#init()
+        super()
+        debug(`Setting inited`)
+        this.init()
+    }
+
+    destroy() {
+        // TODO
     }
+
     #cb: [IT, IOnFunc][] = []
 
     onChange(fn: IOnFunc, that?: any)
@@ -118,8 +128,9 @@ class Setting {
             this.#config[key] = config[key] || this.#config[key]
         }
     }
-    #init() {
-        console.log(`位置:${this.#pathFile}`)
+    init() {
+        debug(`位置:${this.#pathFile}`)
+
         if (fs.pathExistsSync(this.#pathFile)) {
             const confingPath = fs.readFileSync(this.#pathFile, { encoding: "utf8" })
             if (confingPath && fs.pathExistsSync(this.#configPath(confingPath))) {
@@ -152,7 +163,6 @@ class Setting {
             fs.moveSync(storagePath, p)
         }
         if (fs.existsSync(p) && fs.existsSync(storagePath) && isEmptyDir(p)) {
-            console.log("文件夹为空,直接覆盖")
             fs.moveSync(storagePath, p, { overwrite: true })
         }
         fs.writeFileSync(this.#pathFile, p, { encoding: "utf8" })
diff --git a/src/main/modules/tabs/Constant.ts b/src/main/modules/tabs/Constant.ts
new file mode 100644
index 0000000..07b51f9
--- /dev/null
+++ b/src/main/modules/tabs/Constant.ts
@@ -0,0 +1,6 @@
+export function Layout(width, height) {
+    // Tab布局位置
+    const NavbarHeight = 30
+    const OffsetHeight = NavbarHeight + 100
+    return { x: 0, y: OffsetHeight, width: width, height: height - OffsetHeight }
+}
diff --git a/src/main/modules/tabs/Tab.ts b/src/main/modules/tabs/Tab.ts
new file mode 100644
index 0000000..cf4bb06
--- /dev/null
+++ b/src/main/modules/tabs/Tab.ts
@@ -0,0 +1,261 @@
+import { BrowserWindow, WebContentsView, WebPreferences } from "electron"
+import { join } from "node:path"
+import BaseClass from "vc/base/base"
+import _debug from "debug"
+import { Layout } from "./Constant"
+import FuckHTML from "res/fuck.html?asset"
+import { fileURLToPath } from "node:url"
+
+const debug = _debug("app:tab")
+
+interface IOption {
+    url: string
+    active: boolean
+}
+
+class Tab extends BaseClass {
+    init() {
+        // TODO
+    }
+    public url: string = ""
+    public showUrl: string = ""
+    public title: string = ""
+    public favicons: string[] = []
+    public active: boolean = false
+    public alive: boolean = false
+    public isDestory: boolean = false
+    public playing: boolean = false
+    public visible: boolean = false
+    private webContentsView: WebContentsView | null = null
+    private curWindow: BrowserWindow | null = null
+
+    private defaultOptions: IOption = {
+        url: "",
+        active: false,
+    }
+
+    private options: IOption
+
+    get isActive() {
+        return this.active
+    }
+
+    get events() {
+        return this._events
+    }
+
+    constructor(options = {}, window: BrowserWindow) {
+        super()
+        this.listenResize = this.listenResize.bind(this)
+        this.options = {
+            ...this.defaultOptions,
+            ...options,
+        }
+        this.url = this.getUrl(this.options.url)
+        this.showUrl = this.options.url
+        this.curWindow = window
+        this.setActive(this.options.active)
+    }
+    destroyTimer: NodeJS.Timeout | null = null
+    stopDestroyTimer() {
+        if (this.destroyTimer !== null) {
+            clearTimeout(this.destroyTimer)
+            this.destroyTimer = null
+        }
+    }
+    startDestroyTimer() {
+        this.stopDestroyTimer()
+        if (this.visible) return
+        if (this.playing) return
+        this.destroyTimer = setTimeout(() => {
+            if (this.webContentsView && !this.webContentsView.webContents.isDestroyed()) {
+                this.curWindow?.contentView.removeChildView(this.webContentsView!)
+                // @ts-ignore 超过8s没有激活的tab就销毁
+                this.webContentsView.webContents.destroy()
+                this.webContentsView = null
+                this.alive = false
+            }
+        }, 8000)
+    }
+
+    setActive(active: boolean) {
+        if (!active) {
+            if (!this.webContentsView) return
+            this.curWindow!.removeListener("resize", this.listenResize)
+            this.webContentsView.setVisible(false)
+            this.visible = false
+            this.startDestroyTimer()
+        } else {
+            this.stopDestroyTimer()
+            this.visible = true
+            if (!this.webContentsView) {
+                this.create()
+                // , this.curWindow!.contentView.children.length - 1
+                this.curWindow!.contentView.addChildView(this.webContentsView!)
+                this.alive = true
+            }
+            this.listenResize()
+            this.curWindow!.addListener("resize", this.listenResize)
+            this.webContentsView!.setVisible(true)
+        }
+        this.active = active
+    }
+
+    openDevtool() {
+        if (!this.webContentsView) return
+        this.webContentsView.webContents.openDevTools({
+            mode: "right",
+        })
+    }
+
+    reload() {
+        if (!this.webContentsView) return
+        this.webContentsView.webContents.reload()
+    }
+
+    create() {
+        let securityAttr: Partial<WebPreferences> = {}
+        if (this.url.startsWith("file:")) {
+            // 预加载脚本
+            securityAttr = {
+                preload: join(__dirname, "../preload/index.mjs"),
+                sandbox: false,
+            }
+        }
+        this.webContentsView = new WebContentsView({
+            webPreferences: {
+                sandbox: true,
+                webSecurity: true,
+                allowRunningInsecureContent: false,
+                nodeIntegration: false,
+                spellcheck: false,
+                contextIsolation: true,
+                ...securityAttr,
+            },
+        })
+        const webContents = this.webContentsView.webContents
+        this.webContentsView.webContents.loadURL(this.url)
+        // this.webContentsView.webContents.executeJavaScript(`
+        //     const click = (x,y)=>{
+        //         const ev = new MouseEvent("click", {
+        //             view: window,
+        //             bubbles: true,
+        //             cancelable: true,
+        //             screenX: x,
+        //             screenY: y
+        //         })
+
+        //         const el = document.elementFromPoint(x,y);
+        //         console.log(el)
+        //         el.dispatchEvent(ev);
+        //     }
+        //     console.log("点击初始化完成")
+        // `)
+        this.webContentsView.webContents.setWindowOpenHandler(ev => {
+            debug(ev)
+            this.events.emit("window-open", ev)
+            return { action: "deny" }
+        })
+        webContents.addListener("media-paused", () => {
+            this.playing = false
+            this.startDestroyTimer()
+        })
+        webContents.addListener("media-started-playing", () => {
+            this.playing = true
+            this.stopDestroyTimer()
+        })
+        webContents.addListener("did-finish-load", () => {
+            this.url = webContents.getURL()
+            this.showUrl = this.getShowUrl(this.url)
+            this.events.emit("update")
+        })
+        webContents.addListener("did-navigate-in-page", () => {
+            this.url = webContents.getURL()
+            this.showUrl = this.getShowUrl(this.url)
+            this.events.emit("update")
+        })
+        webContents.addListener("page-title-updated", (_, title) => {
+            this.title = title
+            debug(`tab页更新:`, title)
+            this.events.emit("update")
+        })
+        webContents.addListener("page-favicon-updated", (_, favicons) => {
+            this.favicons = favicons
+            debug(favicons)
+            this.events.emit("update")
+        })
+        // 待机的销毁,但不去除实例
+        webContents.addListener("destroyed", () => {
+            this.#destoryWebContentsView()
+        })
+    }
+
+    print() {
+        return {
+            url: this.url,
+            showUrl: this.showUrl,
+        }
+    }
+
+    private getUrl(url) {
+        if (url === "about:blank") {
+            debug(FuckHTML)
+            return FuckHTML
+        }
+        return url
+    }
+
+    private getShowUrl(url) {
+        try {
+            if (fileURLToPath(url) === FuckHTML) {
+                debug(url)
+                debug(FuckHTML)
+                return "about:blank"
+            }
+        } catch (error) {
+            // ignore
+        }
+        return url
+    }
+
+    listenResize() {
+        if (!this.curWindow) {
+            return
+        }
+        if (!this.webContentsView) {
+            return
+        }
+        const size = this.curWindow.getContentSize()
+        this.webContentsView.setBounds(Layout(size[0], size[1]))
+    }
+
+    navigate(url: string) {
+        if (!this.webContentsView) return
+        this.webContentsView.webContents.loadURL(this.getUrl(url))
+    }
+
+    #destoryWebContentsView() {
+        this.stopDestroyTimer()
+        if (this.webContentsView && this.curWindow && !this.curWindow.isDestroyed()) {
+            this.curWindow.contentView.removeChildView(this.webContentsView)
+            this.curWindow.removeListener("resize", this.listenResize)
+        }
+        if (this.webContentsView && !this.webContentsView.webContents.isDestroyed()) {
+            this.webContentsView.webContents.removeAllListeners()
+            this.webContentsView.removeAllListeners()
+            // @ts-ignore 超过8s没有激活的tab就销毁
+            this.webContentsView.webContents.destroy()
+        }
+        this.webContentsView = null
+    }
+
+    destroy() {
+        this.#destoryWebContentsView()
+        this.events.removeAllListeners()
+        this.isDestory = true
+        debug("Tab destroy")
+    }
+}
+
+export { Tab }
+export default Tab
diff --git a/src/main/modules/tabs/index.ts b/src/main/modules/tabs/index.ts
new file mode 100644
index 0000000..5b16094
--- /dev/null
+++ b/src/main/modules/tabs/index.ts
@@ -0,0 +1,95 @@
+import Tab from "./Tab"
+import BaseClass from "vc/base/base"
+import _debug from "debug"
+import { BrowserWindow } from "electron"
+import EventEmitter from "events"
+
+const debug = _debug("app:tabs")
+
+class Tabs extends BaseClass {
+    destroy() {
+        this._tabs.forEach(v => v.destroy())
+        this._tabs = []
+    }
+
+    public events = new EventEmitter()
+
+    constructor() {
+        super()
+    }
+
+    _tabs: Tab[] = []
+
+    init(mainWindow) {
+        this.add("about:blank", true, mainWindow)
+    }
+
+    add(url: string, active: boolean, win: BrowserWindow) {
+        const tab = new Tab({ url }, win)
+        tab.events.on("window-open", ev => {
+            debug(ev)
+            this.add(ev.url, true, win)
+            this.events.emit("tab-active")
+            // tab.navigate(ev.url)
+        })
+        tab.events.on("update", () => {
+            this.events.emit("tab-active")
+        })
+        this._tabs.push(tab)
+        if (active) {
+            this.changeActive(this._tabs.length - 1)
+        }
+        this.events.emit("tab-active")
+    }
+
+    changeActive(index: number) {
+        this._tabs.forEach((tab, i) => {
+            tab.setActive(i === index)
+        })
+        this.events.emit("tab-active", index)
+    }
+
+    openDevtool(index: number) {
+        if (this._tabs[index]) {
+            this._tabs[index].openDevtool()
+        }
+    }
+
+    reload(index: number) {
+        if (this._tabs[index]) {
+            this._tabs[index].reload()
+        }
+    }
+
+    navigate(index: number, url: string) {
+        if (this._tabs[index]) {
+            this._tabs[index].navigate(url)
+        }
+    }
+
+    remove(index: number) {
+        this._tabs[index].destroy()
+        if (this._tabs[index].isActive && index - 1 >= 0) {
+            this.changeActive(index - 1)
+        }
+        this._tabs.splice(index, 1)
+        this.events.emit("tab-active")
+    }
+
+    removeAll(index: number[]) {
+        index
+            .map(v => {
+                return this._tabs[+v]
+            })
+            .forEach(tab => {
+                tab.destroy()
+            })
+        this._tabs = this._tabs.filter(v => {
+            return !v.isDestory
+        })
+        this.events.emit("tab-active")
+    }
+}
+
+export { Tabs }
+export default Tabs
diff --git a/src/main/modules/window-manager/index.ts b/src/main/modules/window-manager/index.ts
new file mode 100644
index 0000000..60e32cc
--- /dev/null
+++ b/src/main/modules/window-manager/index.ts
@@ -0,0 +1,346 @@
+import { BrowserWindow, app, dialog } from "electron"
+import { cloneDeep, merge } from "lodash"
+import { defaultConfig, defaultWindowConfig, getWindowsMap, IConfig, Param } from "./windowsMap"
+import { optimizer } from "@electron-toolkit/utils"
+import BaseClass from "vc/base/base"
+import _debug from "debug"
+
+const debug = _debug("app:window-manager")
+
+declare module "electron" {
+    interface BrowserWindow {
+        $$forceClose?: boolean
+        $$lastChoice?: number
+        $$opts?: Param
+    }
+}
+export { WindowManager }
+export default class WindowManager extends BaseClass {
+    constructor() {
+        super()
+    }
+
+    destroy() {
+        // TODO
+    }
+    globalChioce: number = -1
+    #showWin(info: Param) {
+        if (this.#windows.length >= 6) {
+            dialog.showErrorBox("错误", "窗口数量超出限制")
+            return
+        }
+        if (!info.name) {
+            dialog.showErrorBox("错误", "窗口未指定唯一key")
+            return
+        }
+        const index = this.findIndex(info.name)
+        if (index === -1) {
+            this.#windows.push(this.#add(info))
+        } else {
+            if (this.#windows[index].isDestroyed()) {
+                this.#windows[index] = this.#add(info)
+            } else {
+                if (info.url && info.loadURLInSameWin) {
+                    this.#windows[index].loadURL(info.url)
+                }
+                this.#windows[index].show()
+            }
+        }
+        this.showCurrentWindow()
+    }
+
+    showMainWindow() {
+        this.#showWin(this.mainInfo)
+    }
+
+    showWindow(name: string, opts?: Partial<IConfig>) {
+        let have = false
+        for (const key in this.#urlMap) {
+            const info = this.#urlMap[key]
+            if (new RegExp(key).test(name)) {
+                opts && merge(info, opts)
+                info.name = name
+                if (!info.ignoreEmptyUrl && !info.url) {
+                    dialog.showErrorBox("错误", name + "窗口未提供url")
+                    return
+                }
+                this.#showWin(info as Param)
+                have = true
+            }
+        }
+        if (!have) {
+            dialog.showErrorBox("错误", name + "窗口未创建成功")
+            return
+        }
+    }
+
+    init() {
+        /**
+         * 当应用被激活时触发
+         */
+        app.on("activate", () => {
+            this.showMainWindow()
+        })
+        // Default open or close DevTools by F12 in development
+        // and ignore CommandOrControl + R in production.
+        // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
+        app.on("browser-window-created", (_, window) => {
+            optimizer.watchWindowShortcuts(window)
+        })
+        /**
+         * 应用程序开始关闭时回调,可以通过event.preventDefault()阻止,以下两点需要注意:
+         * 1. 如果是autoUpdater.quitAndInstall()关闭的,那么会所有窗口关闭,并且在close事件之后执行
+         * 2. 关机,重启,用户退出时不会触发
+         */
+        app.on("before-quit", (event: Electron.Event) => {
+            const mainWin = this.get(this.mainInfo.name)
+            if (!mainWin || (mainWin && mainWin?.$$forceClose)) {
+                // app.exit()
+            } else {
+                event.preventDefault()
+            }
+        })
+
+        app.on("window-all-closed", () => {
+            if (process.platform !== "darwin") {
+                app.quit()
+            }
+        })
+    }
+
+    #urlMap = getWindowsMap()
+
+    getWndows() {
+        return this.#windows
+    }
+
+    length() {
+        return this.#windows.length
+    }
+
+    public get mainInfo() {
+        return this.#urlMap["main"] as Param
+    }
+
+    #windows: BrowserWindow[] = []
+
+    #defaultConfig: IConfig = defaultConfig
+
+    #add(config: Param) {
+        const curConfig = cloneDeep(this.#defaultConfig ?? {})
+        for (const key in config) {
+            if (Object.prototype.hasOwnProperty.call(config, key)) {
+                const value = config[key]
+                // if (Reflect.has(curConfig, key)) {
+                curConfig[key] = value
+                // }
+            }
+        }
+        const privateConfig = merge(curConfig.overideWindowOpts ? {} : cloneDeep(defaultWindowConfig), curConfig.windowOpts ?? {})
+        let parentWindow
+        if (typeof privateConfig.parent === "string") {
+            parentWindow = this.get(privateConfig.parent)
+        }
+        if (parentWindow) {
+            privateConfig.parent = parentWindow
+        }
+        const browserWin = new BrowserWindow(privateConfig)
+        browserWin.webContents.setWindowOpenHandler(() => {
+            if (curConfig.denyWindowOpen) {
+                return { action: "deny" }
+            }
+            return { action: "allow" }
+        })
+        // @ts-ignore 不需要解释为啥
+        browserWin.webContents.$$senderName = curConfig.name
+        browserWin.$$forceClose = false
+        browserWin.$$lastChoice = -1
+        browserWin.on("close", (event: any) => {
+            if (this.globalChioce === 1) {
+                this.#onClose(curConfig.name)
+                return
+            }
+            if (!curConfig.confrimWindowClose) {
+                this.#onClose(curConfig.name)
+                return
+            }
+            // eslint-disable-next-line @typescript-eslint/no-this-alias
+            const that = this
+            function justQuit() {
+                browserWin.$$lastChoice = 1
+                // app.quit()
+                // 不要用quit();试了会弹两次
+                browserWin.$$forceClose = true
+                if (curConfig.name === that.mainInfo.name) {
+                    that.globalChioce = 1
+                    app.quit() // exit()直接关闭客户端,不会执行quit();
+                } else {
+                    that.delete(curConfig.name)
+                    that.showCurrentWindow()
+                }
+            }
+            if (browserWin.$$forceClose) {
+                that.delete(curConfig.name)
+                app.quit()
+            } else {
+                let choice = -1
+                if (browserWin && browserWin!.$$lastChoice !== undefined && browserWin.$$lastChoice >= 0) {
+                    choice = browserWin.$$lastChoice
+                } else {
+                    choice = dialog.showMessageBoxSync(browserWin, {
+                        type: "info",
+                        title: curConfig.confrimWindowCloseText.title,
+                        defaultId: curConfig.confrimWindowCloseText.defaultId,
+                        cancelId: curConfig.confrimWindowCloseText.cancelId,
+                        message: curConfig.confrimWindowCloseText.message,
+                        buttons: curConfig.confrimWindowCloseText.buttons,
+                    })
+                }
+                if (choice === 1) {
+                    justQuit()
+                } else {
+                    event && event.preventDefault()
+                }
+            }
+        })
+        browserWin.$$opts = curConfig
+        // 在此注册窗口
+        browserWin.webContents.addListener("did-finish-load", () => {
+            browserWin.webContents.executeJavaScript(`window._global=${JSON.stringify({ name: curConfig.name })};`)
+            browserWin.webContents.send("bind-window-manager", curConfig.name)
+        })
+        // https://www.electronjs.org/zh/docs/latest/tutorial/security#12-%E5%88%9B%E5%BB%BAwebview%E5%89%8D%E7%A1%AE%E8%AE%A4%E5%85%B6%E9%80%89%E9%A1%B9
+        // browserWin.webContents.on("will-attach-webview", (_event, webPreferences) => {
+        //     if (webPreferences.preload !== path.resolve(app.getAppPath(), "webview.js")) {
+        //         // 如果未使用,则删除预加载脚本或验证其位置是否合法
+        //         delete webPreferences.preload
+        //     }
+
+        //     // 禁用 Node.js 集成
+        //     webPreferences.nodeIntegration = false
+
+        //     // 验证正在加载的 URL
+        //     // if (!params.src.startsWith('https://example.com/')) {
+        //     //   event.preventDefault()
+        //     // }
+        // })
+        if (curConfig.type === "info") {
+            // 隐藏菜单
+            browserWin.setMenuBarVisibility(false)
+        }
+        if (curConfig.url) {
+            browserWin.loadURL(curConfig.url)
+            // logger.debug(`当前窗口网址:${curConfig.url}`)
+        }
+        if (curConfig.windowOpts?.show === false) {
+            if (curConfig.url) {
+                browserWin.once("ready-to-show", () => {
+                    debug(`准备展示:`, curConfig.url)
+                    browserWin?.show()
+                })
+            } else {
+                browserWin?.show()
+            }
+        }
+        return browserWin
+    }
+
+    showCurrentWindow() {
+        debug(`current open window: ${this.#windows.map(v => v.$$opts!.name).join(",")}`)
+    }
+
+    #onClose(name: string) {
+        for (let i = this.#windows.length - 1; i >= 0; i--) {
+            const win = this.#windows[i]
+            if (name === win.$$opts!.name) {
+                win.destroy()
+                this.#windows.splice(i, 1)
+            }
+        }
+        this.showCurrentWindow()
+    }
+
+    get(name: string) {
+        return this.#windows.find(v => {
+            return v.$$opts!.name === name
+        })
+    }
+
+    getFocusWindow() {
+        const mainWindow = this.getMainWindow()
+        if (mainWindow?.isFocused()) {
+            return mainWindow
+        }
+        for (let i = 0; i < this.#windows.length; i++) {
+            const win = this.#windows[i]
+            if (win.isFocused()) {
+                return win
+            }
+        }
+        return
+    }
+
+    getMainWindow() {
+        return this.#windows.find(v => {
+            return v.$$opts!.name === this.mainInfo.name
+        })
+    }
+
+    close(name: string | RegExp) {
+        const indexList = this.findAllIndex(name)
+        for (let i = indexList.length - 1; i >= 0; i--) {
+            const index = indexList[i]
+            const win = this.#windows[index]
+            win.close()
+        }
+    }
+
+    delete(name: string | RegExp) {
+        const indexList = this.findAllIndex(name)
+        for (let i = indexList.length - 1; i >= 0; i--) {
+            const index = indexList[i]
+            this.#windows.splice(index, 1)
+        }
+    }
+
+    findIndex(name: string | RegExp) {
+        const index = this.#windows.findIndex(v => {
+            if (typeof name === "string") {
+                return v.$$opts!.name === name
+            } else {
+                return name.test(v.$$opts!.name)
+            }
+        })
+        return index
+    }
+
+    findAllIndex(name: string | RegExp) {
+        const result: number[] = []
+        for (let i = 0; i < this.#windows.length; i++) {
+            const win = this.#windows[i]
+            if (typeof name === "string" && win.$$opts!.name === name) {
+                result.push(i)
+            } else if (typeof name !== "string" && name.test(win.$$opts!.name)) {
+                result.push(i)
+            }
+        }
+        return result
+    }
+
+    // show(name: string | RegExp) {
+    //     let indexList = this.findAllIndex(name)
+    //     if (!!indexList.length) {
+    //         for (let i = 0; i < indexList.length; i++) {
+    //             const index = indexList[i];
+    //             const win = this.#windows[index]
+    //             if (win.isDestroyed()) {
+    //                 this.#windows[index] = this.#add(win.$$opts)
+    //             } else {
+    //                 win.show()
+    //             }
+    //         }
+    //     } else {
+    //         console.warn("该窗口不存在")
+    //     }
+    // }
+}
diff --git a/src/main/modules/window-manager/windowsMap.ts b/src/main/modules/window-manager/windowsMap.ts
new file mode 100644
index 0000000..9606a9b
--- /dev/null
+++ b/src/main/modules/window-manager/windowsMap.ts
@@ -0,0 +1,129 @@
+import config from "config"
+import { BrowserWindowConstructorOptions } from "electron"
+import { getFileUrl } from "vc/utils"
+import icon from "res/icon.png?asset"
+import { join } from "path"
+
+export type Param = Partial<IConfig> & Required<Pick<IConfig, "name">>
+
+export interface IConfig {
+    name?: string
+    url?: string
+    loadURLInSameWin?: boolean
+    type?: "info"
+    windowOpts?: BrowserWindowConstructorOptions
+    overideWindowOpts?: boolean
+    ignoreEmptyUrl?: boolean
+    denyWindowOpen?: boolean
+    confrimWindowClose?: boolean
+    confrimWindowCloseText?: {
+        title: string
+        message: string
+        buttons: string[]
+        defaultId: number
+        cancelId: number
+    }
+}
+
+export const defaultConfig: IConfig = {
+    denyWindowOpen: true,
+}
+
+export const defaultWindowConfig = {
+    height: 600,
+    useContentSize: true,
+    width: 800,
+    show: true,
+    resizable: true,
+    minWidth: 900,
+    minHeight: 600,
+    frame: true,
+    transparent: false,
+    alwaysOnTop: false,
+    webPreferences: {},
+}
+
+export function getWindowsMap(): Record<string, IConfig> {
+    return {
+        main: {
+            name: "main",
+            url: getFileUrl("index.html"),
+            confrimWindowClose: true,
+            confrimWindowCloseText: {
+                title: config.app_title,
+                defaultId: 0,
+                cancelId: 0,
+                message: "确定要关闭吗?",
+                buttons: ["没事", "直接退出"],
+            },
+            windowOpts: {
+                show: false,
+                titleBarStyle: "hidden",
+                titleBarOverlay: true,
+                ...(process.platform === "linux" ? { icon } : {}),
+                webPreferences: {
+                    webviewTag: false,
+                    preload: join(__dirname, "../preload/index.mjs"),
+                    nodeIntegration: true,
+                    contextIsolation: true,
+                },
+            },
+        },
+        _blank: {
+            overideWindowOpts: false,
+            confrimWindowClose: true,
+            confrimWindowCloseText: {
+                title: config.app_title,
+                defaultId: 0,
+                cancelId: 0,
+                message: "确定要关闭吗?",
+                buttons: ["没事", "直接退出"],
+            },
+            type: "info",
+            windowOpts: {
+                height: 600,
+                useContentSize: true,
+                width: 800,
+                show: true,
+                resizable: true,
+                minWidth: 900,
+                minHeight: 600,
+                frame: true,
+                transparent: false,
+                alwaysOnTop: false,
+                // icon: appIconPath,
+                title: config.app_title,
+                webPreferences: {
+                    devTools: false,
+                    sandbox: true,
+                    nodeIntegration: false,
+                    contextIsolation: true,
+                    webviewTag: false,
+                    preload: undefined,
+                },
+            },
+        },
+        "^about": {
+            url: getFileUrl("about.html"),
+            overideWindowOpts: true,
+            confrimWindowClose: false,
+            type: "info",
+            windowOpts: {
+                width: 600,
+                height: 200,
+                minimizable: false,
+                darkTheme: true,
+                modal: true,
+                show: false,
+                resizable: false,
+                // icon: appIconPath,
+                webPreferences: {
+                    devTools: false,
+                    sandbox: false,
+                    nodeIntegration: false,
+                    contextIsolation: true,
+                },
+            },
+        },
+    }
+}
diff --git a/src/main/utils/index.ts b/src/main/utils/index.ts
new file mode 100644
index 0000000..0433eed
--- /dev/null
+++ b/src/main/utils/index.ts
@@ -0,0 +1,21 @@
+import { is } from "@electron-toolkit/utils"
+import { join } from "node:path"
+import { webContents } from "electron"
+
+export function getFileUrl(app: string, route: string = "") {
+    let winURL = ""
+    if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
+        winURL = process.env["ELECTRON_RENDERER_URL"] + `/${app}#/${route}`
+    } else {
+        winURL = join(__dirname, `../renderer/${app}#/${route}`)
+    }
+    return winURL
+}
+
+export function isPromise(value: () => any) {
+    return value && Object.prototype.toString.call(value) === "[object Promise]"
+}
+
+export const broadcast = (event: string, ...args: any[]) => {
+    webContents.getAllWebContents().forEach(browser => browser.send(event, ...args))
+}
diff --git a/src/preload/call.ts b/src/preload/call.ts
new file mode 100644
index 0000000..35ed73f
--- /dev/null
+++ b/src/preload/call.ts
@@ -0,0 +1,71 @@
+import { ipcRenderer } from "electron"
+
+let count = 0
+export function call(command: string, ...args: any[]): Promise<any> {
+    return new Promise((resolve, reject) => {
+        if (!command) {
+            console.warn("命令不能为空")
+            return
+        }
+        count++
+        const timestamp = new Date().getTime()
+        const key = timestamp + "-" + count
+        let timeID: any = null
+        ipcRenderer.once(key, fn)
+
+        function fn(_, err: any, res: any) {
+            clearTimeout(timeID)
+            if (err) {
+                reject(err)
+                return
+            }
+            resolve(res)
+        }
+
+        ipcRenderer.send("command", key, command, ...args)
+
+        // 超过5s就取消监听
+        timeID = setTimeout(() => {
+            reject(new Error(`超过5s未响应: ${command}`))
+            ipcRenderer.removeListener(key, fn)
+        }, 5000)
+    })
+}
+
+export function callLong(command: string, ...args: any[]): Promise<any> {
+    return new Promise((resolve, reject) => {
+        if (!command) {
+            console.warn("命令不能为空")
+            return
+        }
+        count++
+        const timestamp = new Date().getTime()
+        const key = timestamp + "-" + count
+        ipcRenderer.once(key, fn)
+
+        function fn(_, err: any, res: any) {
+            if (err) {
+                reject(err)
+                return
+            }
+            resolve(res)
+        }
+
+        ipcRenderer.send("command", key, command, ...args)
+    })
+}
+
+export function callSync(command: string, ...args: any[]) {
+    if (!command) {
+        console.warn("命令不能为空")
+        return
+    }
+    count++
+    const timestamp = new Date().getTime()
+    const key = timestamp + "-" + count
+    const result = ipcRenderer.sendSync("command", key, command, ...args)
+    if (!result) {
+        return
+    }
+    return result
+}
diff --git a/src/preload/index.ts b/src/preload/index.ts
index 9cf45a0..b453789 100644
--- a/src/preload/index.ts
+++ b/src/preload/index.ts
@@ -1,8 +1,63 @@
-import { contextBridge } from "electron"
+import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron"
 import { electronAPI } from "@electron-toolkit/preload"
+import { call, callLong, callSync } from "./call"
+import { IPopupMenuOption } from "#"
+document.addEventListener("DOMContentLoaded", () => {
+    const initStyle = document.createElement("style")
+    initStyle.textContent = `
+*,
+*::before,
+*::after {
+    box-sizing: border-box;
+    margin: 0;
+    font-weight: normal;
+}
+
+body {
+    min-height: 100vh;
+    // background-image: linear-gradient(to left bottom, #74C1EB, #385FAC);
+    // background: #F8F8F8;
+}
+`
+    document.head.appendChild(initStyle)
+})
 
 // Custom APIs for renderer
-const api = {}
+const api = {
+    call,
+    callLong,
+    callSync,
+    send(command: string, ...argu: any[]) {
+        if (!command) return
+        return ipcRenderer.send(command, ...argu)
+    },
+    sendSync(command: string, ...argu: any[]) {
+        if (!command) return
+        return ipcRenderer.sendSync(command, ...argu)
+    },
+    on(command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) {
+        ipcRenderer.on(command, cb)
+        return () => ipcRenderer.removeListener(command, cb)
+    },
+    once(command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) {
+        ipcRenderer.once(command, cb)
+        return () => ipcRenderer.removeListener(command, cb)
+    },
+    off(command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) {
+        return ipcRenderer.removeListener(command, cb)
+    },
+    offAll(command: string) {
+        return ipcRenderer.removeAllListeners(command)
+    },
+    popupMenu(options: IPopupMenuOption) {
+        ipcRenderer.send("x_popup_menu", curWebContentName, options)
+    },
+}
+
+let curWebContentName = ""
+ipcRenderer.once("bind-window-manager", (_, name: string) => {
+    curWebContentName = name
+})
 
 // Use `contextBridge` APIs to expose Electron APIs to
 // renderer only if context isolation is enabled, otherwise
diff --git a/src/renderer/about.html b/src/renderer/about.html
new file mode 100644
index 0000000..6798e60
--- /dev/null
+++ b/src/renderer/about.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <title>关于我</title>
+        <style>
+            html,
+            body {
+                height: 100%;
+                width: 100%;
+                margin: 0;
+                padding: 0;
+                outline: none;
+                border: 0;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+            }
+        </style>
+    </head>
+
+    <body>
+        <article>
+            <h1 id="demo" style="text-align: center">您好,亲爱的冒险者!</h1>
+        </article>
+    </body>
+</html>
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 9777bba..82eacab 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -6,7 +6,7 @@
         <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
         <meta
             http-equiv="Content-Security-Policy"
-            content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
+            content="default-src 'self' api: 'unsafe-inline'; script-src 'self' api:; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
         />
     </head>
 
diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue
index c3a5ac7..354e5e5 100644
--- a/src/renderer/src/App.vue
+++ b/src/renderer/src/App.vue
@@ -1,30 +1,163 @@
 <script setup lang="ts">
-import Versions from "./components/Versions.vue"
+import NavBar from "@renderer/components/NavBar.vue"
+import { onBeforeMount, ref } from "vue"
+import { PopupMenu } from "./bridge/PopupMenu"
 
-const ipcHandle = () => window.electron.ipcRenderer.send("ping")
+const list = ref<any[]>([])
+const curUrl = ref<any>("")
+const curIndex = ref<any>(-1)
+const listener = (_, v) => {
+    list.value = v
+    const el = v.find(v => v.isActive)
+    console.log(el);
+
+    curIndex.value = v.findIndex(v => v.isActive)
+    if (el) {
+        curUrl.value = el.showUrl
+    } else {
+        curUrl.value = ""
+    }
+}
+if (import.meta.hot) {
+    api.off("TabsCommand.update", listener)
+}
+api.on("TabsCommand.update", listener)
+api.call("TabsCommand.sync")
+
+onBeforeMount(async () => {
+    list.value = await fetch("api://fuck/TabsService/getAllTabs").then(async res => await res.json())
+})
+
+// const url = ref("")
+
+// async function addTab() {
+//     if (!url.value) url.value = "about:blank"
+//     await fetch("api://fuck/TabsService/add", {
+//         method: "POST",
+//         body: JSON.stringify({ url: url.value }),
+//     })
+//     url.value = ""
+//     onClick()
+// }
+
+function handleTabContextMenu(_, index) {
+    const menu = new PopupMenu([
+        {
+            label: "右侧关闭",
+            click() {
+                const all: number[] = []
+                list.value.forEach((_, i) => {
+                    if (i <= index) return
+                    all.push(i)
+                })
+                fetch("api://fuck/TabsService/closeTabAll", {
+                    method: "POST",
+                    body: JSON.stringify({ active: all }),
+                })
+            },
+        },
+        {
+            type: "separator",
+        },
+    ])
+    menu.show()
+}
+
+function changeTab(_, index) {
+    api.call("TabsCommand.setActive", index)
+}
+
+function addTabInput() {
+    if (curUrl.value) {
+        if (curIndex.value !== undefined && curIndex.value >= 0) {
+            api.call("TabsCommand.nagivate", curIndex.value, curUrl.value)
+        } else {
+            api.call("TabsCommand.add", curUrl.value)
+        }
+    }
+}
+function addTab() {
+    api.call("TabsCommand.add", "about:blank")
+}
+
+async function closeTab(_, index) {
+    await fetch("api://fuck/TabsService/closeTab", {
+        method: "POST",
+        body: JSON.stringify({ active: index }),
+    })
+    onClick()
+}
+
+const onClick = async () => {
+    list.value = await api.call("TabsCommand.getAllTabs")
+    // list.value = await fetch("api://fuck/TabsService/getAllTabs").then(async res => await res.json())
+    // fetch("api://fuck/BasicService/showAbout").then(async res => console.log(await res.json()))
+    // fetch("api://index/openAbout", {
+    //     method: "POST",
+    //     body: JSON.stringify({ a: "234" }),
+    // }).then(async res => console.log(await res.json()))
+}
+
+function onClickDevTool() {
+    fetch("api://fuck/BasicService/openTabDevtool")
+}
 </script>
 
 <template>
-    <img alt="logo" class="logo" src="./assets/electron.svg" />
-    <div class="creator">Powered by electron-vite</div>
-    <div class="text">
-        Build an Electron app with
-        <span class="vue">Vue</span>
-        and
-        <span class="ts">TypeScript</span>
-    </div>
-    <p class="tip">
-        Please try pressing
-        <code>F12</code>
-        to open the devTool
-    </p>
-    <div class="actions">
-        <div class="action">
-            <a href="https://electron-vite.org/" target="_blank" rel="noreferrer">Documentation</a>
-        </div>
-        <div class="action">
-            <a target="_blank" rel="noreferrer" @click="ipcHandle">Send IPC</a>
+    <div h-full flex flex-col>
+        <NavBar></NavBar>
+        <div flex-1 h-0 overflow-auto flex flex-col>
+            <div h="100px" flex flex-col b-b="1px solid #E5E5E5">
+                <div flex gap-1 my-1 px-1 w-full>
+                    <div
+                        v-for="(item, index) in list"
+                        :key="index"
+                        p-1
+                        b-b="1px solid gray"
+                        b-l="1px solid gray"
+                        b-r="1px solid gray"
+                        :b-t="item.isActive ? '1px solid red' : '1px solid gray'"
+                        flex
+                        flex-1
+                        w-0
+                        items-center
+                        cursor="pointer"
+                        gap="5px"
+                        max-w="200px"
+                        @contextmenu="handleTabContextMenu(item, index)"
+                        @click="changeTab(item, index)"
+                    >
+                        <div flex-1 w-0 line-1 text-normal>{{ item.title || "加载中..." }}</div>
+                        <span p-1 rounded hover="bg-gray-2 text-hover" @click.stop="closeTab(item, index)">X</span>
+                    </div>
+                    <div
+                        p-1
+                        b-b="1px solid gray"
+                        b-l="1px solid gray"
+                        b-r="1px solid gray"
+                        b-t="1px solid gray"
+                        flex
+                        items-center
+                        cursor="pointer"
+                        gap="5px"
+                        hover="bg-gray-2 text-hover"
+                    >
+                        <span p-1 rounded @click.stop="addTab()">+</span>
+                    </div>
+                </div>
+                <div mx="5px" overflow="auto" flex-1 h-0 flex items-center gap-x="5px">
+                    <div flex-1 w-0 h="35px" px-3 rounded="35px" b="1px solid gray-4" flex items-center>
+                        <input v-model="curUrl" placeholder="输入点什么" w-full text="16px" b-0 leading="25px" outline-0 type="text" />
+                    </div>
+                    <div inline-block hover="bg-gray-2 text-hover" px-1 py-1 rounded cursor="pointer" @click="addTabInput()">
+                        <button text="14px" bg-transparent b-0 cursor="pointer">前往</button>
+                    </div>
+                    <div inline-block hover="bg-gray-2 text-hover" px-1 py-1 rounded cursor="pointer" @click="onClickDevTool()">
+                        <button text="14px" bg-transparent b-0 cursor="pointer">DevTool</button>
+                    </div>
+                </div>
+            </div>
+            <div flex-1 h-0 flex items-center justify-center>fuck</div>
         </div>
     </div>
-    <Versions />
 </template>
diff --git a/src/renderer/src/assets/base.css b/src/renderer/src/assets/base.css
deleted file mode 100644
index c8dbd6b..0000000
--- a/src/renderer/src/assets/base.css
+++ /dev/null
@@ -1,67 +0,0 @@
-:root {
-    --ev-c-white: #ffffff;
-    --ev-c-white-soft: #f8f8f8;
-    --ev-c-white-mute: #f2f2f2;
-
-    --ev-c-black: #1b1b1f;
-    --ev-c-black-soft: #222222;
-    --ev-c-black-mute: #282828;
-
-    --ev-c-gray-1: #515c67;
-    --ev-c-gray-2: #414853;
-    --ev-c-gray-3: #32363f;
-
-    --ev-c-text-1: rgba(255, 255, 245, 0.86);
-    --ev-c-text-2: rgba(235, 235, 245, 0.6);
-    --ev-c-text-3: rgba(235, 235, 245, 0.38);
-
-    --ev-button-alt-border: transparent;
-    --ev-button-alt-text: var(--ev-c-text-1);
-    --ev-button-alt-bg: var(--ev-c-gray-3);
-    --ev-button-alt-hover-border: transparent;
-    --ev-button-alt-hover-text: var(--ev-c-text-1);
-    --ev-button-alt-hover-bg: var(--ev-c-gray-2);
-}
-
-:root {
-    --color-background: var(--ev-c-black);
-    --color-background-soft: var(--ev-c-black-soft);
-    --color-background-mute: var(--ev-c-black-mute);
-
-    --color-text: var(--ev-c-text-1);
-}
-
-*,
-*::before,
-*::after {
-    box-sizing: border-box;
-    margin: 0;
-    font-weight: normal;
-}
-
-ul {
-    list-style: none;
-}
-
-body {
-    min-height: 100vh;
-    color: var(--color-text);
-    background: var(--color-background);
-    line-height: 1.6;
-    font-family:
-        Inter,
-        -apple-system,
-        BlinkMacSystemFont,
-        "Segoe UI",
-        Roboto,
-        Oxygen,
-        Ubuntu,
-        Cantarell,
-        "Fira Sans",
-        "Droid Sans",
-        "Helvetica Neue",
-        sans-serif;
-    text-rendering: optimizeLegibility;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
diff --git a/src/renderer/src/assets/electron.svg b/src/renderer/src/assets/electron.svg
deleted file mode 100644
index 45ef09c..0000000
--- a/src/renderer/src/assets/electron.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <circle cx="64" cy="64" r="64" fill="#2F3242"/>
-  <ellipse cx="63.9835" cy="23.2036" rx="4.48794" ry="4.495" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
-  <path d="M51.3954 39.5028C52.3733 39.6812 53.3108 39.033 53.4892 38.055C53.6676 37.0771 53.0194 36.1396 52.0414 35.9612L51.3954 39.5028ZM28.6153 43.5751L30.1748 44.4741L30.1748 44.4741L28.6153 43.5751ZM28.9393 60.9358C29.4332 61.7985 30.5329 62.0976 31.3957 61.6037C32.2585 61.1098 32.5575 60.0101 32.0636 59.1473L28.9393 60.9358ZM37.6935 66.7457C37.025 66.01 35.8866 65.9554 35.1508 66.6239C34.415 67.2924 34.3605 68.4308 35.029 69.1666L37.6935 66.7457ZM53.7489 81.7014L52.8478 83.2597L53.7489 81.7014ZM96.9206 89.515C97.7416 88.9544 97.9526 87.8344 97.3919 87.0135C96.8313 86.1925 95.7113 85.9815 94.8904 86.5422L96.9206 89.515ZM52.0414 35.9612C46.4712 34.9451 41.2848 34.8966 36.9738 35.9376C32.6548 36.9806 29.0841 39.1576 27.0559 42.6762L30.1748 44.4741C31.5693 42.0549 34.1448 40.3243 37.8188 39.4371C41.5009 38.5479 46.1547 38.5468 51.3954 39.5028L52.0414 35.9612ZM27.0559 42.6762C24.043 47.9029 25.2781 54.5399 28.9393 60.9358L32.0636 59.1473C28.6579 53.1977 28.1088 48.0581 30.1748 44.4741L27.0559 42.6762ZM35.029 69.1666C39.6385 74.24 45.7158 79.1355 52.8478 83.2597L54.6499 80.1432C47.8081 76.1868 42.0298 71.5185 37.6935 66.7457L35.029 69.1666ZM52.8478 83.2597C61.344 88.1726 70.0465 91.2445 77.7351 92.3608C85.359 93.4677 92.2744 92.6881 96.9206 89.515L94.8904 86.5422C91.3255 88.9767 85.4902 89.849 78.2524 88.7982C71.0793 87.7567 62.809 84.8612 54.6499 80.1432L52.8478 83.2597ZM105.359 84.9077C105.359 81.4337 102.546 78.6127 99.071 78.6127V82.2127C100.553 82.2127 101.759 83.4166 101.759 84.9077H105.359ZM99.071 78.6127C95.5956 78.6127 92.7831 81.4337 92.7831 84.9077H96.3831C96.3831 83.4166 97.5892 82.2127 99.071 82.2127V78.6127ZM92.7831 84.9077C92.7831 88.3817 95.5956 91.2027 99.071 91.2027V87.6027C97.5892 87.6027 96.3831 86.3988 96.3831 84.9077H92.7831ZM99.071 91.2027C102.546 91.2027 105.359 88.3817 105.359 84.9077H101.759C101.759 86.3988 100.553 87.6027 99.071 87.6027V91.2027Z" fill="#A2ECFB"/>
-  <path d="M91.4873 65.382C90.8456 66.1412 90.9409 67.2769 91.7002 67.9186C92.4594 68.5603 93.5951 68.465 94.2368 67.7058L91.4873 65.382ZM99.3169 43.6354L97.7574 44.5344L99.3169 43.6354ZM84.507 35.2412C83.513 35.2282 82.6967 36.0236 82.6838 37.0176C82.6708 38.0116 83.4661 38.8279 84.4602 38.8409L84.507 35.2412ZM74.9407 39.8801C75.9127 39.6716 76.5315 38.7145 76.323 37.7425C76.1144 36.7706 75.1573 36.1517 74.1854 36.3603L74.9407 39.8801ZM53.7836 46.3728L54.6847 47.931L53.7836 46.3728ZM25.5491 80.9047C25.6932 81.8883 26.6074 82.5688 27.5911 82.4247C28.5747 82.2806 29.2552 81.3664 29.1111 80.3828L25.5491 80.9047ZM94.2368 67.7058C97.8838 63.3907 100.505 58.927 101.752 54.678C103.001 50.4213 102.9 46.2472 100.876 42.7365L97.7574 44.5344C99.1494 46.9491 99.3603 50.0419 98.2974 53.6644C97.2323 57.2945 94.9184 61.3223 91.4873 65.382L94.2368 67.7058ZM100.876 42.7365C97.9119 37.5938 91.7082 35.335 84.507 35.2412L84.4602 38.8409C91.1328 38.9278 95.7262 41.0106 97.7574 44.5344L100.876 42.7365ZM74.1854 36.3603C67.4362 37.8086 60.0878 40.648 52.8826 44.8146L54.6847 47.931C61.5972 43.9338 68.5948 41.2419 74.9407 39.8801L74.1854 36.3603ZM52.8826 44.8146C44.1366 49.872 36.9669 56.0954 32.1491 62.3927C27.3774 68.63 24.7148 75.2115 25.5491 80.9047L29.1111 80.3828C28.4839 76.1026 30.4747 70.5062 35.0084 64.5802C39.496 58.7143 46.2839 52.7889 54.6847 47.931L52.8826 44.8146Z" fill="#A2ECFB"/>
-  <path d="M49.0825 87.2295C48.7478 86.2934 47.7176 85.8059 46.7816 86.1406C45.8455 86.4753 45.358 87.5055 45.6927 88.4416L49.0825 87.2295ZM78.5635 96.4256C79.075 95.5732 78.7988 94.4675 77.9464 93.9559C77.0941 93.4443 75.9884 93.7205 75.4768 94.5729L78.5635 96.4256ZM79.5703 85.1795C79.2738 86.1284 79.8027 87.1379 80.7516 87.4344C81.7004 87.7308 82.71 87.2019 83.0064 86.2531L79.5703 85.1795ZM84.3832 64.0673H82.5832H84.3832ZM69.156 22.5301C68.2477 22.1261 67.1838 22.535 66.7799 23.4433C66.3759 24.3517 66.7848 25.4155 67.6931 25.8194L69.156 22.5301ZM45.6927 88.4416C47.5994 93.7741 50.1496 98.2905 53.2032 101.505C56.2623 104.724 59.9279 106.731 63.9835 106.731V103.131C61.1984 103.131 58.4165 101.765 55.8131 99.0249C53.2042 96.279 50.8768 92.2477 49.0825 87.2295L45.6927 88.4416ZM63.9835 106.731C69.8694 106.731 74.8921 102.542 78.5635 96.4256L75.4768 94.5729C72.0781 100.235 68.0122 103.131 63.9835 103.131V106.731ZM83.0064 86.2531C85.0269 79.7864 86.1832 72.1831 86.1832 64.0673H82.5832C82.5832 71.8536 81.4723 79.0919 79.5703 85.1795L83.0064 86.2531ZM86.1832 64.0673C86.1832 54.1144 84.4439 44.922 81.4961 37.6502C78.5748 30.4436 74.3436 24.8371 69.156 22.5301L67.6931 25.8194C71.6364 27.5731 75.3846 32.1564 78.1598 39.0026C80.9086 45.7836 82.5832 54.507 82.5832 64.0673H86.1832Z" fill="#A2ECFB"/>
-  <path fill-rule="evenodd" clip-rule="evenodd" d="M103.559 84.9077C103.559 82.4252 101.55 80.4127 99.071 80.4127C96.5924 80.4127 94.5831 82.4252 94.5831 84.9077C94.5831 87.3902 96.5924 89.4027 99.071 89.4027C101.55 89.4027 103.559 87.3902 103.559 84.9077V84.9077Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
-  <path fill-rule="evenodd" clip-rule="evenodd" d="M28.8143 89.4027C31.2929 89.4027 33.3023 87.3902 33.3023 84.9077C33.3023 82.4252 31.2929 80.4127 28.8143 80.4127C26.3357 80.4127 24.3264 82.4252 24.3264 84.9077C24.3264 87.3902 26.3357 89.4027 28.8143 89.4027V89.4027V89.4027Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
-  <path fill-rule="evenodd" clip-rule="evenodd" d="M64.8501 68.0857C62.6341 68.5652 60.451 67.1547 59.9713 64.9353C59.4934 62.7159 60.9007 60.5293 63.1167 60.0489C65.3326 59.5693 67.5157 60.9798 67.9954 63.1992C68.4742 65.4186 67.066 67.6052 64.8501 68.0857Z" fill="#A2ECFB"/>
-</svg>
diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css
deleted file mode 100644
index d6907ce..0000000
--- a/src/renderer/src/assets/main.css
+++ /dev/null
@@ -1,171 +0,0 @@
-@import "./base.css";
-
-body {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    overflow: hidden;
-    background-image: url("./wavy-lines.svg");
-    background-size: cover;
-    user-select: none;
-}
-
-code {
-    font-weight: 600;
-    padding: 3px 5px;
-    border-radius: 2px;
-    background-color: var(--color-background-mute);
-    font-family:
-        ui-monospace,
-        SFMono-Regular,
-        SF Mono,
-        Menlo,
-        Consolas,
-        Liberation Mono,
-        monospace;
-    font-size: 85%;
-}
-
-#app {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    flex-direction: column;
-    margin-bottom: 80px;
-}
-
-.logo {
-    margin-bottom: 20px;
-    -webkit-user-drag: none;
-    height: 128px;
-    width: 128px;
-    will-change: filter;
-    transition: filter 300ms;
-}
-
-.logo:hover {
-    filter: drop-shadow(0 0 1.2em #6988e6aa);
-}
-
-.creator {
-    font-size: 14px;
-    line-height: 16px;
-    color: var(--ev-c-text-2);
-    font-weight: 600;
-    margin-bottom: 10px;
-}
-
-.text {
-    font-size: 28px;
-    color: var(--ev-c-text-1);
-    font-weight: 700;
-    line-height: 32px;
-    text-align: center;
-    margin: 0 10px;
-    padding: 16px 0;
-}
-
-.tip {
-    font-size: 16px;
-    line-height: 24px;
-    color: var(--ev-c-text-2);
-    font-weight: 600;
-}
-
-.vue {
-    background: -webkit-linear-gradient(315deg, #42d392 25%, #647eff);
-    background-clip: text;
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-    font-weight: 700;
-}
-
-.ts {
-    background: -webkit-linear-gradient(315deg, #3178c6 45%, #f0dc4e);
-    background-clip: text;
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-    font-weight: 700;
-}
-
-.actions {
-    display: flex;
-    padding-top: 32px;
-    margin: -6px;
-    flex-wrap: wrap;
-    justify-content: flex-start;
-}
-
-.action {
-    flex-shrink: 0;
-    padding: 6px;
-}
-
-.action a {
-    cursor: pointer;
-    text-decoration: none;
-    display: inline-block;
-    border: 1px solid transparent;
-    text-align: center;
-    font-weight: 600;
-    white-space: nowrap;
-    border-radius: 20px;
-    padding: 0 20px;
-    line-height: 38px;
-    font-size: 14px;
-    border-color: var(--ev-button-alt-border);
-    color: var(--ev-button-alt-text);
-    background-color: var(--ev-button-alt-bg);
-}
-
-.action a:hover {
-    border-color: var(--ev-button-alt-hover-border);
-    color: var(--ev-button-alt-hover-text);
-    background-color: var(--ev-button-alt-hover-bg);
-}
-
-.versions {
-    position: absolute;
-    bottom: 30px;
-    margin: 0 auto;
-    padding: 15px 0;
-    font-family: "Menlo", "Lucida Console", monospace;
-    display: inline-flex;
-    overflow: hidden;
-    align-items: center;
-    border-radius: 22px;
-    background-color: #202127;
-    backdrop-filter: blur(24px);
-}
-
-.versions li {
-    display: block;
-    float: left;
-    border-right: 1px solid var(--ev-c-gray-1);
-    padding: 0 20px;
-    font-size: 14px;
-    line-height: 14px;
-    opacity: 0.8;
-    &:last-child {
-        border: none;
-    }
-}
-
-@media (max-width: 720px) {
-    .text {
-        font-size: 20px;
-    }
-}
-
-@media (max-width: 620px) {
-    .versions {
-        display: none;
-    }
-}
-
-@media (max-width: 350px) {
-    .tip,
-    .actions {
-        display: none;
-    }
-}
diff --git a/src/renderer/src/assets/style/_common.scss b/src/renderer/src/assets/style/_common.scss
new file mode 100644
index 0000000..651c752
--- /dev/null
+++ b/src/renderer/src/assets/style/_common.scss
@@ -0,0 +1,22 @@
+*,
+*::before,
+*::after {
+    box-sizing: border-box;
+    margin: 0;
+    font-weight: normal;
+}
+
+html {
+    --text-normal: #6b6b6b;
+    --text-hover: #000000;
+    height: 100%;
+}
+
+body {
+    --at-apply: text-normal;
+    height: 100%;
+}
+
+#app {
+    height: 100%;
+}
diff --git a/src/renderer/src/assets/style/global/_index.scss b/src/renderer/src/assets/style/global/_index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/renderer/src/assets/wavy-lines.svg b/src/renderer/src/assets/wavy-lines.svg
deleted file mode 100644
index d08c611..0000000
--- a/src/renderer/src/assets/wavy-lines.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1422 800" opacity="0.3">
-  <defs>
-    <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="oooscillate-grad">
-      <stop stop-color="hsl(206, 75%, 49%)" stop-opacity="1" offset="0%"></stop>
-      <stop stop-color="hsl(331, 90%, 56%)" stop-opacity="1" offset="100%"></stop>
-    </linearGradient>
-  </defs>
-  <g stroke-width="1" stroke="url(#oooscillate-grad)" fill="none" stroke-linecap="round">
-    <path d="M 0 448 Q 355.5 -100 711 400 Q 1066.5 900 1422 448" opacity="0.05"></path>
-    <path d="M 0 420 Q 355.5 -100 711 400 Q 1066.5 900 1422 420" opacity="0.11"></path>
-    <path d="M 0 392 Q 355.5 -100 711 400 Q 1066.5 900 1422 392" opacity="0.18"></path>
-    <path d="M 0 364 Q 355.5 -100 711 400 Q 1066.5 900 1422 364" opacity="0.24"></path>
-    <path d="M 0 336 Q 355.5 -100 711 400 Q 1066.5 900 1422 336" opacity="0.30"></path>
-    <path d="M 0 308 Q 355.5 -100 711 400 Q 1066.5 900 1422 308" opacity="0.37"></path>
-    <path d="M 0 280 Q 355.5 -100 711 400 Q 1066.5 900 1422 280" opacity="0.43"></path>
-    <path d="M 0 252 Q 355.5 -100 711 400 Q 1066.5 900 1422 252" opacity="0.49"></path>
-    <path d="M 0 224 Q 355.5 -100 711 400 Q 1066.5 900 1422 224" opacity="0.56"></path>
-    <path d="M 0 196 Q 355.5 -100 711 400 Q 1066.5 900 1422 196" opacity="0.62"></path>
-    <path d="M 0 168 Q 355.5 -100 711 400 Q 1066.5 900 1422 168" opacity="0.68"></path>
-    <path d="M 0 140 Q 355.5 -100 711 400 Q 1066.5 900 1422 140" opacity="0.75"></path>
-    <path d="M 0 112 Q 355.5 -100 711 400 Q 1066.5 900 1422 112" opacity="0.81"></path>
-    <path d="M 0 84 Q 355.5 -100 711 400 Q 1066.5 900 1422 84" opacity="0.87"></path>
-    <path d="M 0 56 Q 355.5 -100 711 400 Q 1066.5 900 1422 56" opacity="0.94"></path>
-  </g>
-</svg>
diff --git a/src/renderer/src/bridge/PopupMenu.ts b/src/renderer/src/bridge/PopupMenu.ts
new file mode 100644
index 0000000..c360f2f
--- /dev/null
+++ b/src/renderer/src/bridge/PopupMenu.ts
@@ -0,0 +1,66 @@
+/**
+ * ContextMenu
+ * @author: oldj
+ * @homepage: https://oldj.net
+ */
+
+import { IMenuItemOption } from "#"
+
+let _idx: number = 0
+
+type OffFunction = () => void
+
+export class PopupMenu {
+    private _id: string
+    private _items: IMenuItemOption[]
+    private _offs: any[] = []
+
+    constructor(menu_items: IMenuItemOption[]) {
+        this._id = `popup_menu_${Math.floor(Math.random() * 1e8)}`
+        this._items = menu_items
+    }
+
+    show() {
+        // console.log('show')
+        this.onHide()
+        // eslint-disable-next-line @typescript-eslint/no-this-alias
+        const that = this
+        function readMenu(_items: IMenuItemOption[]) {
+            return _items.map(i => {
+                const d = { ...i }
+                if (typeof d.click === "function") {
+                    const r = Math.floor(Math.random() * 1e8)
+                    const evt = `popup_menu_item_${_idx++}_${r}`
+                    const off = api.once(evt, d.click as any)
+                    that._offs.push(off)
+                    d._click_evt = evt
+                    delete d.click
+                }
+                if (d.submenu && Array.isArray(d.submenu)) {
+                    d.submenu = readMenu(d.submenu)
+                }
+                return d
+            })
+        }
+        const items = readMenu(this._items)
+
+        api.popupMenu({
+            menu_id: this._id,
+            items,
+        })
+        ;((offs: OffFunction[]) => {
+            api.once(`popup_menu_close:${this._id}`, () => {
+                // console.log(`on popup_menu_close:${this._id}`)
+                setTimeout(() => {
+                    offs.map(o => o())
+                }, 100)
+            })
+        })(this._offs)
+    }
+
+    private onHide() {
+        // console.log('hide...')
+        this._offs.map(o => o())
+        this._offs = []
+    }
+}
diff --git a/src/renderer/src/components/NavBar.vue b/src/renderer/src/components/NavBar.vue
new file mode 100644
index 0000000..da3ec07
--- /dev/null
+++ b/src/renderer/src/components/NavBar.vue
@@ -0,0 +1,25 @@
+<template>
+    <div relative h="30px" leading="29px" pr="137px" select-none border-b="1px solid #E5E5E5" bg="#F8F8F8">
+        <div absolute top-0 right-0 bottom-0 left-0 style="-webkit-app-region: drag"></div>
+        <div h-full px-2 flex items-center gap-1 justify-between>
+            <div flex items-center gap-1>
+                <img w="16px" h="16px" :src="icon" />
+                <div relative h-full inline-flex items-center text-sm>{{ config.app_title }}</div>
+            </div>
+            <div float-right h-full flex items-center relative style="-webkit-app-region: no-drag">
+                <div text-sm px-2 hover:rounded-md hover:bg-gray-2 hover:cursor-pointer text="hover:hover" @click="onClickAbout">关于</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import icon from "@res/icon.png"
+import config from "config"
+
+const onClickAbout = () => {
+    fetch("api://fuck/BasicService/showAbout")
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/renderer/src/global.d.ts b/src/renderer/src/global.d.ts
new file mode 100644
index 0000000..c3453cb
--- /dev/null
+++ b/src/renderer/src/global.d.ts
@@ -0,0 +1 @@
+declare const api
diff --git a/src/renderer/src/main.ts b/src/renderer/src/main.ts
index 1ecb799..e59cccc 100644
--- a/src/renderer/src/main.ts
+++ b/src/renderer/src/main.ts
@@ -1,6 +1,6 @@
 import "virtual:uno.css"
 import "@unocss/reset/normalize.css"
-import "./assets/main.css"
+import "@renderer/assets/style/_common.scss"
 
 import { createApp } from "vue"
 import App from "./App.vue"
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
new file mode 100644
index 0000000..417d2f8
--- /dev/null
+++ b/src/types/index.d.ts
@@ -0,0 +1,9 @@
+interface IMenuItemOption extends Electron.MenuItemConstructorOptions {
+    // 参见:https://www.electronjs.org/docs/api/menu-item
+    _click_evt?: string
+}
+
+export interface IPopupMenuOption {
+    menu_id: string
+    items: IMenuItemOption[]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 59bd72d..340f5f4 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -1,6 +1,6 @@
 {
   "extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
-  "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "config/**/*"],
+  "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "config/**/*", "src/types/**/*"],
   "compilerOptions": {
     "composite": true,
     "emitDecoratorMetadata": true,
@@ -8,6 +8,8 @@
     "types": ["electron-vite/node", "reflect-metadata"],
     "baseUrl": ".",
     "paths": {
+      "#": ["src/types/index"],
+      "#/*": ["src/types/*"],
       "config": ["config/index.ts"],
       "config/*": ["config/*"],
       "vc/*": ["src/main/*"],
diff --git a/tsconfig.web.json b/tsconfig.web.json
index e9d73a9..49fb572 100644
--- a/tsconfig.web.json
+++ b/tsconfig.web.json
@@ -4,15 +4,24 @@
     "src/renderer/src/env.d.ts",
     "src/renderer/src/**/*",
     "src/renderer/src/**/*.vue",
-    "src/preload/*.d.ts"
+    "src/preload/*.d.ts",
+    "src/types/**/*",
+    "config/**/*"
   ],
   "compilerOptions": {
     "composite": true,
     "baseUrl": ".",
     "paths": {
+      "#": ["src/types/index"],
+      "#/*": ["src/types/*"],
+      "config": ["config/index.ts"],
+      "config/*": ["config/*"],
       "@renderer/*": [
         "src/renderer/src/*"
-      ]
+      ],
+      "@res/*": [
+        "resources/*"
+      ],
     }
   }
 }
diff --git a/uno.config.ts b/uno.config.ts
index b2036fa..9c6f0aa 100644
--- a/uno.config.ts
+++ b/uno.config.ts
@@ -1,6 +1,76 @@
-import { defineConfig, presetAttributify, presetUno } from "unocss"
+import { defineConfig, presetAttributify, presetUno, transformerDirectives } from "unocss"
 import presetRemToPx from "@unocss/preset-rem-to-px"
 
 export default defineConfig({
     presets: [presetAttributify(), presetUno(), presetRemToPx()],
+    transformers: [transformerDirectives()],
+    shortcuts: [
+        // 正方形 square-100px
+        [
+            /^square-\[?(.*?)\]?$/,
+            ([, size]) => {
+                return `w-${size} h-${size}`
+            },
+        ],
+        // 圆形 circle-100px
+        [
+            /^circle-\[?(.*?)\]?$/,
+            ([, size]) => {
+                return `square-${size} rounded-full`
+            },
+        ],
+        // 垂直水平居中
+        ["flex-center", "flex justify-center items-center"],
+    ],
+    rules: [
+        [
+            /^text-(.*)$/,
+            ([, c]) => {
+                if (c === "normal") return { color: "var(--text-normal)" }
+                if (c === "hover") return { color: "var(--text-hover)" }
+            },
+        ],
+        // 多行文本超出部分省略号 line-n (已内置 line-clamp-n)
+        [
+            /^line-(\d+)$/,
+            ([, l]) => {
+                if (~~l === 1) {
+                    return {
+                        overflow: "hidden",
+                        "text-overflow": "ellipsis",
+                        "white-space": "nowrap",
+                        width: "100%",
+                    }
+                }
+                return {
+                    overflow: "hidden",
+                    display: "-webkit-box",
+                    "-webkit-box-orient": "vertical",
+                    "-webkit-line-clamp": l,
+                }
+            },
+        ],
+        // 一侧圆角 rounded-left-5px (已内置 rounded-l-n)
+        [
+            /^rounded-(left|right|top|bottom)-(.*?)$/,
+            ([, position, m]) => {
+                let x1, x2, y1, y2
+                if (["left", "right"].includes(position)) {
+                    y1 = "top"
+                    y2 = "bottom"
+                    x1 = x2 = position
+                } else {
+                    x1 = "left"
+                    x2 = "right"
+                    y1 = y2 = position
+                }
+                if (m === "full") m = "99999px"
+
+                return {
+                    [`border-${y1}-${x1}-radius`]: m,
+                    [`border-${y2}-${x2}-radius`]: m,
+                }
+            },
+        ],
+    ],
 })
diff --git a/推荐.md b/推荐.md
new file mode 100644
index 0000000..84b5413
--- /dev/null
+++ b/推荐.md
@@ -0,0 +1,15 @@
+
+插件化:
+
+https://rubickcenter.github.io/docs/core/index.html#%E5%9F%BA%E4%BA%8E-browserview-%E5%AE%9E%E7%8E%B0%E6%8F%92%E4%BB%B6%E5%8C%96%E8%83%BD%E5%8A%9B
+
+
+electron+vue虚拟桌面开发遇坑之透明窗口鼠标穿透
+
+
+https://blog.csdn.net/weixin_42421494/article/details/102800491
+
+
+截图
+
+https://zhuanlan.zhihu.com/p/46043613?from_voters_page=true