Compare commits
2 Commits
fcad3681b4
...
e969ec2236
Author | SHA1 | Date |
---|---|---|
|
e969ec2236 | 1 day ago |
|
a9de1ec525 | 3 days ago |
46 changed files with 759 additions and 433 deletions
@ -0,0 +1,123 @@ |
|||
type FireFN = (...argu: any[]) => void |
|||
|
|||
// 监听器类型定义,支持优先级
|
|||
interface Listener<F extends FireFN> { |
|||
fn: F |
|||
once: boolean |
|||
priority: number |
|||
} |
|||
|
|||
class FireEvent<T extends Record<string | symbol, FireFN>> { |
|||
// 使用 Map 存储事件监听器,支持 symbol 键
|
|||
private events = new Map<keyof T, Array<Listener<T[keyof T]>>>() |
|||
|
|||
// 获取事件监听器列表,如果不存在则创建
|
|||
private getListeners<S extends keyof T>(name: S): Array<Listener<T[S]>> { |
|||
if (!this.events.has(name)) { |
|||
this.events.set(name, []) |
|||
} |
|||
return this.events.get(name) as Array<Listener<T[S]>> |
|||
} |
|||
|
|||
// 按优先级排序监听器
|
|||
private sortListeners<S extends keyof T>(name: S) { |
|||
const listeners = this.getListeners(name) |
|||
listeners.sort((a, b) => b.priority - a.priority) |
|||
} |
|||
|
|||
// 打印事件和监听器信息
|
|||
print() { |
|||
console.log("Registered Events:") |
|||
this.events.forEach((listeners, name) => { |
|||
// 显式处理 symbol 类型
|
|||
const keyType = typeof name === "symbol" ? `Symbol(${name.description || ""})` : String(name) |
|||
console.log(` ${keyType}: ${listeners.length} listeners`) |
|||
}) |
|||
} |
|||
|
|||
// 添加事件监听器,支持优先级
|
|||
on<S extends keyof T>(name: S, fn: T[S], priority = 0): this { |
|||
const listeners = this.getListeners(name) |
|||
listeners.push({ fn, once: false, priority }) |
|||
this.sortListeners(name) |
|||
return this // 支持链式调用
|
|||
} |
|||
|
|||
// 触发事件
|
|||
emit<S extends keyof T>(name: S, ...args: Parameters<T[S]>): this { |
|||
const listeners = this.getListeners(name).slice() // 创建副本以避免移除时的问题
|
|||
|
|||
for (const { fn } of listeners) { |
|||
try { |
|||
fn(...args) |
|||
} catch (error) { |
|||
console.error(`Error in event handler for ${String(name)}:`, error) |
|||
} |
|||
} |
|||
|
|||
// 移除一次性监听器
|
|||
if (listeners.some(l => l.once)) { |
|||
this.events.set( |
|||
name, |
|||
this.getListeners(name).filter(l => !l.once), |
|||
) |
|||
} |
|||
|
|||
return this |
|||
} |
|||
|
|||
// 移除事件监听器
|
|||
off<S extends keyof T>(name: S, fn?: T[S]): this { |
|||
if (!this.events.has(name)) return this |
|||
|
|||
const listeners = this.getListeners(name) |
|||
|
|||
if (!fn) { |
|||
// 移除所有监听器
|
|||
this.events.delete(name) |
|||
} else { |
|||
// 移除特定监听器
|
|||
const filtered = listeners.filter(l => l.fn !== fn) |
|||
if (filtered.length === 0) { |
|||
this.events.delete(name) |
|||
} else { |
|||
this.events.set(name, filtered) |
|||
} |
|||
} |
|||
|
|||
return this |
|||
} |
|||
|
|||
// 添加一次性事件监听器
|
|||
once<S extends keyof T>(name: S, fn: T[S], priority = 0): this { |
|||
const listeners = this.getListeners(name) |
|||
listeners.push({ fn, once: true, priority }) |
|||
this.sortListeners(name) |
|||
return this |
|||
} |
|||
|
|||
// 清除所有事件监听器
|
|||
clear(): this { |
|||
this.events.clear() |
|||
return this |
|||
} |
|||
|
|||
// 获取指定事件的监听器数量
|
|||
listenerCount<S extends keyof T>(name: S): number { |
|||
return this.events.get(name)?.length || 0 |
|||
} |
|||
|
|||
// 检查事件是否有监听器
|
|||
hasListeners<S extends keyof T>(name: S): boolean { |
|||
return this.listenerCount(name) > 0 |
|||
} |
|||
|
|||
// 获取所有事件名称
|
|||
eventNames(): Array<keyof T> { |
|||
return Array.from(this.events.keys()) |
|||
} |
|||
} |
|||
|
|||
export function buildEmitter<T extends Record<string | symbol, FireFN>>() { |
|||
return new FireEvent<T>() |
|||
} |
@ -0,0 +1,51 @@ |
|||
// type FireKey = string
|
|||
type FireFN = (...argu: any[]) => void |
|||
|
|||
class FireEvent<T extends Record<string | symbol, FireFN>> { |
|||
#events: Record<keyof T, FireFN[]> = {} as any |
|||
print() { |
|||
Object.keys(this.#events).forEach(key => { |
|||
console.log(`${key}: ${this.#events[key]}\n`) |
|||
}) |
|||
} |
|||
on<S extends keyof T>(name: S, fn: T[S]) { |
|||
if (!this.#events[name]) { |
|||
this.#events[name] = [] |
|||
} |
|||
this.#events[name].push(fn) |
|||
} |
|||
emit<S extends keyof T>(name: S, ...argu: Parameters<T[S]>) { |
|||
if (this.#events[name]) { |
|||
this.#events[name].forEach(fn => { |
|||
fn(...argu) |
|||
}) |
|||
} |
|||
} |
|||
off<S extends keyof T>(name: S, fn?: T[S]) { |
|||
const len = this.#events[name].length |
|||
if (!len) { |
|||
return |
|||
} |
|||
if (!fn) { |
|||
this.#events[name] = [] |
|||
} else { |
|||
for (let i = len - 1; i >= 0; i--) { |
|||
const _fn = this.#events[name][i] |
|||
if (_fn === fn) { |
|||
this.#events[name].splice(i, 1) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
once<S extends keyof T>(name: S, fn: T[S]) { |
|||
const _fn: any = (...argu: any[]) => { |
|||
fn(...argu) |
|||
this.off<S>(name, _fn) |
|||
} |
|||
this.on(name, _fn) |
|||
} |
|||
} |
|||
|
|||
export function buildEmitter<T extends Record<string | symbol, FireFN>>() { |
|||
return new FireEvent<T>() |
|||
} |
@ -0,0 +1,27 @@ |
|||
// 抽象基类,使用泛型来正确推导子类类型
|
|||
abstract class BaseSingleton { |
|||
private static _instance: any |
|||
|
|||
public constructor() { |
|||
if (this.constructor === BaseSingleton) { |
|||
throw new Error("禁止直接实例化 BaseOne 抽象类") |
|||
} |
|||
|
|||
if ((this.constructor as any)._instance) { |
|||
throw new Error("构造函数私有化失败,禁止重复 new") |
|||
} |
|||
|
|||
// this.constructor 是子类,所以这里设为 instance
|
|||
;(this.constructor as any)._instance = this |
|||
} |
|||
|
|||
public static getInstance<T extends BaseSingleton>(this: new () => T): T { |
|||
const clazz = this as any as typeof BaseSingleton |
|||
if (!clazz._instance) { |
|||
clazz._instance = new this() |
|||
} |
|||
return clazz._instance as T |
|||
} |
|||
} |
|||
|
|||
export { BaseSingleton } |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"name": "base", |
|||
"version": "1.0.0", |
|||
"description": "", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"test": "echo \"Error: no test specified\" && exit 1" |
|||
}, |
|||
"keywords": [], |
|||
"author": "", |
|||
"license": "ISC", |
|||
"packageManager": "pnpm@10.4.1" |
|||
} |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"name": "helper", |
|||
"version": "1.0.0", |
|||
"description": "", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"test": "echo \"Error: no test specified\" && exit 1" |
|||
}, |
|||
"keywords": [], |
|||
"author": "", |
|||
"license": "ISC", |
|||
"packageManager": "pnpm@10.4.1" |
|||
} |
@ -0,0 +1,7 @@ |
|||
export const enum EventEnum { |
|||
UPDATE_PROGRESS = "update-progress", |
|||
} |
|||
|
|||
export type EventMaps = { |
|||
[EventEnum.UPDATE_PROGRESS]: () => void |
|||
} |
@ -0,0 +1,8 @@ |
|||
import { broadcast } from "main/utils" |
|||
import { EventEnum } from "../common" |
|||
|
|||
export { EventEnum } |
|||
|
|||
export function emit(key: EventEnum, ...args: any[]) { |
|||
broadcast(key, ...args) |
|||
} |
@ -0,0 +1,74 @@ |
|||
type DownloadPercent = { |
|||
url: string |
|||
option?: object |
|||
onprocess?: (now: number, all: number) => void |
|||
onsuccess?: (data: any) => void |
|||
onerror?: (res: Response) => void |
|||
} |
|||
const RequestPercent = async ({ |
|||
url = "", |
|||
option = { |
|||
headers: { |
|||
responseType: "arraybuffer", |
|||
}, |
|||
}, |
|||
onsuccess, |
|||
onerror, |
|||
onprocess, |
|||
}: DownloadPercent) => { |
|||
const response = (await fetch(url, option)) as any |
|||
if (!response.ok) { |
|||
onerror?.(response) |
|||
throw new Error(`下载失败`) |
|||
} |
|||
const reader = response?.body.getReader() |
|||
|
|||
// 文件总长度
|
|||
const contentLength = +response.headers.get("content-length") |
|||
|
|||
let receivedLength = 0 |
|||
const chunks: any[] = [] |
|||
// eslint-disable-next-line no-constant-condition
|
|||
while (true) { |
|||
const { done, value } = await reader.read() |
|||
|
|||
if (done) { |
|||
break |
|||
} |
|||
|
|||
chunks.push(value) |
|||
receivedLength += value.length |
|||
onprocess?.(receivedLength, contentLength) |
|||
} |
|||
// 这里的chunksAll 已经是ArrayBuffer的数据类型了,可以直接返回,也可以转为blob处理
|
|||
const chunksAll = new Uint8Array(receivedLength) |
|||
let position = 0 |
|||
for (const chunk of chunks) { |
|||
chunksAll.set(chunk, position) |
|||
position += chunk.length |
|||
} |
|||
|
|||
onsuccess?.(chunksAll) |
|||
|
|||
return chunksAll |
|||
} |
|||
|
|||
export { RequestPercent } |
|||
export default RequestPercent |
|||
|
|||
// RequestPercent({
|
|||
// url: "http://117.21.250.136:9812/ZxqyGateway/biz/file/downApk/%E6%98%93%E4%BC%81%E6%95%B0%E8%BD%AC%E5%B9%B3%E5%8F%B0app-1.2.7.apk",
|
|||
// option: {
|
|||
// headers: {
|
|||
// responseType: "arraybuffer",
|
|||
// },
|
|||
// },
|
|||
// onerror: () => {},
|
|||
// onsuccess: data => {
|
|||
// fs.writeFileSync("./aaa.apk", Buffer.from(data))
|
|||
// console.log("success", data)
|
|||
// },
|
|||
// onprocess: (receivedLength, contentLength) => {
|
|||
// console.log(receivedLength, contentLength)
|
|||
// },
|
|||
// })
|
@ -0,0 +1,6 @@ |
|||
import { buildEmitter } from "base/event/main" |
|||
import type { IOnFunc } from "setting/main" |
|||
|
|||
export const emitter = buildEmitter<{ |
|||
update: IOnFunc |
|||
}>() |
@ -1,5 +1,5 @@ |
|||
import { Snippet } from "." |
|||
|
|||
export function useSnippet() { |
|||
return Snippet.getInstance<Snippet>() |
|||
return Snippet.getInstance() |
|||
} |
|||
|
@ -0,0 +1,15 @@ |
|||
import { EventEnum } from "helper/updater/common" |
|||
|
|||
const curProgress = ref(0) |
|||
|
|||
api.on(EventEnum.UPDATE_PROGRESS, ({ percent, now, all }) => { |
|||
curProgress.value = percent |
|||
}) |
|||
|
|||
function useUpdate() { |
|||
return { |
|||
curProgress, |
|||
} |
|||
} |
|||
|
|||
export { useUpdate } |
@ -0,0 +1,15 @@ |
|||
import Updater from "helper/updater/main" |
|||
import _logger from "logger/main" |
|||
|
|||
const logger = _logger.createNamespace("UpdaterCommand") |
|||
|
|||
export default class UpdaterCommand { |
|||
static init() { |
|||
// 命令初始化
|
|||
logger.debug("UpdaterCommand init") |
|||
} |
|||
|
|||
async triggerHotUpdate() { |
|||
Updater.triggerHotUpdate() |
|||
} |
|||
} |
@ -1,5 +0,0 @@ |
|||
const keys = ["hot-update-ready"] as const |
|||
|
|||
type AllKeys = (typeof keys)[number] |
|||
|
|||
export type { AllKeys } |
@ -1,14 +0,0 @@ |
|||
// import type { AllKeys } from "../common"
|
|||
|
|||
const curProgress = ref(0) |
|||
// api.on<AllKeys>("progress", () => {
|
|||
// curProgress.value = 10
|
|||
// })
|
|||
|
|||
function useUpdate() { |
|||
return { |
|||
curProgress, |
|||
} |
|||
} |
|||
|
|||
export { useUpdate } |
@ -1,10 +0,0 @@ |
|||
import { inject } from "inversify" |
|||
import Updater from "main/modules/updater" |
|||
|
|||
export default class PlatFormCommand { |
|||
constructor(@inject(Updater) private _Updater: Updater) {} |
|||
|
|||
async triggerHotUpdate() { |
|||
await this._Updater.triggerHotUpdate() |
|||
} |
|||
} |
@ -1,8 +0,0 @@ |
|||
import { broadcast } from "main/utils" |
|||
import { AllKeys } from "common/event/common" |
|||
|
|||
function emitHotUpdateReady(...argu) { |
|||
broadcast<AllKeys>("hot-update-ready", ...argu) |
|||
} |
|||
|
|||
export { emitHotUpdateReady } |
@ -1,12 +0,0 @@ |
|||
export abstract class _Base { |
|||
static instance |
|||
|
|||
static getInstance<T>(): T { |
|||
if (!this.instance) { |
|||
// 如果实例不存在,则创建一个新的实例
|
|||
// @ts-ignore ...
|
|||
this.instance = new this() |
|||
} |
|||
return this.instance |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
## event |
|||
|
|||
通用事件处理模块 |
|||
|
|||
- main/**/* 处理主进程的模块 |
|||
- main/command.ts 会通过ioc收集,进入依赖管理中 |
|||
- 其他 处理渲染进程的模块 |
@ -0,0 +1,60 @@ |
|||
import debug from "debug" |
|||
import { app } from "electron" |
|||
import path from "node:path" |
|||
import logger from "logger/main" |
|||
import * as rfs from "rotating-file-stream" |
|||
import fs from "fs" |
|||
|
|||
// 配置根目录
|
|||
const logsPath = app.getPath("logs") |
|||
logger.debug(`日志地址:${logsPath}`) |
|||
|
|||
const LOG_ROOT = path.join(logsPath) |
|||
|
|||
// 缓存当前应用启动的日志文件流
|
|||
let currentLogStream: rfs.RotatingFileStream | null = null |
|||
|
|||
// 生成当前启动时的日志文件名
|
|||
const getLogFileName = () => { |
|||
const now = new Date() |
|||
const timestamp = now.toISOString().replace(/[:.]/g, "-") |
|||
return `app-${timestamp}.log` |
|||
} |
|||
|
|||
// 覆盖 debug.log 方法
|
|||
const originalLog = debug.log |
|||
debug.log = function (...args) { |
|||
// 保留原始控制台输出
|
|||
originalLog.apply(this, args) |
|||
|
|||
// 确保日志目录存在
|
|||
if (!fs.existsSync(LOG_ROOT)) { |
|||
fs.mkdirSync(LOG_ROOT, { recursive: true }) |
|||
} |
|||
|
|||
// 延迟初始化日志流,直到第一次写入
|
|||
if (!currentLogStream) { |
|||
const logFileName = getLogFileName() |
|||
currentLogStream = rfs.createStream(logFileName, { |
|||
path: LOG_ROOT, |
|||
size: "10M", // 单个文件最大 10MB
|
|||
rotate: 10, // 保留最近 10 个文件
|
|||
}) |
|||
} |
|||
|
|||
// @ts-ignore 获取当前命名空间
|
|||
const namespace = this.namespace || "unknown" |
|||
|
|||
// 写入日志(添加时间戳和命名空间)
|
|||
const timestamp = new Date().toISOString() |
|||
const message = args.join(" ") |
|||
currentLogStream.write(`[${timestamp}] [${namespace}] ${message}\n`) |
|||
} |
|||
|
|||
app.on("before-quit", () => { |
|||
if (currentLogStream) { |
|||
currentLogStream.end() |
|||
currentLogStream.destroy() |
|||
currentLogStream = null |
|||
} |
|||
}) |
Loading…
Reference in new issue