Browse Source

refactor(命令模块): 将命令模块从主进程迁移到通用模块,并重构相关代码

将 `BasicCommand`、`TabsCommand` 和 `UpdateCommand` 从主进程迁移到 `common/event` 目录下,统一管理命令模块。同时,重构了 `PlatForm` 模块,使其使用新的 `ApiFactory` 进行 API 调用,提升了代码的可维护性和复用性。
feat/icon
npmrun 3 weeks ago
parent
commit
7246ab2d9a
  1. 47
      .eslintrc.cjs
  2. 23
      .prettierrc
  3. 40
      config/index.ts
  4. 64
      packages/locales/index.ts
  5. 64
      packages/locales/main.ts
  6. 10
      src/common/_ioc.main.ts
  7. 13
      src/common/event/PlatForm/index.ts
  8. 6
      src/common/event/PlatForm/main/command.ts
  9. 0
      src/common/event/Tabs/main/command.ts
  10. 8
      src/common/event/update/index.ts
  11. 2
      src/common/event/update/main/command.ts
  12. 2
      src/common/event/update/main/index.ts
  13. 53
      src/common/lib/abstract.ts
  14. 29
      src/common/lib/browser.ts
  15. 20
      src/common/lib/electron.ts
  16. 4
      src/main/App.ts
  17. 2
      src/main/_ioc.ts
  18. 12
      src/main/commands/SettingCommand.ts
  19. 15
      src/renderer/src/main.ts
  20. 1
      tsconfig.node.json
  21. 1
      tsconfig.web.json

47
.eslintrc.cjs

