feat: 项目结构调整|新增依赖
This commit is contained in:
21
build/scripts/generateProdEntry.js
Normal file
21
build/scripts/generateProdEntry.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(process.cwd(), 'dist/main/build.jsc'))) {
|
||||||
|
throw new Error('字节码文件未找到,请先执行编译')
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
const {app} = require('electron');
|
||||||
|
require('bytenode');
|
||||||
|
require('./index.jsc);
|
||||||
|
`
|
||||||
|
|
||||||
|
const outputPath = path.join(process.cwd(), 'dist/main/build.js')
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
fs.writeFileSync(outputPath, content.trim())
|
||||||
|
console.log(`生产环境入口文件已生成: ${outputPath}`)
|
||||||
@@ -25,12 +25,12 @@ const config: ForgeConfig = {
|
|||||||
build: [
|
build: [
|
||||||
{
|
{
|
||||||
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
|
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
|
||||||
entry: 'src/main.ts',
|
entry: 'src/electron/main/main.ts',
|
||||||
config: 'vite.main.config.ts',
|
config: 'vite.main.config.ts',
|
||||||
target: 'main',
|
target: 'main',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
entry: 'src/preload.ts',
|
entry: 'src/electron/preload/preload.ts',
|
||||||
config: 'vite.preload.config.ts',
|
config: 'vite.preload.config.ts',
|
||||||
target: 'preload',
|
target: 'preload',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/renderer.ts"></script>
|
<script type="module" src="/src/renderer/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
110
package-lock.json
generated
110
package-lock.json
generated
@@ -11,7 +11,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"bytenode": "^1.5.7",
|
"bytenode": "^1.5.7",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"log4js": "^6.9.1",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
@@ -29,6 +32,7 @@
|
|||||||
"@tailwindcss/postcss": "^4.1.14",
|
"@tailwindcss/postcss": "^4.1.14",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/electron-squirrel-startup": "^1.0.2",
|
"@types/electron-squirrel-startup": "^1.0.2",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
@@ -2804,6 +2808,23 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.17.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||||
|
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash-es": {
|
||||||
|
"version": "4.17.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||||
|
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/mute-stream": {
|
"node_modules/@types/mute-stream": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/mute-stream/-/mute-stream-0.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/mute-stream/-/mute-stream-0.0.4.tgz",
|
||||||
@@ -4568,6 +4589,13 @@
|
|||||||
"node": ">=12.10"
|
"node": ">=12.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
|
||||||
|
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -4641,11 +4669,19 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-format": {
|
||||||
|
"version": "4.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
|
||||||
|
"integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -6438,7 +6474,6 @@
|
|||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
|
||||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/flora-colossus": {
|
"node_modules/flora-colossus": {
|
||||||
@@ -6977,7 +7012,6 @@
|
|||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/graphemer": {
|
"node_modules/graphemer": {
|
||||||
@@ -8355,6 +8389,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash-es": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.get": {
|
"node_modules/lodash.get": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz",
|
"resolved": "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
@@ -8496,6 +8536,22 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/log4js": {
|
||||||
|
"version": "6.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz",
|
||||||
|
"integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"date-format": "^4.0.14",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"flatted": "^3.2.7",
|
||||||
|
"rfdc": "^1.3.0",
|
||||||
|
"streamroller": "^3.1.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lowercase-keys": {
|
"node_modules/lowercase-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||||
@@ -8813,7 +8869,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/mute-stream": {
|
"node_modules/mute-stream": {
|
||||||
@@ -10297,7 +10352,6 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/rimraf": {
|
"node_modules/rimraf": {
|
||||||
@@ -10934,6 +10988,52 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/streamroller": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"date-format": "^4.0.14",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"fs-extra": "^8.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/streamroller/node_modules/fs-extra": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^4.0.0",
|
||||||
|
"universalify": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6 <7 || >=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/streamroller/node_modules/jsonfile": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"graceful-fs": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/streamroller/node_modules/universalify": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
"package": "electron-forge package",
|
"package": "electron-forge package",
|
||||||
"make": "electron-forge make",
|
"make": "electron-forge make",
|
||||||
"publish": "electron-forge publish",
|
"publish": "electron-forge publish",
|
||||||
"lint": "eslint --ext .ts,.tsx ."
|
"lint": "eslint --ext .ts,.tsx .",
|
||||||
|
"generate-prod-entry": "node build/scripts/generateProdEntry.js",
|
||||||
|
"build:encrypt": "npm run clean && npm run lint && npm run package && npm run generate-prod-entry"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": {
|
"author": {
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
"@tailwindcss/postcss": "^4.1.14",
|
"@tailwindcss/postcss": "^4.1.14",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/electron-squirrel-startup": "^1.0.2",
|
"@types/electron-squirrel-startup": "^1.0.2",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
@@ -45,7 +48,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"bytenode": "^1.5.7",
|
"bytenode": "^1.5.7",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"log4js": "^6.9.1",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
|
|||||||
6
shims-vue.d.ts
vendored
Normal file
6
shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
// 泛型参数:Props类型、Emits类型、Slots类型(默认任意)
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import started from "electron-squirrel-startup";
|
import started from "electron-squirrel-startup";
|
||||||
import { TabManager } from './controller/tab-manager'
|
import { TabManager } from '@modules/tab-manager'
|
||||||
import "./controller/window-size-controll";
|
import { logger } from '@modules/logger'
|
||||||
|
import "@modules/window-size";
|
||||||
|
|
||||||
if (started) {
|
if (started) {
|
||||||
app.quit();
|
app.quit();
|
||||||
@@ -17,6 +18,7 @@ class AppMain {
|
|||||||
this.registerLifecycle()
|
this.registerLifecycle()
|
||||||
this.registerCommonIPC()
|
this.registerCommonIPC()
|
||||||
this.registerAppIPC()
|
this.registerAppIPC()
|
||||||
|
this.registerLogIPC()
|
||||||
}
|
}
|
||||||
|
|
||||||
private createWindow(options?: { frameless?: boolean; route?: string }): BrowserWindow {
|
private createWindow(options?: { frameless?: boolean; route?: string }): BrowserWindow {
|
||||||
@@ -34,8 +36,8 @@ class AppMain {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
devTools: this.isDev,
|
devTools: this.isDev,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true, // 同时启动上下文隔离
|
||||||
sandbox: true,
|
sandbox: true, // 启动沙箱模式
|
||||||
preload: path.join(__dirname, "preload.js"),
|
preload: path.join(__dirname, "preload.js"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -55,6 +57,12 @@ class AppMain {
|
|||||||
} else {
|
} else {
|
||||||
win.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
|
win.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 暴露安全 API 示例:通过 IPC 处理文件读取
|
||||||
|
ipcMain.handle('read-file', async (event, filePath) => {
|
||||||
|
const fs = require('fs');
|
||||||
|
return fs.promises.readFile(filePath, 'utf-8'); // 主进程处理敏感操作
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private initTabsIfNeeded(options?: { frameless?: boolean; route?: string }) {
|
private initTabsIfNeeded(options?: { frameless?: boolean; route?: string }) {
|
||||||
@@ -118,7 +126,33 @@ class AppMain {
|
|||||||
ipcMain.handle('tab:switch', (_e, tabId: string) => tabs.switch(tabId))
|
ipcMain.handle('tab:switch', (_e, tabId: string) => tabs.switch(tabId))
|
||||||
ipcMain.handle('tab:close', (_e, tabId: string) => tabs.close(tabId))
|
ipcMain.handle('tab:close', (_e, tabId: string) => tabs.close(tabId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private registerLogIPC() {
|
||||||
|
ipcMain.handle('log-to-main', (_e, logLevel: string, message: string) => {
|
||||||
|
switch(logLevel) {
|
||||||
|
case 'trace':
|
||||||
|
logger.trace(message)
|
||||||
|
break
|
||||||
|
case 'debug':
|
||||||
|
logger.debug(message)
|
||||||
|
break
|
||||||
|
case 'info':
|
||||||
|
logger.info(message)
|
||||||
|
break
|
||||||
|
case 'warn':
|
||||||
|
logger.warn(message)
|
||||||
|
break
|
||||||
|
case 'error':
|
||||||
|
logger.error(message)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
logger.info(message)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new AppMain().init()
|
new AppMain().init()
|
||||||
|
|
||||||
|
|
||||||
95
src/electron/main/modules/ipc/index.ts
Normal file
95
src/electron/main/modules/ipc/index.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { ipcMain, BaseWindow } from 'electron'
|
||||||
|
|
||||||
|
type Handler = (...args:any[]) => any
|
||||||
|
type AsyncHandler = (...args:any[]) => Promise<any>
|
||||||
|
|
||||||
|
export class IPCManager {
|
||||||
|
private static instance: IPCManager
|
||||||
|
private handlers: Map<string, Handler>
|
||||||
|
private asyncHandlers: Map<string,AsyncHandler>
|
||||||
|
private constructor() {
|
||||||
|
this.handlers = new Map()
|
||||||
|
this.asyncHandlers =new Map()
|
||||||
|
this.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): IPCManager {
|
||||||
|
if (!IPCManager.instance) {
|
||||||
|
IPCManager.instance =new IPCManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
return IPCManager.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize(): void {
|
||||||
|
//注册同步处理器
|
||||||
|
ipcMain.on('ipc:invoke',(event, channel,...args) => {
|
||||||
|
try {
|
||||||
|
const handler = this.handlers.get(channel)
|
||||||
|
|
||||||
|
if(handler){
|
||||||
|
event.returnValue = handler(...args)
|
||||||
|
} else {
|
||||||
|
event.returnValue = { success: false, error: `No handler for channel: ${channel}`}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
event.returnValue = { success: false, error:(error as Error).message}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 注册异步处理器
|
||||||
|
ipcMain.handle('ipc:invokeAsync', async (_event, channel, ...args) => {
|
||||||
|
try {
|
||||||
|
const handler = this.asyncHandlers.get(channel)
|
||||||
|
|
||||||
|
if(handler){
|
||||||
|
return await handler(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`No async handler for channel: ${channel}`)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('get-window-id',(event) => {
|
||||||
|
event.returnValue = event.sender.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册同步处理器
|
||||||
|
public register(channel:string, handler:Handler):void {
|
||||||
|
this.handlers.set(channel, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册异步处理器
|
||||||
|
public registerAsync(channel:string, handler:AsyncHandler):void {
|
||||||
|
this.asyncHandlers.set(channel, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 广播消息给所有窗口
|
||||||
|
public broadcast(channel:string, ...args:any[]):void {
|
||||||
|
BaseWindow.getAllWindows().forEach(window => {
|
||||||
|
if (!window.isDestroyed()) {
|
||||||
|
window.webContents.send(channel, ...args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息给指定窗口
|
||||||
|
public sendToWindow(windowId: string, channel:string, ...args:any[]):void {
|
||||||
|
const window = BaseWindow.fromId(windowId)
|
||||||
|
|
||||||
|
if (window && !window.isDestroyed()) {
|
||||||
|
window.webContents.send(channel, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理所有处理器
|
||||||
|
public clear():void {
|
||||||
|
this.handlers.clear()
|
||||||
|
this.asyncHandlers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ipcManager = IPCManager.getInstance()
|
||||||
29
src/electron/main/modules/logger/index.ts
Normal file
29
src/electron/main/modules/logger/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as log4js from 'log4js';
|
||||||
|
|
||||||
|
log4js.configure({
|
||||||
|
appenders: {
|
||||||
|
out: {
|
||||||
|
type: 'stdout'
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
type: 'file',
|
||||||
|
filename: 'logs/app.log',
|
||||||
|
backups: 3,
|
||||||
|
compress: false,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
layout: {
|
||||||
|
type: 'pattern',
|
||||||
|
pattern: '[%d{yyyy-MM-dd hh:mm:ss.SSS}] [%p] %m'
|
||||||
|
},
|
||||||
|
keepFileExt: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
default: {
|
||||||
|
appenders: ['out', 'app'],
|
||||||
|
level: 'debug'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const logger = log4js.getLogger();
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron'
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
|
import { IPCChannel, IPCAPI } from '@/shared/types/ipc.types'
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
const api: IPCAPI = {
|
||||||
openBaidu: () => ipcRenderer.invoke('open-baidu')
|
openBaidu: () => ipcRenderer.invoke('open-baidu'),
|
||||||
})
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('api', {
|
|
||||||
versions: process.versions,
|
versions: process.versions,
|
||||||
|
|
||||||
external: {
|
external: {
|
||||||
open: (url: string) => ipcRenderer.invoke('external-open', url)
|
open: (url: string) => ipcRenderer.invoke('external-open', url)
|
||||||
},
|
},
|
||||||
|
|
||||||
window: {
|
window: {
|
||||||
minimize: () => ipcRenderer.send('window-min'),
|
minimize: () => ipcRenderer.send('window-min'),
|
||||||
maximize: () => ipcRenderer.send('window-max'),
|
maximize: () => ipcRenderer.send('window-max'),
|
||||||
close: () => ipcRenderer.send('window-close')
|
close: () => ipcRenderer.send('window-close')
|
||||||
},
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
setFrameless: (route?: string) => ipcRenderer.invoke('app:set-frameless', route)
|
setFrameless: (route?: string) => ipcRenderer.invoke('app:set-frameless', route)
|
||||||
},
|
},
|
||||||
|
|
||||||
tabs: {
|
tabs: {
|
||||||
create: (url?: string) => ipcRenderer.invoke('tab:create', url),
|
create: (url?: string) => ipcRenderer.invoke('tab:create', url),
|
||||||
list: () => ipcRenderer.invoke('tab:list'),
|
list: () => ipcRenderer.invoke('tab:list'),
|
||||||
@@ -31,5 +34,38 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
ipcRenderer.on(event, listener)
|
ipcRenderer.on(event, listener)
|
||||||
return () => ipcRenderer.removeListener(event, listener)
|
return () => ipcRenderer.removeListener(event, listener)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通过 IPC 调用主进程
|
||||||
|
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
|
||||||
|
|
||||||
|
// 同步调用
|
||||||
|
invoke: (channel: IPCChannel, ...args: any[]) => ipcRenderer.sendSync('ipc:invoke', channel, ...args),
|
||||||
|
|
||||||
|
// 异步调用
|
||||||
|
invokeAsync: (channel: IPCChannel, ...args: any[]) => {
|
||||||
|
try {
|
||||||
|
ipcRenderer.invoke('ipc:invokeAsync', channel, ...args)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
|
||||||
|
// 监听主进程消息
|
||||||
|
on: (event: IPCChannel, callback: (...args: any[]) => void) => {
|
||||||
|
const subscription = (_event: any, ...args: any[]) => callback(...args)
|
||||||
|
ipcRenderer.on(event, subscription)
|
||||||
|
return () => ipcRenderer.removeListener(event, subscription)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送消息到主进程
|
||||||
|
send: (channel: IPCChannel, ...args: any[]) => ipcRenderer.send(channel, ...args),
|
||||||
|
|
||||||
|
// 获取窗口ID
|
||||||
|
getCurrentWindowId: () => ipcRenderer.sendSync(IPCChannel.GET_WINDOW_ID),
|
||||||
|
|
||||||
|
// 发送日志
|
||||||
|
logToMain: (logLevel: string, message: string) => ipcRenderer.send('log-to-main', logLevel, message),
|
||||||
|
}
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('ipcAPI', api)
|
||||||
2
src/env.d.ts
vendored
2
src/env.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
declare module "@stores/counter";
|
|
||||||
declare module "@utils/request";
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { ipcMain } from 'electron'
|
|
||||||
import { TabManager } from '../controller/tab-manager'
|
|
||||||
|
|
||||||
export const registerTabIpc = (tabs: TabManager) => {
|
|
||||||
ipcMain.handle('tab:create', (_e, url?: string) => tabs.create(url))
|
|
||||||
ipcMain.handle('tab:list', () => tabs.list())
|
|
||||||
ipcMain.handle('tab:navigate', (_e, payload: { tabId: string; url: string }) => tabs.navigate(payload.tabId, payload.url))
|
|
||||||
ipcMain.handle('tab:reload', (_e, tabId: string) => tabs.reload(tabId))
|
|
||||||
ipcMain.handle('tab:back', (_e, tabId: string) => tabs.goBack(tabId))
|
|
||||||
ipcMain.handle('tab:forward', (_e, tabId: string) => tabs.goForward(tabId))
|
|
||||||
ipcMain.handle('tab:switch', (_e, tabId: string) => tabs.switch(tabId))
|
|
||||||
ipcMain.handle('tab:close', (_e, tabId: string) => tabs.close(tabId))
|
|
||||||
}
|
|
||||||
47
src/plugins/bytenode/vite-plugin-electron-encrypt.ts
Normal file
47
src/plugins/bytenode/vite-plugin-electron-encrypt.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import type { PluginOption } from 'vite'
|
||||||
|
import * as bytenode from 'bytenode'
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs-extra'
|
||||||
|
|
||||||
|
export interface ElectronBytecodeOptions {
|
||||||
|
entry?: string
|
||||||
|
keepSource?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function electronBytecode(options?: ElectronBytecodeOptions): PluginOption {
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-electron-encrypt',
|
||||||
|
apply: 'build',
|
||||||
|
async closeBundle() {
|
||||||
|
if (process.env.NODE_ENV !== 'production') return
|
||||||
|
const electronVersion = require('electron/package.json').version
|
||||||
|
const nodeVersion = process.version
|
||||||
|
const versionData = {
|
||||||
|
node: nodeVersion,
|
||||||
|
electron: electronVersion,
|
||||||
|
platform: process.platform,
|
||||||
|
arch: process.arch,
|
||||||
|
buildTime: new Date().toISOString()
|
||||||
|
}
|
||||||
|
const versionPath = path.resolve(process.cwd(), 'dist', 'version.json')
|
||||||
|
|
||||||
|
fs.ensureDirSync(path.dirname(versionPath))
|
||||||
|
fs.writeJsonSync(versionPath, versionData, { spaces: 2 })
|
||||||
|
|
||||||
|
const entryPath = path.resolve(process.cwd(), options?.entry || '.vite/build/main.js')
|
||||||
|
|
||||||
|
if (!fs.pathExistsSync(entryPath)) return
|
||||||
|
|
||||||
|
const outputPath = entryPath.replace(/\.js$/, '.jsc')
|
||||||
|
const backupPath = `${entryPath}.bak`
|
||||||
|
|
||||||
|
fs.copyFileSync(entryPath, backupPath)
|
||||||
|
|
||||||
|
await bytenode.compileFile({ filename: entryPath, output: outputPath })
|
||||||
|
|
||||||
|
if (!options?.keepSource) {
|
||||||
|
fs.removeSync(entryPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file will automatically be loaded by vite and run in the "renderer" context.
|
|
||||||
* To learn more about the differences between the "main" and the "renderer" context in
|
|
||||||
* Electron, visit:
|
|
||||||
*
|
|
||||||
* https://electronjs.org/docs/tutorial/process-model
|
|
||||||
*
|
|
||||||
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
|
|
||||||
* in a renderer process, please be aware of potential security implications. You can read
|
|
||||||
* more about security risks here:
|
|
||||||
*
|
|
||||||
* https://electronjs.org/docs/tutorial/security
|
|
||||||
*
|
|
||||||
* To enable Node.js integration in this file, open up `main.ts` and enable the `nodeIntegration`
|
|
||||||
* flag:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* // Create the browser window.
|
|
||||||
* mainWindow = new BrowserWindow({
|
|
||||||
* width: 800,
|
|
||||||
* height: 600,
|
|
||||||
* webPreferences: {
|
|
||||||
* nodeIntegration: true
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "./index.css";
|
|
||||||
import { createApp } from "vue";
|
|
||||||
import { createPinia } from "pinia";
|
|
||||||
import router from "./router";
|
|
||||||
import App from "./App.vue";
|
|
||||||
|
|
||||||
// 创建 Vue 应用实例
|
|
||||||
const app = createApp(App);
|
|
||||||
|
|
||||||
// 使用 Pinia 状态管理
|
|
||||||
app.use(createPinia());
|
|
||||||
|
|
||||||
// 使用 Vue Router
|
|
||||||
app.use(router);
|
|
||||||
|
|
||||||
// 挂载应用到 DOM
|
|
||||||
app.mount("#app");
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCounterStore } from "@stores/counter";
|
import { useCounterStore } from "@store/counter";
|
||||||
|
|
||||||
// 使用 Pinia store
|
// 使用 Pinia store
|
||||||
const counterStore = useCounterStore();
|
const counterStore = useCounterStore();
|
||||||
2
src/renderer/env.d.ts
vendored
Normal file
2
src/renderer/env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
declare module "@store/counter";
|
||||||
|
declare module "@utils/request";
|
||||||
17
src/renderer/main.ts
Normal file
17
src/renderer/main.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import "./index.css";
|
||||||
|
import { createApp } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
import router from "./router";
|
||||||
|
import App from "./App.vue";
|
||||||
|
|
||||||
|
// 创建 Vue 应用实例
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// 使用 Pinia 状态管理
|
||||||
|
app.use(createPinia());
|
||||||
|
|
||||||
|
// 使用 Vue Router
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
// 挂载应用到 DOM
|
||||||
|
app.mount("#app");
|
||||||
25
src/renderer/store/sharedStore.ts
Normal file
25
src/renderer/store/sharedStore.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { isEqual } from 'lodash-es'
|
||||||
|
|
||||||
|
export const useSharedStore = defineStore('shared', {
|
||||||
|
state: () => ({
|
||||||
|
sharedData: {},
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
updateSharedData(data: any) {
|
||||||
|
this.sharedData = Object.assign(this.sharedData, data)
|
||||||
|
|
||||||
|
const cloneData = JSON.parse(JSON.stringify(this.sharedData))
|
||||||
|
|
||||||
|
if (!isEqual(this.sharedData, data)) {
|
||||||
|
// 同步状态到主进程
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -38,26 +38,26 @@ const refreshActiveAddress = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const syncList = async () => {
|
const syncList = async () => {
|
||||||
const list: TabInfo[] = await (window as any).api.tabs.list()
|
const list: TabInfo[] = await (window as any).ipcAPI.tabs.list()
|
||||||
tabs.splice(0, tabs.length, ...list)
|
tabs.splice(0, tabs.length, ...list)
|
||||||
if (!activeId.value && list.length > 0) activeId.value = list[0].id
|
if (!activeId.value && list.length > 0) activeId.value = list[0].id
|
||||||
refreshActiveAddress()
|
refreshActiveAddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onNewTab = async () => {
|
const onNewTab = async () => {
|
||||||
const info: TabInfo = await (window as any).api.tabs.create('about:blank')
|
const info: TabInfo = await (window as any).ipcAPI.tabs.create('about:blank')
|
||||||
activeId.value = info.id
|
activeId.value = info.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSwitch = async (id: string) => {
|
const onSwitch = async (id: string) => {
|
||||||
await (window as any).api.tabs.switch(id)
|
await (window as any).ipcAPI.tabs.switch(id)
|
||||||
activeId.value = id
|
activeId.value = id
|
||||||
refreshActiveAddress()
|
refreshActiveAddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCloseTab = async () => {
|
const onCloseTab = async () => {
|
||||||
if (!activeId.value) return
|
if (!activeId.value) return
|
||||||
await (window as any).api.tabs.close(activeId.value)
|
await (window as any).ipcAPI.tabs.close(activeId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeUrl = (u: string) => {
|
const normalizeUrl = (u: string) => {
|
||||||
@@ -71,46 +71,46 @@ const onNavigate = async () => {
|
|||||||
if (!activeId.value || !address.value) return
|
if (!activeId.value || !address.value) return
|
||||||
const url = normalizeUrl(address.value)
|
const url = normalizeUrl(address.value)
|
||||||
if (!url) return
|
if (!url) return
|
||||||
await (window as any).api.tabs.navigate(activeId.value, url)
|
await (window as any).ipcAPI.tabs.navigate(activeId.value, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReload = async () => {
|
const onReload = async () => {
|
||||||
if (!activeId.value) return
|
if (!activeId.value) return
|
||||||
await (window as any).api.tabs.reload(activeId.value)
|
await (window as any).ipcAPI.tabs.reload(activeId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBack = async () => {
|
const onBack = async () => {
|
||||||
if (!activeId.value) return
|
if (!activeId.value) return
|
||||||
await (window as any).api.tabs.back(activeId.value)
|
await (window as any).ipcAPI.tabs.back(activeId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onForward = async () => {
|
const onForward = async () => {
|
||||||
if (!activeId.value) return
|
if (!activeId.value) return
|
||||||
await (window as any).api.tabs.forward(activeId.value)
|
await (window as any).ipcAPI.tabs.forward(activeId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCloseTabId = async (id: string) => {
|
const onCloseTabId = async (id: string) => {
|
||||||
await (window as any).api.tabs.close(id)
|
await (window as any).ipcAPI.tabs.close(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCloseWindow = () => (window as any).api.window.close()
|
const onCloseWindow = () => (window as any).ipcAPI.window.close()
|
||||||
const onMinimizeWindow = () => (window as any).api.window.minimize()
|
const onMinimizeWindow = () => (window as any).ipcAPI.window.minimize()
|
||||||
const onMaximizeWindow = () => (window as any).api.window.maximize()
|
const onMaximizeWindow = () => (window as any).ipcAPI.window.maximize()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await syncList()
|
await syncList()
|
||||||
;(window as any).api.tabs.on('tab-created', (info: TabInfo) => {
|
;(window as any).ipcAPI.tabs.on('tab-created', (info: TabInfo) => {
|
||||||
const i = tabs.findIndex(t => t.id === info.id)
|
const i = tabs.findIndex(t => t.id === info.id)
|
||||||
if (i === -1) tabs.push(info)
|
if (i === -1) tabs.push(info)
|
||||||
activeId.value = info.id
|
activeId.value = info.id
|
||||||
refreshActiveAddress()
|
refreshActiveAddress()
|
||||||
})
|
})
|
||||||
;(window as any).api.tabs.on('tab-updated', (info: TabInfo) => {
|
;(window as any).ipcAPI.tabs.on('tab-updated', (info: TabInfo) => {
|
||||||
const i = tabs.findIndex(t => t.id === info.id)
|
const i = tabs.findIndex(t => t.id === info.id)
|
||||||
if (i >= 0) tabs[i] = info
|
if (i >= 0) tabs[i] = info
|
||||||
if (activeId.value === info.id) refreshActiveAddress()
|
if (activeId.value === info.id) refreshActiveAddress()
|
||||||
})
|
})
|
||||||
;(window as any).api.tabs.on('tab-closed', ({ tabId }: { tabId: string }) => {
|
;(window as any).ipcAPI.tabs.on('tab-closed', ({ tabId }: { tabId: string }) => {
|
||||||
const i = tabs.findIndex(t => t.id === tabId)
|
const i = tabs.findIndex(t => t.id === tabId)
|
||||||
if (i >= 0) tabs.splice(i, 1)
|
if (i >= 0) tabs.splice(i, 1)
|
||||||
if (activeId.value === tabId) {
|
if (activeId.value === tabId) {
|
||||||
@@ -119,7 +119,7 @@ onMounted(async () => {
|
|||||||
refreshActiveAddress()
|
refreshActiveAddress()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
;(window as any).api.tabs.on('tab-switched', ({ tabId }: { tabId: string }) => {
|
;(window as any).ipcAPI.tabs.on('tab-switched', ({ tabId }: { tabId: string }) => {
|
||||||
activeId.value = tabId
|
activeId.value = tabId
|
||||||
refreshActiveAddress()
|
refreshActiveAddress()
|
||||||
})
|
})
|
||||||
@@ -103,7 +103,7 @@ tab-switched: { tabId: TabId }
|
|||||||
1. 核心能力(主进程 + IPC + 预加载)
|
1. 核心能力(主进程 + IPC + 预加载)
|
||||||
- 实现 `TabManager` 与全部导航方法
|
- 实现 `TabManager` 与全部导航方法
|
||||||
- 注册 IPC(tabs/bookmarks/plugins)与广播
|
- 注册 IPC(tabs/bookmarks/plugins)与广播
|
||||||
- 预加载扩展 `window.api.tabs/*`、事件订阅封装
|
- 预加载扩展 `window.ipcAPI.tabs/*`、事件订阅封装
|
||||||
2. 渲染层界面
|
2. 渲染层界面
|
||||||
- 新建 `BrowserLayout` 与基础组件(TabBar、AddressBar、Controls)
|
- 新建 `BrowserLayout` 与基础组件(TabBar、AddressBar、Controls)
|
||||||
- 打通地址栏与导航;同步标题与加载状态
|
- 打通地址栏与导航;同步标题与加载状态
|
||||||
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const openBaidu = () => {
|
const openBaidu = () => {
|
||||||
(window as any).electronAPI?.openBaidu()
|
(window as any).ipcAPI?.openBaidu()
|
||||||
|
|
||||||
|
// 发送日志
|
||||||
|
(window as any).ipcAPI?.logToMain('info', '打开百度')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from "vue";
|
import { ref, reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { login as apiLogin } from "@/api/login";
|
import { login as apiLogin } from "@/renderer/api/login";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = reactive({ account: "", password: "" });
|
const form = reactive({ account: "", password: "" });
|
||||||
@@ -80,7 +80,7 @@ const onSubmit = async () => {
|
|||||||
// const token = res && (res.token || res.data?.token || res.access_token);
|
// const token = res && (res.token || res.data?.token || res.access_token);
|
||||||
// if (!token) throw new Error("登录失败");
|
// if (!token) throw new Error("登录失败");
|
||||||
// localStorage.setItem("token", token);
|
// localStorage.setItem("token", token);
|
||||||
await (window as any).api.app.setFrameless('/browser')
|
await (window as any).ipcAPI.app.setFrameless('/browser')
|
||||||
} finally {
|
} finally {
|
||||||
// loading.value = false;
|
// loading.value = false;
|
||||||
}
|
}
|
||||||
56
src/shared/types/ipc.types.ts
Normal file
56
src/shared/types/ipc.types.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
export enum IPCChannel {
|
||||||
|
APP_MINIMIZE ='app:minimize',
|
||||||
|
APP_MAXIMIZE ='app:maximize',
|
||||||
|
APP_QUIT ='app:quit',
|
||||||
|
FILE_READ = 'file:read',
|
||||||
|
FILE_WRITE = 'file:write',
|
||||||
|
GET_WINDOW_ID='get-window-id',
|
||||||
|
CUSTOM_EVENT ='custom:event',
|
||||||
|
TIME_UPDATE = 'time:update'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义每个通道的参数和返回值类型
|
||||||
|
export interface IPCTypings {
|
||||||
|
// 同步通信
|
||||||
|
[IPCChannel.APP_MINIMIZE]: {
|
||||||
|
params: [window:number]
|
||||||
|
return: {success: boolean, error?: string}
|
||||||
|
}
|
||||||
|
[IPCChannel.APP_MAXIMIZE]: {
|
||||||
|
params: [window:number]
|
||||||
|
return: {success: boolean, error?: string}
|
||||||
|
}
|
||||||
|
[IPCChannel.GET_WINDOW_ID]: {
|
||||||
|
params: []
|
||||||
|
return: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步通信
|
||||||
|
[IPCChannel.FILE_READ]: {
|
||||||
|
params: [filePath: string]
|
||||||
|
return: Promise<{success: boolean, data?: string, error?: string}>
|
||||||
|
}
|
||||||
|
[IPCChannel.FILE_WRITE]: {
|
||||||
|
params: [filePath: string, content: string]
|
||||||
|
return: Promise<{success: boolean, error?: string}>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件通信
|
||||||
|
[IPCChannel.TIME_UPDATE]: {
|
||||||
|
params: [time: string]
|
||||||
|
return: void
|
||||||
|
}
|
||||||
|
[IPCChannel.CUSTOM_EVENT]: {
|
||||||
|
params: [message: string]
|
||||||
|
return: void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义IPC API 接口
|
||||||
|
export interface IPCAPI {
|
||||||
|
invoke<T extends keyof IPCTypings>(channel: T, ...args: IPCTypings[T]['params']): IPCTypings[T]['return'],
|
||||||
|
invokeAsync<T extends keyof IPCTypings>(channel: T, ...args: IPCTypings[T]['params']): IPCTypings[T]['return'],
|
||||||
|
on<T extends keyof IPCTypings>(channel: T, callback: (...args: IPCTypings[T]['params']) => void): () => void
|
||||||
|
send<T extends keyof IPCTypings>(channel: T, ...args: IPCTypings[T]['params']): void,
|
||||||
|
getCurrentWindowId(): number
|
||||||
|
}
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@stores/*": ["./src/stores/*"],
|
"@store/*": ["src/renderer/store/*"],
|
||||||
"@utils/*": ["./src/utils/*"],
|
"@utils/*": ["src/renderer/utils/*"],
|
||||||
"@api/*": ["./src/api/*"],
|
"@api/*": ["src/renderer/api/*"],
|
||||||
"@types/*": ["./src/types/*"]
|
"@modules/*": ["src/electron/main/modules/*"],
|
||||||
},
|
},
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|||||||
@@ -5,11 +5,8 @@ import { resolve } from "path";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": resolve(__dirname, "./src"),
|
"@": resolve(__dirname, "./src/electron"),
|
||||||
"@stores": resolve(__dirname, "./src/stores"),
|
"@modules": resolve(__dirname, "./src/electron/main/modules"),
|
||||||
"@utils": resolve(__dirname, "./src/utils"),
|
|
||||||
"@api": resolve(__dirname, "./src/api"),
|
|
||||||
"@types": resolve(__dirname, "./src/types"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
// https://vitejs.dev/config
|
// https://vitejs.dev/config
|
||||||
export default defineConfig({});
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": resolve(__dirname, "./src")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -13,11 +13,10 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
preserveSymlinks: true,
|
preserveSymlinks: true,
|
||||||
alias: {
|
alias: {
|
||||||
"@": resolve(__dirname, "./src"),
|
"@": resolve(__dirname, "./src/renderer"),
|
||||||
"@stores": resolve(__dirname, "./src/stores"),
|
"@store": resolve(__dirname, "./src/renderer/store"),
|
||||||
"@utils": resolve(__dirname, "./src/utils"),
|
"@utils": resolve(__dirname, "./src/renderer/utils"),
|
||||||
"@api": resolve(__dirname, "./src/api"),
|
"@api": resolve(__dirname, "./src/renderer/api"),
|
||||||
"@types": resolve(__dirname, "./src/types"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user