diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 99966b9..ac1e039 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,34 +1,23 @@ +const { readFileSync, readFile } = require("node:fs") + /* eslint-env node */ require("@rushstack/eslint-patch/modern-module-resolution") +const prettierConfig = JSON.parse(readFileSync("./.prettierrc.json", { encoding: "utf-8" })) + module.exports = { - extends: [ - "eslint:recommended", - "plugin:vue/vue3-recommended", - "@electron-toolkit", - "@electron-toolkit/eslint-config-ts/eslint-recommended", - "@vue/eslint-config-typescript/recommended", - "@vue/eslint-config-prettier", - ], - rules: { - "vue/require-default-prop": "off", - "vue/multi-word-component-names": "off", - "@typescript-eslint/no-explicit-any": "off", - "prettier/prettier": [ - "error", - { - tabWidth: 4, - useTabs: false, - semi: false, - singleQuote: false, - trailingComma: "all", - bracketSpacing: true, - arrowParens: "avoid", - printWidth: 140, - htmlWhitespaceSensitivity: "ignore", - proseWrap: "preserve", - endOfLine: "auto", - }, - ], - }, + extends: [ + "eslint:recommended", + "plugin:vue/vue3-recommended", + "@electron-toolkit", + "@electron-toolkit/eslint-config-ts/eslint-recommended", + "@vue/eslint-config-typescript/recommended", + "@vue/eslint-config-prettier", + ], + rules: { + "vue/require-default-prop": "off", + "vue/multi-word-component-names": "off", + "@typescript-eslint/no-explicit-any": "off", + "prettier/prettier": ["error", prettierConfig], + }, } diff --git a/.prettierrc b/.prettierrc index eab9949..ed0ee55 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "tabWidth": 4, + "tabWidth": 2, "useTabs": false, "semi": false, "singleQuote": false, @@ -9,5 +9,24 @@ "printWidth": 140, "htmlWhitespaceSensitivity": "ignore", "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 + } + } + ] } diff --git a/config/index.ts b/config/index.ts index b59754c..aa7e86b 100644 --- a/config/index.ts +++ b/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 { app_title: string - default_config: { - 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 - } + default_config: IDefaultConfig } + +// 默认配置导出 export default { app_title: "zephyr", // 和风 default_config: { @@ -29,4 +41,4 @@ export default { "update.allowDowngrade": false, "update.allowPrerelease": false, }, -} as IConfig +} as const satisfies IConfig diff --git a/packages/locales/index.ts b/packages/locales/index.ts index e518c7b..dae47c4 100644 --- a/packages/locales/index.ts +++ b/packages/locales/index.ts @@ -1,41 +1,41 @@ if (import.meta.env.DEV) { - // 引入之后可以热更新 - import("./languages/zh.json") - import("./languages/en.json") + // 引入之后可以热更新 + import("./languages/zh.json") + import("./languages/en.json") } const datetimeFormats = { - en: { - short: { - year: "numeric", - month: "short", - day: "numeric", - }, - long: { - year: "numeric", - month: "short", - day: "numeric", - weekday: "short", - hour: "numeric", - minute: "numeric", - }, + en: { + short: { + year: "numeric", + month: "short", + day: "numeric", }, - zh: { - short: { - year: "numeric", - month: "short", - day: "numeric", - }, - long: { - year: "numeric", - month: "short", - day: "numeric", - weekday: "short", - hour: "numeric", - minute: "numeric", - hour12: true, - }, + long: { + year: "numeric", + month: "short", + day: "numeric", + weekday: "short", + hour: "numeric", + minute: "numeric", }, + }, + 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 } diff --git a/packages/locales/main.ts b/packages/locales/main.ts index d217258..d49c0be 100644 --- a/packages/locales/main.ts +++ b/packages/locales/main.ts @@ -5,47 +5,47 @@ import zh from "./languages/zh.json" import en from "./languages/en.json" type FlattenObject = T extends object - ? { - [K in keyof T & (string | number)]: FlattenObject - }[keyof T & (string | number)] - : Prefix + ? { + [K in keyof T & (string | number)]: FlattenObject + }[keyof T & (string | number)] + : Prefix type FlattenKeys = FlattenObject type TranslationKey = FlattenKeys class Locale { - locale: string = "zh" - - constructor() { - try { - this.locale = app.getLocale() - } catch (e) { - console.log(e) - } - } + locale: string = "zh" - isCN(): boolean { - return this.locale.startsWith("zh") + constructor() { + try { + this.locale = app.getLocale() + } catch (e) { + console.log(e) } - - t(key: TranslationKey, replacements?: Record): string { - let text: string = this.isCN() ? get(zh, key) : get(en, key) - if (!text) { - text = get(zh, key) - if (!text) { - return key - } - } - if (replacements) { - // 替换所有形如 {key} 的占位符 - Object.entries(replacements).forEach(([key, value]) => { - console.log(text) - text = text.replace(new RegExp(`{${key}}`, "g"), value) - }) - } - return text + } + + isCN(): boolean { + return this.locale.startsWith("zh") + } + + t(key: TranslationKey, replacements?: Record): string { + let text: string = this.isCN() ? get(zh, key) : get(en, key) + if (!text) { + text = get(zh, key) + if (!text) { + return key + } + } + if (replacements) { + // 替换所有形如 {key} 的占位符 + Object.entries(replacements).forEach(([key, value]) => { + console.log(text) + text = text.replace(new RegExp(`{${key}}`, "g"), value) + }) } + return text + } } const Locales = new Locale() diff --git a/src/main/commands/_ioc.ts b/src/common/_ioc.main.ts similarity index 60% rename from src/main/commands/_ioc.ts rename to src/common/_ioc.main.ts index 8c00409..5efa038 100644 --- a/src/main/commands/_ioc.ts +++ b/src/common/_ioc.main.ts @@ -1,13 +1,11 @@ import { Container, ContainerModule } from "inversify" -import BasicCommand from "./BasicCommand" -import TabsCommand from "./TabsCommand" -import UpdateCommand from "./UpdateCommand" - -// TODO 考虑迁移,将所有命令都注册common/event中 +import UpdateCommand from "common/event/Update/main/command" +import PlatFormCommand from "common/event/PlatForm/main/command" +import TabsCommand from "common/event/Tabs/main/command" const modules = new ContainerModule(bind => { - bind("BasicCommand").to(BasicCommand).inSingletonScope() bind("TabsCommand").to(TabsCommand).inSingletonScope() + bind("PlatFormCommand").to(PlatFormCommand).inSingletonScope() bind("UpdateCommand").to(UpdateCommand).inSingletonScope() }) diff --git a/src/common/event/PlatForm/index.ts b/src/common/event/PlatForm/index.ts index 2edf73e..267076a 100644 --- a/src/common/event/PlatForm/index.ts +++ b/src/common/event/PlatForm/index.ts @@ -1,24 +1,29 @@ import { _Base } from "common/lib/_Base" +import { ApiFactory } from "common/lib/abstract" class PlatForm extends _Base { constructor() { super() } + private get api() { + return ApiFactory.getApiClient() + } + async showAbout() { - return await fetch("api://fuck/BasicService/showAbout") + return this.api.call("BasicService.showAbout") } async isFullScreen() { - return await api.call("BasicCommand.isFullscreen") + return this.api.call("PlatFormCommand.isFullscreen") } async toggleFullScreen() { - return api.call("BasicCommand.fullscreen") + return this.api.call("PlatFormCommand.fullscreen") } async toggleDevTools() { - return api.call("BasicCommand.toggleDevTools") + return this.api.call("PlatFormCommand.toggleDevTools") } } diff --git a/src/main/commands/BasicCommand.ts b/src/common/event/PlatForm/main/command.ts similarity index 95% rename from src/main/commands/BasicCommand.ts rename to src/common/event/PlatForm/main/command.ts index c4c0fe1..46f7670 100644 --- a/src/main/commands/BasicCommand.ts +++ b/src/common/event/PlatForm/main/command.ts @@ -3,7 +3,7 @@ import { inject } from "inversify" import Tabs from "main/modules/tabs" import WindowManager from "main/modules/window-manager" -export default class BasicCommand { +export default class PlatFormCommand { constructor( @inject(WindowManager) private _WindowManager: WindowManager, @inject(Tabs) private _Tabs: Tabs, @@ -20,6 +20,10 @@ export default class BasicCommand { } } + showAbout() { + this._WindowManager.showWindow("about") + } + toggleDevTools() { const focusedWindow = this._WindowManager.getFocusWindow() if (focusedWindow) { diff --git a/src/main/commands/TabsCommand.ts b/src/common/event/Tabs/main/command.ts similarity index 100% rename from src/main/commands/TabsCommand.ts rename to src/common/event/Tabs/main/command.ts diff --git a/src/common/event/update/index.ts b/src/common/event/update/index.ts index 5d11315..20f0ad3 100644 --- a/src/common/event/update/index.ts +++ b/src/common/event/update/index.ts @@ -1,9 +1,9 @@ -import type { AllKeys } from "../common" +// import type { AllKeys } from "../common" const curProgress = ref(0) -api.on("progress", () => { - curProgress.value = 10 -}) +// api.on("progress", () => { +// curProgress.value = 10 +// }) function useUpdate() { return { diff --git a/src/main/commands/UpdateCommand.ts b/src/common/event/update/main/command.ts similarity index 85% rename from src/main/commands/UpdateCommand.ts rename to src/common/event/update/main/command.ts index d967bc7..5407dc3 100644 --- a/src/main/commands/UpdateCommand.ts +++ b/src/common/event/update/main/command.ts @@ -1,7 +1,7 @@ import { inject } from "inversify" import Updater from "main/modules/updater" -export default class BasicCommand { +export default class PlatFormCommand { constructor(@inject(Updater) private _Updater: Updater) {} async triggerHotUpdate() { diff --git a/src/common/event/update/main.ts b/src/common/event/update/main/index.ts similarity index 78% rename from src/common/event/update/main.ts rename to src/common/event/update/main/index.ts index 5f40482..aa3132d 100644 --- a/src/common/event/update/main.ts +++ b/src/common/event/update/main/index.ts @@ -1,5 +1,5 @@ import { broadcast } from "main/utils" -import { AllKeys } from "../common" +import { AllKeys } from "common/event/common" function emitHotUpdateReady(...argu) { broadcast("hot-update-ready", ...argu) diff --git a/src/common/lib/abstract.ts b/src/common/lib/abstract.ts new file mode 100644 index 0000000..9712b8a --- /dev/null +++ b/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(command: string, ...args: any[]): Promise + on(channel: K, callback: (...args: any[]) => void): void + off(channel: K, callback: (...args: any[]) => void): void + offAll(channel: K): void +} + +class NullApiClient implements IApiClient { + async call(command: string, ...args: any[]): Promise { + args + console.warn(`API call to ${command} failed: API client not initialized`) + return undefined as any + } + + on(channel: K, callback: (...args: any[]) => void): void { + callback + console.warn(`Failed to register listener for ${channel}: API client not initialized`) + } + + off(channel: K, callback: (...args: any[]) => void): void { + callback + console.warn(`Failed to unregister listener for ${channel}: API client not initialized`) + } + + offAll(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 + } +} diff --git a/src/common/lib/browser.ts b/src/common/lib/browser.ts new file mode 100644 index 0000000..100e60d --- /dev/null +++ b/src/common/lib/browser.ts @@ -0,0 +1,29 @@ +import { IApiClient } from "./abstract" + +export class BrowserApiClient implements IApiClient { + call(command: string, ...args: any[]): Promise { + // 浏览器特定实现,可能使用 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(channel: K, callback: (...args: any[]) => void): void { + // 浏览器中可能使用 WebSocket 或其他方式 + console.log("不支持 on 方法", channel, callback) + } + + off(channel: K, callback: (...args: any[]) => void): void { + // 相应的解绑实现 + console.log("不支持 on 方法", channel, callback) + } + + offAll(channel: K): void { + // 相应的全部解绑实现 + console.log("不支持 on 方法", channel) + } +} diff --git a/src/common/lib/electron.ts b/src/common/lib/electron.ts new file mode 100644 index 0000000..27908a5 --- /dev/null +++ b/src/common/lib/electron.ts @@ -0,0 +1,20 @@ +import { IApiClient } from "./abstract" + +export class ElectronApiClient implements IApiClient { + call(command: string, ...args: any[]): Promise { + // Electron 特定实现 + return window.api.call(command, ...args) + } + + on(channel: K, callback: (...args: any[]) => void): void { + window.api.on(channel, callback) + } + + off(channel: K, callback: (...args: any[]) => void): void { + window.api.off(channel, callback) + } + + offAll(channel: K): void { + window.api.offAll(channel) + } +} diff --git a/src/main/App.ts b/src/main/App.ts index 8dc4b67..b440f86 100644 --- a/src/main/App.ts +++ b/src/main/App.ts @@ -70,8 +70,8 @@ class App extends BaseClass { this._Zephyr.init() electronApp.setAppUserModelId("top.xieyaxin") this._WindowManager.showMainWindow() - this._Command.invoke("BasicCommand.setTheme", "light") - this._Command.invoke("BasicCommand.setTitlBar", { + this._Command.invoke("PlatFormCommand.setTheme", "light") + this._Command.invoke("PlatFormCommand.setTitlBar", { height: 29, color: "#F8F8F8", symbolColor: "#000000", diff --git a/src/main/_ioc.ts b/src/main/_ioc.ts index 6d5311c..7613d44 100644 --- a/src/main/_ioc.ts +++ b/src/main/_ioc.ts @@ -2,7 +2,7 @@ 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 iocCommand, { destroyAllCommand } from "common/_ioc.main" import App from "./App" async function destroyAll() { diff --git a/src/main/commands/SettingCommand.ts b/src/main/commands/SettingCommand.ts deleted file mode 100644 index 6ff9095..0000000 --- a/src/main/commands/SettingCommand.ts +++ /dev/null @@ -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() -// } -// } diff --git a/src/renderer/src/main.ts b/src/renderer/src/main.ts index 904ede1..1102c55 100644 --- a/src/renderer/src/main.ts +++ b/src/renderer/src/main.ts @@ -10,6 +10,19 @@ import router from "./router" import i18n from "./i18n" 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(router as any) +app.use(router) app.mount("#app") diff --git a/tsconfig.node.json b/tsconfig.node.json index c967ae0..78ada96 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -8,6 +8,7 @@ "src/types/**/*", "packages/locales/main.ts", "src/common/**/*.main.ts", + "src/common/**/main/**/*", "src/common/**/main.ts", "src/common/**/*.common.ts", "src/common/**/common.ts" diff --git a/tsconfig.web.json b/tsconfig.web.json index ad767d6..778190b 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -15,6 +15,7 @@ ], "exclude": [ "packages/locales/main.ts", + "src/common/**/main/**/*", "src/common/**/*.main.ts", "src/common/**/main.ts" ],