@ -1,34 +1,23 @@
const { readFileSync, readFile } = require("node:fs")
/* eslint-env node */ /* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution") require("@rushstack/eslint-patch/modern-module-resolution")
const prettierConfig = JSON.parse(readFileSync("./.prettierrc.json", { encoding: "utf-8" }))
module.exports = { module.exports = {
extends: [ extends: [
"eslint:recommended", "eslint:recommended",
"plugin:vue/vue3-recommended", "plugin:vue/vue3-recommended",
"@electron-toolkit", "@electron-toolkit",
"@electron-toolkit/eslint-config-ts/eslint-recommended", "@electron-toolkit/eslint-config-ts/eslint-recommended",
"@vue/eslint-config-typescript/recommended", "@vue/eslint-config-typescript/recommended",
"@vue/eslint-config-prettier", "@vue/eslint-config-prettier",
], ],
rules: { rules: {
"vue/require-default-prop": "off", "vue/require-default-prop": "off",
"vue/multi-word-component-names": "off", "vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"prettier/prettier": [ "prettier/prettier": ["error", prettierConfig],
"error", },
{
tabWidth: 4,
useTabs: false,
semi: false,
singleQuote: false,
trailingComma: "all",
bracketSpacing: true,
arrowParens: "avoid",
printWidth: 140,
htmlWhitespaceSensitivity: "ignore",
proseWrap: "preserve",
endOfLine: "auto",
},
],
},
} }

23
.prettierrc

@ -1,5 +1,5 @@
{ {
"tabWidth": 4, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"semi": false, "semi": false,
"singleQuote": false, "singleQuote": false,
@ -9,5 +9,24 @@
"printWidth": 140, "printWidth": 140,
"htmlWhitespaceSensitivity": "ignore", "htmlWhitespaceSensitivity": "ignore",
"proseWrap": "preserve", "proseWrap": "preserve",
"endOfLine": "auto" "endOfLine": "auto",
"vueIndentScriptAndStyle": true,
"embeddedLanguageFormatting": "auto",
"jsxSingleQuote": false,
"jsxBracketSameLine": false,
"quoteProps": "as-needed",
"overrides": [
{
"files": "*.json",
"options": {
"printWidth": 80
}
},
{
"files": ["*.vue", "*.tsx"],
"options": {
"singleAttributePerLine": false
}
}
]
} }

40
config/index.ts

@ -1,19 +1,31 @@
// 定义主题类型
type ThemeType = "light" | "dark" | "auto"
// 定义语言类型
type LanguageType = "zh" | "en"
// 定义编辑器logo类型
type LogoType = "logo" | "bg"
// 配置接口定义
interface IDefaultConfig {
language: LanguageType
"common.theme": ThemeType
"desktop:wallpaper": string
"update.repo"?: string
"update.owner"?: string
"update.allowDowngrade": boolean
"update.allowPrerelease": boolean
"editor.bg": string
"editor.logoType": LogoType
"editor.fontFamily": string
storagePath: string
}
interface IConfig { interface IConfig {
app_title: string app_title: string
default_config: { default_config: IDefaultConfig
language: "zh" | "en"
"common.theme": "light" | "dark" | "auto"
"desktop:wallpaper": string
"update.repo"?: string
"update.owner"?: string
"update.allowDowngrade": boolean
"update.allowPrerelease": boolean
"editor.bg": string
"editor.logoType": "logo" | "bg"
"editor.fontFamily": string
storagePath: string
}
} }
// 默认配置导出
export default { export default {
app_title: "zephyr", // 和风 app_title: "zephyr", // 和风
default_config: { default_config: {
@ -29,4 +41,4 @@ export default {
"update.allowDowngrade": false, "update.allowDowngrade": false,
"update.allowPrerelease": false, "update.allowPrerelease": false,
}, },
} as IConfig } as const satisfies IConfig

64
packages/locales/index.ts

@ -1,41 +1,41 @@
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
// 引入之后可以热更新 // 引入之后可以热更新
import("./languages/zh.json") import("./languages/zh.json")
import("./languages/en.json") import("./languages/en.json")
} }
const datetimeFormats = { const datetimeFormats = {
en: { en: {
short: { short: {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric", day: "numeric",
},
long: {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "numeric",
},
}, },
zh: { long: {
short: { year: "numeric",
year: "numeric", month: "short",
month: "short", day: "numeric",
day: "numeric", weekday: "short",
}, hour: "numeric",
long: { minute: "numeric",
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "numeric",
hour12: true,
},
}, },
},
zh: {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "numeric",
hour12: true,
},
},
} }
export { datetimeFormats } export { datetimeFormats }

64
packages/locales/main.ts

@ -5,47 +5,47 @@ import zh from "./languages/zh.json"
import en from "./languages/en.json" import en from "./languages/en.json"
type FlattenObject<T, Prefix extends string = ""> = T extends object type FlattenObject<T, Prefix extends string = ""> = T extends object
? { ? {
[K in keyof T & (string | number)]: FlattenObject<T[K], Prefix extends "" ? `${K}` : `${Prefix}.${K}`> [K in keyof T & (string | number)]: FlattenObject<T[K], Prefix extends "" ? `${K}` : `${Prefix}.${K}`>
}[keyof T & (string | number)] }[keyof T & (string | number)]
: Prefix : Prefix
type FlattenKeys<T> = FlattenObject<T> type FlattenKeys<T> = FlattenObject<T>
type TranslationKey = FlattenKeys<typeof zh> type TranslationKey = FlattenKeys<typeof zh>
class Locale { class Locale {
locale: string = "zh" locale: string = "zh"
constructor() {
try {
this.locale = app.getLocale()
} catch (e) {
console.log(e)
}
}
isCN(): boolean { constructor() {
return this.locale.startsWith("zh") try {
this.locale = app.getLocale()
} catch (e) {
console.log(e)
} }
}
t(key: TranslationKey, replacements?: Record<string, string>): string {
let text: string = this.isCN() ? get(zh, key) : get(en, key) isCN(): boolean {
if (!text) { return this.locale.startsWith("zh")
text = get(zh, key) }
if (!text) {
return key t(key: TranslationKey, replacements?: Record<string, string>): string {
} let text: string = this.isCN() ? get(zh, key) : get(en, key)
} if (!text) {
if (replacements) { text = get(zh, key)
// 替换所有形如 {key} 的占位符 if (!text) {
Object.entries(replacements).forEach(([key, value]) => { return key
console.log(text) }
text = text.replace(new RegExp(`{${key}}`, "g"), value) }
}) if (replacements) {
} // 替换所有形如 {key} 的占位符
return text Object.entries(replacements).forEach(([key, value]) => {
console.log(text)
text = text.replace(new RegExp(`{${key}}`, "g"), value)
})
} }
return text
}
} }
const Locales = new Locale() const Locales = new Locale()

10
src/main/commands/_ioc.ts → src/common/_ioc.main.ts

@ -1,13 +1,11 @@
import { Container, ContainerModule } from "inversify" import { Container, ContainerModule } from "inversify"
import BasicCommand from "./BasicCommand" import UpdateCommand from "common/event/Update/main/command"
import TabsCommand from "./TabsCommand" import PlatFormCommand from "common/event/PlatForm/main/command"
import UpdateCommand from "./UpdateCommand" import TabsCommand from "common/event/Tabs/main/command"
// TODO 考虑迁移,将所有命令都注册common/event中
const modules = new ContainerModule(bind => { const modules = new ContainerModule(bind => {
bind("BasicCommand").to(BasicCommand).inSingletonScope()
bind("TabsCommand").to(TabsCommand).inSingletonScope() bind("TabsCommand").to(TabsCommand).inSingletonScope()
bind("PlatFormCommand").to(PlatFormCommand).inSingletonScope()
bind("UpdateCommand").to(UpdateCommand).inSingletonScope() bind("UpdateCommand").to(UpdateCommand).inSingletonScope()
}) })

13
src/common/event/PlatForm/index.ts

@ -1,24 +1,29 @@
import { _Base } from "common/lib/_Base" import { _Base } from "common/lib/_Base"
import { ApiFactory } from "common/lib/abstract"
class PlatForm extends _Base { class PlatForm extends _Base {
constructor() { constructor() {
super() super()
} }
private get api() {
return ApiFactory.getApiClient()
}
async showAbout() { async showAbout() {
return await fetch("api://fuck/BasicService/showAbout") return this.api.call("BasicService.showAbout")
} }
async isFullScreen() { async isFullScreen() {
return await api.call("BasicCommand.isFullscreen") return this.api.call("PlatFormCommand.isFullscreen")
} }
async toggleFullScreen() { async toggleFullScreen() {
return api.call("BasicCommand.fullscreen") return this.api.call("PlatFormCommand.fullscreen")
} }
async toggleDevTools() { async toggleDevTools() {
return api.call("BasicCommand.toggleDevTools") return this.api.call("PlatFormCommand.toggleDevTools")
} }
} }

6
src/main/commands/BasicCommand.ts → src/common/event/PlatForm/main/command.ts

@ -3,7 +3,7 @@ import { inject } from "inversify"
import Tabs from "main/modules/tabs" import Tabs from "main/modules/tabs"
import WindowManager from "main/modules/window-manager" import WindowManager from "main/modules/window-manager"
export default class BasicCommand { export default class PlatFormCommand {
constructor( constructor(
@inject(WindowManager) private _WindowManager: WindowManager, @inject(WindowManager) private _WindowManager: WindowManager,
@inject(Tabs) private _Tabs: Tabs, @inject(Tabs) private _Tabs: Tabs,
@ -20,6 +20,10 @@ export default class BasicCommand {
} }
} }
showAbout() {
this._WindowManager.showWindow("about")
}
toggleDevTools() { toggleDevTools() {
const focusedWindow = this._WindowManager.getFocusWindow() const focusedWindow = this._WindowManager.getFocusWindow()
if (focusedWindow) { if (focusedWindow) {

0
src/main/commands/TabsCommand.ts → src/common/event/Tabs/main/command.ts

8
src/common/event/update/index.ts

@ -1,9 +1,9 @@
import type { AllKeys } from "../common" // import type { AllKeys } from "../common"
const curProgress = ref(0) const curProgress = ref(0)
api.on<AllKeys>("progress", () => { // api.on<AllKeys>("progress", () => {
curProgress.value = 10 // curProgress.value = 10
}) // })
function useUpdate() { function useUpdate() {
return { return {

2
src/main/commands/UpdateCommand.ts → src/common/event/update/main/command.ts

@ -1,7 +1,7 @@
import { inject } from "inversify" import { inject } from "inversify"
import Updater from "main/modules/updater" import Updater from "main/modules/updater"
export default class BasicCommand { export default class PlatFormCommand {
constructor(@inject(Updater) private _Updater: Updater) {} constructor(@inject(Updater) private _Updater: Updater) {}
async triggerHotUpdate() { async triggerHotUpdate() {

2
src/common/event/update/main.ts → src/common/event/update/main/index.ts

@ -1,5 +1,5 @@
import { broadcast } from "main/utils" import { broadcast } from "main/utils"
import { AllKeys } from "../common" import { AllKeys } from "common/event/common"
function emitHotUpdateReady(...argu) { function emitHotUpdateReady(...argu) {
broadcast<AllKeys>("hot-update-ready", ...argu) broadcast<AllKeys>("hot-update-ready", ...argu)

53
src/common/lib/abstract.ts

@ -0,0 +1,53 @@
import { ElectronApiClient } from "common/lib/electron"
import { BrowserApiClient } from "common/lib/browser"
// 定义抽象 API 接口
export interface IApiClient {
call<T = any>(command: string, ...args: any[]): Promise<T>
on<K extends string>(channel: K, callback: (...args: any[]) => void): void
off<K extends string>(channel: K, callback: (...args: any[]) => void): void
offAll<K extends string>(channel: K): void
}
class NullApiClient implements IApiClient {
async call<T = any>(command: string, ...args: any[]): Promise<T> {
args
console.warn(`API call to ${command} failed: API client not initialized`)
return undefined as any
}
on<K extends string>(channel: K, callback: (...args: any[]) => void): void {
callback
console.warn(`Failed to register listener for ${channel}: API client not initialized`)
}
off<K extends string>(channel: K, callback: (...args: any[]) => void): void {
callback
console.warn(`Failed to unregister listener for ${channel}: API client not initialized`)
}
offAll<K extends string>(channel: K): void {
console.warn(`Failed to unregister all listeners for ${channel}: API client not initialized`)
}
}
// 创建 API 工厂
export class ApiFactory {
private static instance: IApiClient = new NullApiClient() // 默认使用空实现
static setApiClient(client: IApiClient) {
this.instance = client
}
static getApiClient(): IApiClient {
if (this.instance instanceof NullApiClient) {
// 根据环境选择合适的 API 客户端
if (window.api && window.electron) {
this.instance = new ElectronApiClient()
} else {
this.instance = new BrowserApiClient()
}
}
return this.instance
}
}

29
src/common/lib/browser.ts

@ -0,0 +1,29 @@
import { IApiClient } from "./abstract"
export class BrowserApiClient implements IApiClient {
call<T = any>(command: string, ...args: any[]): Promise<T> {
// 浏览器特定实现,可能使用 fetch 或其他方式
const [service, method] = command.split(".")
return fetch(`/api/${service}/${method}`, {
method: "POST",
body: JSON.stringify(args),
headers: { "Content-Type": "application/json" },
}).then(res => res.json())
}
// 实现其他方法...
on<K extends string>(channel: K, callback: (...args: any[]) => void): void {
// 浏览器中可能使用 WebSocket 或其他方式
console.log("不支持 on 方法", channel, callback)
}
off<K extends string>(channel: K, callback: (...args: any[]) => void): void {
// 相应的解绑实现
console.log("不支持 on 方法", channel, callback)
}
offAll<K extends string>(channel: K): void {
// 相应的全部解绑实现
console.log("不支持 on 方法", channel)
}
}

20
src/common/lib/electron.ts

@ -0,0 +1,20 @@
import { IApiClient } from "./abstract"
export class ElectronApiClient implements IApiClient {
call<T = any>(command: string, ...args: any[]): Promise<T> {
// Electron 特定实现
return window.api.call(command, ...args)
}
on<K extends string>(channel: K, callback: (...args: any[]) => void): void {
window.api.on(channel, callback)
}
off<K extends string>(channel: K, callback: (...args: any[]) => void): void {
window.api.off(channel, callback)
}
offAll<K extends string>(channel: K): void {
window.api.offAll(channel)
}
}

4
src/main/App.ts

@ -70,8 +70,8 @@ class App extends BaseClass {
this._Zephyr.init() this._Zephyr.init()
electronApp.setAppUserModelId("top.xieyaxin") electronApp.setAppUserModelId("top.xieyaxin")
this._WindowManager.showMainWindow() this._WindowManager.showMainWindow()
this._Command.invoke("BasicCommand.setTheme", "light") this._Command.invoke("PlatFormCommand.setTheme", "light")
this._Command.invoke("BasicCommand.setTitlBar", { this._Command.invoke("PlatFormCommand.setTitlBar", {
height: 29, height: 29,
color: "#F8F8F8", color: "#F8F8F8",
symbolColor: "#000000", symbolColor: "#000000",

2
src/main/_ioc.ts

@ -2,7 +2,7 @@ import IOC from "./_iocClass"
import { Container } from "inversify" import { Container } from "inversify"
import iocModules, { destroyAllModules } from "./modules/_ioc" import iocModules, { destroyAllModules } from "./modules/_ioc"
import iocController, { destroyAllController } from "./controller/_ioc" import iocController, { destroyAllController } from "./controller/_ioc"
import iocCommand, { destroyAllCommand } from "./commands/_ioc" import iocCommand, { destroyAllCommand } from "common/_ioc.main"
import App from "./App" import App from "./App"
async function destroyAll() { async function destroyAll() {

12
src/main/commands/SettingCommand.ts

@ -1,12 +0,0 @@
// import { inject } from "inversify"
// import Setting from "main/modules/setting"
// class SettingCommand {
// constructor(@inject(Setting) private _Setting: Setting) {
// console.log(this._Setting)
// }
// getAll() {
// return this._Setting.config()
// }
// }

15
src/renderer/src/main.ts

@ -10,6 +10,19 @@ import router from "./router"
import i18n from "./i18n" import i18n from "./i18n"
const app = createApp(App) const app = createApp(App)
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error("应用错误:", err)
console.info("错误信息:", info)
// 可以添加错误上报逻辑
}
// 开发环境下的性能监控
if (import.meta.env.DEV) {
app.config.performance = true
}
app.use(i18n) app.use(i18n)
app.use(router as any) app.use(router)
app.mount("#app") app.mount("#app")

1
tsconfig.node.json

@ -8,6 +8,7 @@
"src/types/**/*", "src/types/**/*",
"packages/locales/main.ts", "packages/locales/main.ts",
"src/common/**/*.main.ts", "src/common/**/*.main.ts",
"src/common/**/main/**/*",
"src/common/**/main.ts", "src/common/**/main.ts",
"src/common/**/*.common.ts", "src/common/**/*.common.ts",
"src/common/**/common.ts" "src/common/**/common.ts"

1
tsconfig.web.json

@ -15,6 +15,7 @@
], ],
"exclude": [ "exclude": [
"packages/locales/main.ts", "packages/locales/main.ts",
"src/common/**/main/**/*",
"src/common/**/*.main.ts", "src/common/**/*.main.ts",
"src/common/**/main.ts" "src/common/**/main.ts"
], ],

Loading…
Cancel
Save