diff --git a/miniprogram/app.ts b/miniprogram/app.ts index f5d5c29..004bd19 100644 --- a/miniprogram/app.ts +++ b/miniprogram/app.ts @@ -1,15 +1,23 @@ +import { IAppAPIParam } from "./core/Api"; import { Logger } from "./core/Logger"; import { LevelLogLabel, LifeCycleLogLabel } from "./core/PresetLogLabel"; -import { Storage } from "./core/Storage"; +App({ -App({ + /** + * API 模块需要的全局数据 + * 参见 "/core/Api" + */ + api: { + nextId: 1, + pool: [] + }, /** * 存储缓存键值 */ - storageCache: new Set(), + // storageCache: new Set(), /** * 小程序加载时 @@ -17,14 +25,5 @@ App({ onLaunch() { Logger.log("小程序启动...", LevelLogLabel.TraceLabel, LifeCycleLogLabel.OnLaunchLabel); - - let s = new Storage("test", { - a: new Date(), - be: 2 - }); - - setTimeout(() => { - s.set("be", 12); - }, 1000) } }) \ No newline at end of file diff --git a/miniprogram/core/Api.ts b/miniprogram/core/Api.ts new file mode 100644 index 0000000..041df1b --- /dev/null +++ b/miniprogram/core/Api.ts @@ -0,0 +1,295 @@ +import { EventEmitter } from "./EventEmitter"; +import { LogLabel } from "./LogLabel"; +import { Logger } from "./Logger"; +import { LevelLogLabel, colorRadio } from "./PresetLogLabel"; + +interface IAppAPIParam { + api: { + + /** + * API 编号 + */ + nextId: number; + + /** + * 请求池 + */ + pool: API[]; + } +} + +interface IAnyData { + [x:string]: any +} + +type IWxRequestOption = WechatMiniprogram.RequestOption; + +type DeepReadonly = { + readonly [P in keyof T]: DeepReadonly; +}; + +/** + * 请求参数设置 + */ +type IParamSetting = { + [P in keyof T]: { + + /** + * 默认值 + */ + defaultValue?: T[P], + + /** + * ### 数据测试 + * 1、支持正则表达式测试 \ + * 2、支持使用 string === string 测试 \ + * 3、支持使用 number === number 测试 \ + * 4、支持使用自定义函数测试 + */ + tester?: RegExp | ((data:T[P]) => boolean) | string | number, + + /** + * ### 预解析函数 + * 1、此函数用来处理该键值 \ + * 2、当返回 undefined 时此键值被遗弃 \ + * 3、返回值时,此键值被覆盖 + * + * @param data 当前给定数据 + * @param key 当前给定数据键值 + * @param all 全部输入数据 + * @returns 解析结果 + */ + parse?: ((data:T[P], key:string, all:DeepReadonly>) => T[P] | undefined), + + /** + * 是否为请求头数据 + */ + isHeader?: boolean, + + /** + * 是否为模板 + */ + isTemplate?: boolean + + /** + * 是否可选 + */ + Optional?: boolean + } +} + +/** + * API 事件 + */ +type IAPIEvent = { + + /** + * 当数据初始化事件 + */ + initData: Partial; + + /** + * 请求数据解析完成后 + */ + parseRequestData: Partial; +} + +/** + * 接口调用 + */ +class API extends EventEmitter> { + + /** + * 基础 URL + * TODO: 这里可能涉及负载均衡 + */ + public static get baseUrl():string { + return "https://xxx.xxx"; + } + + public static defaultLogLabel:LogLabel = new LogLabel( + `API:API`, colorRadio(200, 120, 222) + ); + + /** + * Logger 使用的标签 + */ + private LogLabel:LogLabel = API.defaultLogLabel; + + /** + * Api 唯一 ID + */ + public key:string = "API"; + + /** + * API url + */ + public url:string = "/"; + + /** + * API 需要的参数列表 + */ + public params:IParamSetting = {} as any; + + /** + * API 需要的数据 + */ + public data?:Partial; + + /** + * 请求数据 + */ + public requestData?:IWxRequestOption; + + //#region wx.request + + public timeout?:number; + public method:HTTPMethod = HTTPMethod.GET; + public enableHttp2:boolean = false; + public enableQuic:boolean = false; + public enableCache:boolean = false; + + /** + * 是否自动解析返回的 json + * 对应 wx.request 的 dataType + */ + public jsonParse:boolean = true; + + //#endregion wx.request + + /** + * 初始化标签 + */ + public initLabel() { + this.LogLabel = new LogLabel( + `API:${ this.key }`, colorRadio(200, 120, 222) + ); + } + + /** + * 初始化数据 + * 注意:data 是不安全的,请传入数据副本 + * @param data API需要的全部数据 + */ + public param(data?: Partial) { + + this.data = data; + + if (this.data === void 0) { + Logger.log(`数据初始化异常: 没有输入 [data] 数据!`, + LevelLogLabel.FatalLabel, this.LogLabel); + return; + } + + for (let key in this.params) { + + let data = this.data[key]; + let { defaultValue, Optional, tester } = this.params[key]; + + // 默认值赋予 + if (data === void 0 && defaultValue !== void 0) { + this.data[key] = defaultValue; + } + + // 数据存在测试 + if (data === void 0 && !Optional) { + Logger.log(`数据校验异常: 数据 [${key}] 是必须的,但是并没有接收到!`, + LevelLogLabel.FatalLabel, this.LogLabel); + } + + // 用户自定义测试 + if (data !== void 0 && tester !== void 0) { + let testRes:boolean = false; + + if (tester instanceof RegExp) { + testRes = tester.test(data!); + } else if (typeof tester === "string" || typeof tester === "number") { + testRes = tester === data; + } else if (tester instanceof Function) { + testRes = tester(data!); + } else { + Logger.logMultiple( + [LevelLogLabel.FatalLabel, this.LogLabel], + `数据校验异常: [${ key }] 参数存在未知类型的 tester:`, tester + ); + } + + if (!testRes) { + Logger.logMultiple( + [LevelLogLabel.FatalLabel, this.LogLabel], + `数据校验异常: [${ key }] 参数数据未通过自定义的 tester:`, data + ); + } + } + } + + // 触发数据初始化事件 + this.emit("initData", this.data); + + if (this.data === void 0) { + Logger.log(`收集请求数据异常: 没有输入 [data] 数据!`, + LevelLogLabel.FatalLabel, this.LogLabel); + return; + } + + // 重置请求数据 + const requestData:IWxRequestOption = this.requestData = { + url: API.baseUrl + this.url, + data: {}, header: {}, + timeout: this.timeout, + method: this.method, + dataType: this.jsonParse ? "json" : undefined, + enableHttp2: this.enableHttp2, + enableQuic: this.enableQuic, + enableCache: this.enableCache + }; + + // 数据解析 + for (let key in this.params) { + + let data = this.data[key]; + let { parse } = this.params[key]; + + // 数据预解析 + if (parse !== void 0) { + this.data[key] = parse(data!, key, this.data as DeepReadonly>); + } + } + + // 触发数据解析 + this.emit("parseRequestData", this.data); + + // 数据收集 + for (let key in this.params) { + + let data = this.data[key]; + let { isHeader, isTemplate } = this.params[key]; + + // 加载数据 + if (!isTemplate) { + if (isHeader) { + requestData.header![key] = data; + } else { + (requestData.data as IAnyData)[key] = data; + } + } + } + } +} + +/** + * HTTP 请求类型 + */ +enum HTTPMethod { + OPTIONS = "OPTIONS", + GET = "GET", + HEAD = "HEAD", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE", + TRACE = "TRACE", + CONNECT = "CONNECT" +} + +export default API; +export { API, IParamSetting, IAppAPIParam, HTTPMethod } \ No newline at end of file diff --git a/miniprogram/core/EventEmitter.ts b/miniprogram/core/EventEmitter.ts index 2dc3cd7..34fb56d 100644 --- a/miniprogram/core/EventEmitter.ts +++ b/miniprogram/core/EventEmitter.ts @@ -18,102 +18,92 @@ export type EventHandlerMap> = Map< EventHandlerList | WildCardEventHandlerList >; -export interface Emitter> { - all: EventHandlerMap; +type GenericEventHandler> = + | Handler + | WildcardHandler; + +export class EventEmitter> { + + /** + * A Map of event names to registered handler functions. + */ + public all: EventHandlerMap; + + public constructor() { + this.all = new Map(); + } on(type: Key, handler: Handler): void; on(type: '*', handler: WildcardHandler): void; + /** + * Register an event handler for the given type. + * @param {string|symbol} type Type of event to listen for, or `'*'` for all events + * @param {Function} handler Function to call in response to given event + * @memberOf mitt + */ + public on(type: Key, handler: GenericEventHandler) { + const handlers: Array> | undefined = this.all!.get(type); + if (handlers) { + handlers.push(handler); + } + else { + this.all!.set(type, [handler] as EventHandlerList); + } + } + off(type: Key, handler?: Handler): void; off(type: '*', handler: WildcardHandler): void; - emit(type: Key, event: Events[Key]): void; - emit(type: undefined extends Events[Key] ? Key : never): void; -} - -/** - * Mitt: Tiny (~200b) functional event emitter / pubsub. - * @name mitt - * @returns {Mitt} - */ -export default function mitt>( - all?: EventHandlerMap -): Emitter { - type GenericEventHandler = - | Handler - | WildcardHandler; - all = all || new Map(); - - return { - - /** - * A Map of event names to registered handler functions. - */ - all, - - /** - * Register an event handler for the given type. - * @param {string|symbol} type Type of event to listen for, or `'*'` for all events - * @param {Function} handler Function to call in response to given event - * @memberOf mitt - */ - on(type: Key, handler: GenericEventHandler) { - const handlers: Array | undefined = all!.get(type); - if (handlers) { - handlers.push(handler); + /** + * Remove an event handler for the given type. + * If `handler` is omitted, all handlers of the given type are removed. + * @param {string|symbol} type Type of event to unregister `handler` from, or `'*'` + * @param {Function} [handler] Handler function to remove + * @memberOf mitt + */ + public off(type: Key, handler?: GenericEventHandler) { + const handlers: Array> | undefined = this.all!.get(type); + if (handlers) { + if (handler) { + handlers.splice(handlers.indexOf(handler) >>> 0, 1); } else { - all!.set(type, [handler] as EventHandlerList); - } - }, - - /** - * Remove an event handler for the given type. - * If `handler` is omitted, all handlers of the given type are removed. - * @param {string|symbol} type Type of event to unregister `handler` from, or `'*'` - * @param {Function} [handler] Handler function to remove - * @memberOf mitt - */ - off(type: Key, handler?: GenericEventHandler) { - const handlers: Array | undefined = all!.get(type); - if (handlers) { - if (handler) { - handlers.splice(handlers.indexOf(handler) >>> 0, 1); - } - else { - all!.set(type, []); - } - } - }, - - /** - * Invoke all handlers for the given type. - * If present, `'*'` handlers are invoked after type-matched handlers. - * - * Note: Manually firing '*' handlers is not supported. - * - * @param {string|symbol} type The event type to invoke - * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler - * @memberOf mitt - */ - emit(type: Key, evt?: Events[Key]) { - let handlers = all!.get(type); - if (handlers) { - (handlers as EventHandlerList) - .slice() - .map((handler) => { - handler(evt!); - }); - } - - handlers = all!.get('*'); - if (handlers) { - (handlers as WildCardEventHandlerList) - .slice() - .map((handler) => { - handler(type, evt!); - }); + this.all!.set(type, []); } } - }; + } + + emit(type: Key, event: Events[Key]): void; + emit(type: undefined extends Events[Key] ? Key : never): void; + + /** + * Invoke all handlers for the given type. + * If present, `'*'` handlers are invoked after type-matched handlers. + * + * Note: Manually firing '*' handlers is not supported. + * + * @param {string|symbol} type The event type to invoke + * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler + * @memberOf mitt + */ + emit(type: Key, evt?: Events[Key]) { + let handlers = this.all!.get(type); + if (handlers) { + (handlers as EventHandlerList) + .slice() + .map((handler) => { + handler(evt!); + }); + } + + handlers = this.all!.get('*'); + if (handlers) { + (handlers as WildCardEventHandlerList) + .slice() + .map((handler) => { + handler(type, evt!); + }); + } + } } \ No newline at end of file diff --git a/miniprogram/core/Module.ts b/miniprogram/core/Module.ts index 6fdf8c2..e05192e 100644 --- a/miniprogram/core/Module.ts +++ b/miniprogram/core/Module.ts @@ -1,4 +1,4 @@ -import mitt, { Emitter, EventHandlerMap, EventType, Handler, WildcardHandler } from "./EventEmitter"; +import { EventEmitter, EventType } from "./EventEmitter"; import { LogLabel, LogStyle } from "./LogLabel"; import { Logger } from "./Logger"; import { LevelLogLabel } from "./PresetLogLabel"; @@ -64,7 +64,8 @@ class Modular< DEP extends Depends = Depends, E extends Record = Record, TD extends IAnyTypeObject = IAnyTypeObject> -implements WXContext, Emitter { +extends EventEmitter +implements WXContext { // [x:string]: any; @@ -121,6 +122,8 @@ implements WXContext, Emitter { */ public constructor(manager:M, nameSpace:string, depend?: DEP) { + super(); + // 保存微信上下文 this.manager = manager; @@ -131,34 +134,6 @@ implements WXContext, Emitter { this.functionList = new Set(); this.paramList = new Set(); this.nameSpace = nameSpace; - - this.emitter = mitt(); - - } - - /** - * 内部事件控制器 - */ - private emitter:Emitter; - - public get all():EventHandlerMap { return this.emitter.all }; - - on(type: Key, handler: Handler): void; - on(type: "*", handler: WildcardHandler): void; - on(type: any, handler: any): void { - return this.emitter.on(type, handler); - } - - off(type: Key, handler?: Handler): void; - off(type: "*", handler: WildcardHandler): void; - off(type: any, handler?: any): void { - return this.emitter.off(type, handler); - } - - emit(type: Key, event: E[Key]): void; - emit(type: undefined extends E[Key] ? Key : never): void; - emit(type: any, event?: any): void { - return this.emitter.emit(type, event); } public setData(data:Partial, callback?: () => void):void { diff --git a/miniprogram/pages/Timetable/TestCore.ts b/miniprogram/pages/Timetable/TestCore.ts new file mode 100644 index 0000000..2ebf306 --- /dev/null +++ b/miniprogram/pages/Timetable/TestCore.ts @@ -0,0 +1,74 @@ +import { Modular, Manager, ILifetime } from "../../core/Module"; +import { API, IParamSetting } from "../../core/Api"; +import { Storage } from "../../core/Storage"; + +/** + * 顶部状态栏 + */ +class TestCore extends Modular +implements Partial { + + public onLoad() { + + let s = new Storage("test", { + a: new Date(), + be: 2 + }); + + setTimeout(() => { + s.set("be", 12); + }, 1000) + + interface ITestApiInput { + name: string, + id: number, + info: { + data: string + } + } + + class TestApi extends API { + + public override key:string = "TestApi"; + + public override params: IParamSetting = { + name: { + tester: "123", + isHeader: true + }, + id: { + parse: (i) => ++i, + }, + info: {} + } + + public constructor() { + super(); + this.initLabel(); + + this.emit("initData", {}) + + this.on("initData", (d) => { + + }) + + this.on("parseRequestData", (data) => { + console.log("parseRequestData", data) + }) + } + } + + let api = new TestApi(); + api.param({ + name: "123", + id: 456, + info: { + data: "abc" + } + }); + console.log(api); + } +} + +export default TestCore; +export { TestCore }; \ No newline at end of file diff --git a/miniprogram/pages/Timetable/Timetable.ts b/miniprogram/pages/Timetable/Timetable.ts index 82a4874..05c0898 100644 --- a/miniprogram/pages/Timetable/Timetable.ts +++ b/miniprogram/pages/Timetable/Timetable.ts @@ -1,5 +1,6 @@ import { Manager} from "../../core/Module"; import { StatusBar } from "./StatusBar"; +import { TestCore } from "./TestCore"; /** * 此页面使用 Manager 进行模块化管理 @@ -7,4 +8,5 @@ import { StatusBar } from "./StatusBar"; */ Manager.Page((manager)=>{ manager.addModule(StatusBar, "statusBar"); + manager.addModule(TestCore, "testCore"); }) \ No newline at end of file diff --git a/project.config.json b/project.config.json index bf78da6..ec8c334 100644 --- a/project.config.json +++ b/project.config.json @@ -35,7 +35,7 @@ "outputPath": "" }, "enableEngineNative": false, - "useIsolateContext": true, + "useIsolateContext": false, "userConfirmedBundleSwitch": false, "packNpmManually": false, "packNpmRelationList": [],