Compare commits
	
		
			No commits in common. "master" and "dev-sanaesy" have entirely different histories.
		
	
	
		
			master
			...
			dev-sanaes
		
	
		
| @ -1,95 +0,0 @@ | ||||
| import { API, IAnyData, GeneralCallbackResult } from "../core/Api"; | ||||
| import { EventType, Emitter } from "../core/Emitter"; | ||||
| 
 | ||||
| type ILoginEvent = { | ||||
| 
 | ||||
|     /** | ||||
|      * session 过期 | ||||
|      */ | ||||
|     expire: GeneralCallbackResult; | ||||
| 
 | ||||
|     /** | ||||
|      * 登录失败 | ||||
|      */ | ||||
|     unauthorized: GeneralCallbackResult; | ||||
| 
 | ||||
|     /** | ||||
|      * 未知的问题 | ||||
|      */ | ||||
|     error: GeneralCallbackResult; | ||||
| 
 | ||||
|     /** | ||||
|      * 数据损坏或丢失 | ||||
|      */ | ||||
|     badData: GeneralCallbackResult; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 教务处基础 API | ||||
|  * @template I API 输入数据 | ||||
|  * @template O API 输出数据 | ||||
|  * @template E API 中的事件 | ||||
|  * @template U 用户自定义的输出数据 | ||||
|  */ | ||||
| abstract class EduBase< | ||||
| 	I extends IAnyData = IAnyData,  | ||||
| 	O extends IAnyData = IAnyData, | ||||
| 	E extends Record<EventType, any> = {} | ||||
| > extends API<I, O, { | ||||
| 	[P in (keyof ILoginEvent | keyof E)]: P extends keyof ILoginEvent ? ILoginEvent[P] : E[P] | ||||
| }> { | ||||
| 
 | ||||
| 	protected useEduCallback( | ||||
| 		parseFunction: (data: any) => O | ||||
| 	): void { | ||||
| 
 | ||||
| 		this.addFailedCallBack(); | ||||
| 
 | ||||
|         this.on("success", (data) => { | ||||
| 
 | ||||
|             let isSuccess = true; | ||||
|             let errMsg = ""; | ||||
|             let res: O | undefined; | ||||
|             const info: any = data.data; | ||||
| 
 | ||||
|             // 数据缺失检测
 | ||||
|             if(!info) { | ||||
|                 isSuccess = false; | ||||
|                 errMsg = "Bad Data"; | ||||
|                 (this as Emitter<IAnyData>).emit("badData", { errMsg }); | ||||
|             } | ||||
| 
 | ||||
|             if (isSuccess) switch (info.code) { | ||||
|                 case (1): | ||||
|                     res = parseFunction(info.data); | ||||
|                     errMsg = info.err_msg ?? "Success"; | ||||
|                     (this as Emitter<IAnyData>).emit("ok", res!); | ||||
|                     break; | ||||
| 
 | ||||
|                 case (2): | ||||
|                     isSuccess = false; | ||||
|                     errMsg = info.err_msg ?? "Session Expire"; | ||||
|                     (this as Emitter<IAnyData>).emit("expire", { errMsg }); | ||||
|                     break; | ||||
| 
 | ||||
|                 case (3): | ||||
|                     isSuccess = false; | ||||
|                     errMsg = info.err_msg ?? "Unauthorized"; | ||||
|                     (this as Emitter<IAnyData>).emit("unauthorized", { errMsg }); | ||||
|                     break; | ||||
| 
 | ||||
|                 case (4): | ||||
|                     isSuccess = false; | ||||
|                     errMsg = info.err_msg ?? "Error"; | ||||
|                     (this as Emitter<IAnyData>).emit("error", { errMsg }); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             if (!isSuccess) (this as Emitter<IAnyData>).emit("no", { errMsg }); | ||||
|             (this as Emitter<IAnyData>).emit("done", { errMsg, data: res }); | ||||
|         }); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export { EduBase }; | ||||
| export default EduBase; | ||||
| @ -1,5 +1,4 @@ | ||||
| import { HTTPMethod, IParamSetting } from "../core/Api"; | ||||
| import { EduBase } from "./EduBase"; | ||||
| import { API, HTTPMethod, IParamSetting, GeneralCallbackResult } from "../core/Api"; | ||||
| 
 | ||||
| interface ILoginInput { | ||||
| 
 | ||||
| @ -32,18 +31,46 @@ interface ILoginOutput { | ||||
|      */ | ||||
|     actualName: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 用户是否关注了公共号 | ||||
|      */ | ||||
|     isSubscribeWxAccount: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * 教务处的 session | ||||
|      */ | ||||
|     eduSession: string; | ||||
| } | ||||
| 
 | ||||
| interface ILoginEvent { | ||||
| 
 | ||||
|     /** | ||||
|      * session 过期 | ||||
|      */ | ||||
|     expire: GeneralCallbackResult; | ||||
| 
 | ||||
|     /** | ||||
|      * 登录失败 | ||||
|      */ | ||||
|     unauthorized: GeneralCallbackResult; | ||||
| 
 | ||||
|     /** | ||||
|      * 未知的问题 | ||||
|      */ | ||||
|     error: GeneralCallbackResult; | ||||
| 
 | ||||
|     /** | ||||
|      * 数据损坏或丢失 | ||||
|      */ | ||||
|     badData: GeneralCallbackResult; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Login API | ||||
|  * 此 API 用来向教务处发起登录请求 | ||||
|  * 请求成功后将获得教务处返回的 session | ||||
|  */ | ||||
| class Login extends EduBase<ILoginInput, ILoginOutput> { | ||||
| class Login extends API<ILoginInput, ILoginOutput, ILoginEvent> { | ||||
| 
 | ||||
|     public override url: string = "/login"; | ||||
| 
 | ||||
| @ -65,13 +92,57 @@ class Login extends EduBase<ILoginInput, ILoginOutput> { | ||||
|         super(); | ||||
|         this.initDebugLabel("Login"); | ||||
| 
 | ||||
|         this.useEduCallback((data) => ({ | ||||
|             idCardLast6: data.idCard, | ||||
|             eduService: data.ip, | ||||
|             actualName: data.name, | ||||
|             isSubscribeWxAccount: data.official, | ||||
|             eduSession: data.session | ||||
|         })); | ||||
|         this.addFailedCallBack(); | ||||
| 
 | ||||
|         this.on("success", (data) => { | ||||
| 
 | ||||
|             let isSuccess = true; | ||||
|             let errMsg = ""; | ||||
|             let res: ILoginOutput | undefined; | ||||
|             const info: any = data.data; | ||||
| 
 | ||||
|             // 数据缺失检测
 | ||||
|             if(!info) { | ||||
|                 isSuccess = false; | ||||
|                 errMsg = "Bad Data"; | ||||
|                 this.emit("badData", { errMsg }); | ||||
|             } | ||||
| 
 | ||||
|             if (isSuccess) switch (info.code) { | ||||
|                 case (1): | ||||
|                     res = { | ||||
|                         idCardLast6: info.data.idCard, | ||||
|                         eduService: info.data.ip, | ||||
|                         actualName: info.data.name, | ||||
|                         isSubscribeWxAccount: info.data.official, | ||||
|                         eduSession: info.data.session | ||||
|                     }; | ||||
|                     errMsg = info.err_msg ?? "Success"; | ||||
|                     this.emit("ok", res); | ||||
|                     break; | ||||
| 
 | ||||
|                 case (2): | ||||
|                     isSuccess = false; | ||||
|                     errMsg = info.err_msg ?? "Session Expire"; | ||||
|                     this.emit("expire", { errMsg }); | ||||
|                     break; | ||||
| 
 | ||||
|                 case (3): | ||||
|                     isSuccess = false; | ||||
|                     errMsg = info.err_msg ?? "Unauthorized"; | ||||
|                     this.emit("unauthorized", { errMsg }); | ||||
|                     break; | ||||
| 
 | ||||
|                 case (4): | ||||
|                     isSuccess = false; | ||||
|                     errMsg = info.err_msg ?? "Error"; | ||||
|                     this.emit("error", { errMsg }); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             if (!isSuccess) this.emit("no", { errMsg }); | ||||
|             this.emit("done", { errMsg, data: res }); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,110 +0,0 @@ | ||||
| import { HTTPMethod, IParamSetting } from "../core/Api"; | ||||
| import { EduBase } from "./EduBase"; | ||||
| 
 | ||||
| interface IScheduleInput { | ||||
| 
 | ||||
|     /** | ||||
|      * session | ||||
|      */ | ||||
|     cookie: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 学期 | ||||
|      */ | ||||
|     semester: string; | ||||
| } | ||||
| 
 | ||||
| interface IClassData { | ||||
| 
 | ||||
|     /** | ||||
|      * 课程名字 | ||||
|      */ | ||||
|     name: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 上课地点 | ||||
|      */ | ||||
|     room?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 课程老师 | ||||
|      */ | ||||
|     teacher?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 周数 | ||||
|      */ | ||||
|     week: string; | ||||
| } | ||||
| 
 | ||||
| type IScheduleOutput = { | ||||
| 
 | ||||
|     /** | ||||
|      * 课程列表 | ||||
|      */ | ||||
|     classList: IClassData[]; | ||||
| 
 | ||||
|     /** | ||||
|      * 稀疏矩阵编号 | ||||
|      */ | ||||
|     index: number; | ||||
| }[]; | ||||
| 
 | ||||
| /** | ||||
|  * Schedule API | ||||
|  * 需要session与semester | ||||
|  * 此 API 用来向教务处发起获取课程表的请求 | ||||
|  * 请求成功后将获得教务处返回的课程表JSON文件 | ||||
|  */ | ||||
| class Schedlue extends EduBase<IScheduleInput, IScheduleOutput> { | ||||
|   | ||||
|     public override url = "/course_timetable"; | ||||
| 
 | ||||
|     public override method: HTTPMethod = HTTPMethod.GET; | ||||
| 
 | ||||
|     public override params: IParamSetting<IScheduleInput> = { | ||||
| 
 | ||||
|         cookie: { | ||||
|             mapKey: "cookie", | ||||
|             isHeader: true | ||||
|         }, | ||||
| 
 | ||||
|         semester: { | ||||
|             mapKey: "semester", | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     public constructor() { | ||||
|         super(); | ||||
|         this.initDebugLabel("Schedule"); | ||||
|          | ||||
|         this.useEduCallback((data) => { | ||||
|             const res: IScheduleOutput = []; | ||||
| 
 | ||||
|             for( let i = 0; i < data.length; i++ ) { | ||||
|                 const classList: IClassData[] = []; | ||||
|                 const CTTDetails = data[i].CTTDetails ?? []; | ||||
| 
 | ||||
|                 for( let j = 0; j < CTTDetails.length; j++ ) { | ||||
| 
 | ||||
|                     classList.push({ | ||||
|                         name: CTTDetails[j].Name, | ||||
|                         room: CTTDetails[j].Room, | ||||
|                         teacher: CTTDetails[j].Teacher, | ||||
|                         week: CTTDetails[j].Week, | ||||
|                     }) | ||||
|                 } | ||||
| 
 | ||||
|                 res.push({ | ||||
|                     classList, | ||||
|                     index: data[i].Id | ||||
|                 }) | ||||
|             } | ||||
|             return res; | ||||
| 
 | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { Schedlue }; | ||||
| export default Schedlue; | ||||
| @ -16,14 +16,10 @@ $black-filter: brightness(0) opacity(.65); | ||||
| $white-filter: brightness(100) opacity(.65); | ||||
| $blue-filter: opacity(1); | ||||
| 
 | ||||
| @mixin container { | ||||
|     width: 88%; | ||||
|     padding: 0 6%; | ||||
| } | ||||
| 
 | ||||
| // 页面容器外边距 | ||||
| view.container { | ||||
|     @include container; | ||||
|     width: 88%; | ||||
|     padding: 0 6%; | ||||
| } | ||||
| 
 | ||||
| // 带阴影的 card | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import { IAppAPIParam, IAnyData } from "./core/Api"; | ||||
| import { IAppAPIParam } from "./core/Api"; | ||||
| import { IAppStorageParam, Storage, IStorageData } from "./core/Storage"; | ||||
| import { Logger, LevelLogLabel, LifeCycleLogLabel } from "./core/Logger"; | ||||
| 
 | ||||
| 
 | ||||
| App<IAppAPIParam & IAppStorageParam & IAnyData>({ | ||||
| App<IAppAPIParam & IAppStorageParam>({ | ||||
| 
 | ||||
|     /** | ||||
|      * API 模块需要的全局数据 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Emitter, EventType, EventMixin } from "./Emitter"; | ||||
| import { Emitter, EventType } from "./Emitter"; | ||||
| import { API_FAILED_SHOW_MESSAGE } from "./Config"; | ||||
| import { Logger, LogLabel, LevelLogLabel, colorRadio, StatusLabel } from "./Logger"; | ||||
| interface IAppAPIParam { | ||||
| @ -156,20 +156,28 @@ class API< | ||||
|     O extends IAnyData = IAnyData, | ||||
|     E extends Record<EventType, any> = Record<EventType, any>, | ||||
|     U extends IAnyData = IAnyData | ||||
| > extends Emitter<EventMixin<IAPIEvent<I, O> & IAPIResultEvent<O, U>, E>> { | ||||
| 
 | ||||
|     /** | ||||
|      * 默认调试标签 | ||||
|      */ | ||||
|     public static defaultLogLabel:LogLabel = new LogLabel( | ||||
|         `API:API`, colorRadio(200, 120, 222) | ||||
|     ); | ||||
| > extends Emitter < | ||||
|     { | ||||
|         // 这个复杂的泛型是为了 MixIn 用户自定义事件类型
 | ||||
|         // 懂得如何使用就可以了
 | ||||
|         // 不要试图去理解下面这三行代码,真正的恶魔在等着你
 | ||||
|         [P in (keyof (IAPIEvent<I, O> & IAPIResultEvent<O, U>) | keyof E)] :  | ||||
|         P extends keyof IAPIEvent<I, O> ? IAPIEvent<I, O>[P] :  | ||||
|         P extends keyof IAPIResultEvent<O, U> ? IAPIResultEvent<O, U>[P] : E[P] | ||||
|     } | ||||
| > { | ||||
| 
 | ||||
|     /** | ||||
|      * 基础 URL | ||||
|      * TODO: 这里可能涉及负载均衡 | ||||
|      */ | ||||
|     public baseUrl: string = "https://jwc.nogg.cn"; | ||||
|     public static get baseUrl():string { | ||||
|         return "https://jwc.nogg.cn"; | ||||
|     } | ||||
| 
 | ||||
|     public static defaultLogLabel:LogLabel = new LogLabel( | ||||
|         `API:API`, colorRadio(200, 120, 222) | ||||
|     ); | ||||
| 
 | ||||
|     /** | ||||
|      * Logger 使用的标签 | ||||
| @ -307,11 +315,11 @@ class API< | ||||
|         } | ||||
| 
 | ||||
|         // 触发数据初始化事件
 | ||||
|         (this as Emitter<IAnyData>).emit("initData", this.data); | ||||
|         this.emit("initData", this.data); | ||||
| 
 | ||||
|         // 重置请求数据
 | ||||
|         const requestData:IWxRequestOption<O> = this.requestData = { | ||||
|             url: this.baseUrl + this.url, | ||||
|             url: API.baseUrl + this.url, | ||||
|             data: {}, header: {}, | ||||
|             timeout: this.timeout, | ||||
|             method: this.method, | ||||
| @ -334,7 +342,7 @@ class API< | ||||
|         } | ||||
| 
 | ||||
|         // 触发数据解析
 | ||||
|         (this as Emitter<IAnyData>).emit("parseRequestData", this.data); | ||||
|         this.emit("parseRequestData", this.data); | ||||
| 
 | ||||
|         // 数据收集
 | ||||
|         for (let key in this.params) { | ||||
| @ -452,18 +460,18 @@ class API< | ||||
|         let request = () => { | ||||
| 
 | ||||
|             // 触发请求发送事件
 | ||||
|             (this as Emitter<IAnyData>).emit("request", this.requestData!) | ||||
|             this.emit("request", this.requestData!) | ||||
| 
 | ||||
|             wx.request<O>({ | ||||
|                 ...this.requestData!, | ||||
|                 success: (e) => { | ||||
|                     (this as Emitter<IAnyData>).emit("success", e); | ||||
|                     this.emit("success", e); | ||||
|                 }, | ||||
|                 fail: (e) => { | ||||
|                     (this as Emitter<IAnyData>).emit("fail", e); | ||||
|                     this.emit("fail", e); | ||||
|                 }, | ||||
|                 complete: (e) => { | ||||
|                     (this as Emitter<IAnyData>).emit("complete", e); | ||||
|                     this.emit("complete", e); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| @ -481,12 +489,12 @@ class API< | ||||
|                 // 使用上次请求结果
 | ||||
|                 if (this.policy === RequestPolicy.useLastRequest) { | ||||
|                     lastAPI.on("success", (e) => { | ||||
|                         (this as Emitter<IAnyData>).emit("success", e as SuccessCallbackResult<O>); | ||||
|                         (this as Emitter<IAnyData>).emit("complete", {errMsg: e.errMsg}); | ||||
|                         this.emit("success", e as SuccessCallbackResult<O>); | ||||
|                         this.emit("complete", {errMsg: e.errMsg}); | ||||
|                     }); | ||||
|                     lastAPI.on("fail", (e) => { | ||||
|                         (this as Emitter<IAnyData>).emit("fail", e); | ||||
|                         (this as Emitter<IAnyData>).emit("complete", {errMsg: e.errMsg}); | ||||
|                         this.emit("fail", e); | ||||
|                         this.emit("complete", {errMsg: e.errMsg}); | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
| @ -596,8 +604,8 @@ class API< | ||||
|      */ | ||||
|     public addFailedCallBack(): this { | ||||
|         this.on("fail", (e) => { | ||||
|             (this as Emitter<IAnyData>).emit("no", e as any); | ||||
|             (this as Emitter<IAnyData>).emit("done", e as any); | ||||
|             this.emit("no", e as any); | ||||
|             this.emit("done", e as any); | ||||
|         }); | ||||
|         return this; | ||||
|     } | ||||
| @ -689,4 +697,4 @@ enum HTTPMethod { | ||||
| } | ||||
| 
 | ||||
| export default API; | ||||
| export { API, IParamSetting, IAppAPIParam, IAnyData, ICallBack, HTTPMethod, RequestPolicy, GeneralCallbackResult } | ||||
| export { API, IParamSetting, IAppAPIParam, ICallBack, HTTPMethod, RequestPolicy, GeneralCallbackResult } | ||||
| @ -1,277 +0,0 @@ | ||||
| import { Storage } from "./Storage"; | ||||
| 
 | ||||
| /** | ||||
|  * 数据层键值设置 | ||||
|  */ | ||||
| interface IDataParamSettingItem { | ||||
| 
 | ||||
|     /** | ||||
|      * 类型 | ||||
|      */ | ||||
|     type: any; | ||||
| 
 | ||||
|     /** | ||||
|      * 键值是否可以获取 | ||||
|      */ | ||||
|     get?: ((...P: any) => this["type"]) | INone; | ||||
| 
 | ||||
|     /** | ||||
|      * 键值是否可以设置 | ||||
|      */ | ||||
|     set?: ((...P: [this["type"], ...any]) => any) | INone; | ||||
| 
 | ||||
|     /** | ||||
|      * 是否仅为异步获取 | ||||
|      */ | ||||
|     getAsync?: ((...P: any) => Promise<this["type"]>) | INone; | ||||
| 
 | ||||
|     /** | ||||
|      * 是否仅为异步设置 | ||||
|      */ | ||||
|     setAsync?: ((...P: [this["type"], ...any]) => Promise<any>) | INone; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 数据层参数类型 | ||||
|  */ | ||||
| interface IDataParamSetting { | ||||
|     [x: string]: IDataParamSettingItem | ||||
| } | ||||
| 
 | ||||
| type INone = undefined | void; | ||||
| 
 | ||||
| /** | ||||
|  * 注册表结构 | ||||
|  */ | ||||
| type IRegistryItem<S extends IDataParamSettingItem> = { | ||||
| 
 | ||||
|     /** | ||||
|      * 获取方法 | ||||
|      */ | ||||
|     get: S["get"]; | ||||
| 
 | ||||
|     /** | ||||
|      * 异步获取方法 | ||||
|      */ | ||||
|     getAsync: S["getAsync"] extends Function ? S["getAsync"] :  | ||||
|     S["get"] extends ((...P: any) => S["type"]) ? (...param: Parameters<S["get"]>) => Promise<S["type"]> : INone; | ||||
| 
 | ||||
|     /** | ||||
|      * 设置方法 | ||||
|      */ | ||||
|     set: S["set"]; | ||||
| 
 | ||||
|     /** | ||||
|      * 异步设置方法 | ||||
|      */ | ||||
|     setAsync: S["setAsync"] extends Function ? S["setAsync"] :  | ||||
|     S["set"] extends ((...P: [S["type"], ...any]) => any) ? (...param: Parameters<S["set"]>) => Promise<ReturnType<S["set"]>> : INone; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 注册表参数类型 | ||||
|  */ | ||||
| type IRegistry<D extends IDataParamSetting> = { | ||||
|     [P in keyof D]: IRegistryItem<D[P]>; | ||||
| } | ||||
| 
 | ||||
| type IRegistryPartial<D extends IDataParamSetting> = { | ||||
|     [P in keyof D]?: Partial<IRegistryItem<D[P]>>; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| type IAutoSelect<IF extends boolean, A, B> = IF extends true ? A : B; | ||||
| 
 | ||||
| /** | ||||
|  * Core 数据层架构 | ||||
|  *  | ||||
|  * 使用示例: | ||||
|  * ```typescript
 | ||||
|  * class TestData extends Data<{ | ||||
|  *     test: { | ||||
|  *         type: number | ||||
|  *         get: () => number, | ||||
|  *         set: (val: number) => void, | ||||
|  *         getAsync: () => Promise<number> | ||||
|  *     } | ||||
|  *     }> { | ||||
|  *         public onLoad() { | ||||
|  *         let dataObject = {key: 1} | ||||
|  *         this.getter("test", () => 1); | ||||
|  *         this.registerKeyFromObject(dataObject, "test", "key"); | ||||
|  *     } | ||||
|  * } | ||||
|  * ``` | ||||
|  */ | ||||
| class Data<D extends IDataParamSetting> { | ||||
| 
 | ||||
|     /** | ||||
|      * getter setter 注册表 | ||||
|      */ | ||||
|     private registryList: IRegistryPartial<D> = {}; | ||||
| 
 | ||||
|     /** | ||||
|      * 加载函数 | ||||
|      */ | ||||
|     public onLoad(): any {}; | ||||
| 
 | ||||
|     /** | ||||
|      * 注册一个 Setter 到键值上 | ||||
|      * @param key 注册的键值 | ||||
|      * @param setter 注册的 Setter | ||||
|      * @param isAsync 是否为异步函数 | ||||
|      */ | ||||
|     protected setter<KEY extends keyof D> (key: KEY, setter: IRegistryItem<D[KEY]>["set"]): this | ||||
|     protected setter<KEY extends keyof D> (key: KEY, setter: IRegistryItem<D[KEY]>["setAsync"], isAsync: true): this | ||||
|     protected setter< | ||||
|         KEY extends keyof D,  | ||||
|         ASYNC extends boolean | ||||
|     > ( | ||||
|         key: KEY,  | ||||
|         setter: IAutoSelect<ASYNC, IRegistryItem<D[KEY]>["setAsync"], IRegistryItem<D[KEY]>["set"]>,  | ||||
|         isAsync?: ASYNC | ||||
|     ): this { | ||||
| 
 | ||||
|         // 如果此键值不存在,新建
 | ||||
|         if (!this.registryList[key]) this.registryList[key] = {}; | ||||
| 
 | ||||
|         // 设置异步 setter
 | ||||
|         if (isAsync) this.registryList[key]!.setAsync = setter as IRegistryItem<D[KEY]>["setAsync"]; | ||||
| 
 | ||||
|         // 设置同步 setter
 | ||||
|         else this.registryList[key]!.set = setter as IRegistryItem<D[KEY]>["set"]; | ||||
| 
 | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 注册一个 Getter 到键值上 | ||||
|      * @param key 注册的键值 | ||||
|      * @param getter 注册的 Getter | ||||
|      * @param isAsync 是否为异步函数 | ||||
|      */ | ||||
|     protected getter<KEY extends keyof D> (key: KEY, getter: IRegistryItem<D[KEY]>["get"]): this | ||||
|     protected getter<KEY extends keyof D> (key: KEY, getter: IRegistryItem<D[KEY]>["getAsync"], isAsync: true): this | ||||
|     protected getter< | ||||
|         KEY extends keyof D,  | ||||
|         ASYNC extends boolean | ||||
|     > ( | ||||
|         key: KEY,  | ||||
|         getter: IAutoSelect<ASYNC, IRegistryItem<D[KEY]>["getAsync"], IRegistryItem<D[KEY]>["get"]>,  | ||||
|         isAsync?: ASYNC | ||||
|     ): this { | ||||
|   | ||||
|         // 如果此键值不存在,新建
 | ||||
|         if (!this.registryList[key]) this.registryList[key] = {}; | ||||
| 
 | ||||
|         // 设置异步 getter
 | ||||
|         if (isAsync) this.registryList[key]!.getAsync = getter as IRegistryItem<D[KEY]>["getAsync"]; | ||||
| 
 | ||||
|         // 设置同步 getter
 | ||||
|         else this.registryList[key]!.get = getter as IRegistryItem<D[KEY]>["get"]; | ||||
| 
 | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 将对象的键值关联到 Data 层中 | ||||
|      * @param key Data 层键值 | ||||
|      * @param keyFromObject 对象源键值 | ||||
|      * @param object 关联对象 | ||||
|      */ | ||||
|     protected registerKeyFromObject< | ||||
|         KEY extends keyof D,  | ||||
|         F extends string,  | ||||
|         O extends {[K in F]: D[KEY]["type"]} | ||||
|     > (object: O, key: KEY, keyFromObject: F = key as any) { | ||||
| 
 | ||||
|         // 注册同步获取
 | ||||
|         this.getter(key, () => { | ||||
|             return object[keyFromObject] | ||||
|         }); | ||||
| 
 | ||||
|         // 注册同步设置
 | ||||
|         this.setter(key, (data: any) => { | ||||
|             object[keyFromObject] = data | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 关联 Storage 中的 key | ||||
|      * @param key Data 层键值 | ||||
|      * @param keyFromStorage StorageKey | ||||
|      */ | ||||
|     protected registerKeyFromStorage< | ||||
|         KEY extends keyof D, | ||||
|         F extends string,  | ||||
|         S extends Storage<{[K in F]: D[KEY]["type"]}> | ||||
|     > (storage: S, key: KEY, keyFromStorage: F = key as any) { | ||||
| 
 | ||||
|         // 同步获取
 | ||||
|         this.getter(key, () => { | ||||
|             return storage.get(keyFromStorage); | ||||
|         }); | ||||
| 
 | ||||
|         // 同步设置
 | ||||
|         this.setter(key, (data: any) => { | ||||
|             storage.set(keyFromStorage, data); | ||||
|         }); | ||||
| 
 | ||||
|         // 异步设置
 | ||||
|         this.setter(key, (async (data: any) => { | ||||
|             await storage.set(keyFromStorage, data); | ||||
|         }) as any, true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 导出数据对象 | ||||
|      * @returns 数据对象 | ||||
|      */ | ||||
|     public export(): IRegistry<D> { | ||||
|         this.autoFillFunction(); | ||||
|         return this.registryList as IRegistry<D>; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 自动填充缺失的异步函数 | ||||
|      * 在注册表中搜索全部的同步函数 | ||||
|      * 并自动填充对应的异步函数 | ||||
|      * 此函数会在 export 前静默执行 | ||||
|      */ | ||||
|     protected autoFillFunction(): void { | ||||
| 
 | ||||
|         // 填充函数
 | ||||
|         const fillFunction = <KEY extends keyof D>(key: KEY): void => { | ||||
| 
 | ||||
|             const item = this.registryList[key] as IRegistryItem<D[KEY]>; | ||||
|             if (!item) return; | ||||
|      | ||||
|             // 检验 getter
 | ||||
|             if (item.get && !item.getAsync) { | ||||
|                 item.getAsync = this.syncFn2AsyncFn(item.get) as any; | ||||
|             } | ||||
|      | ||||
|             // 检验 setter
 | ||||
|             if (item.set && !item.setAsync) { | ||||
|                 item.setAsync = this.syncFn2AsyncFn(item.set) as any; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // 在注册表中查找
 | ||||
|         for (const key in this.registryList) fillFunction(key); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 将同步函数转换为异步函数 | ||||
|      * @param fn 同步函数 | ||||
|      */ | ||||
|     protected syncFn2AsyncFn<F extends (...p: any) => any> (fn: F): (...param: Parameters<F>) => ReturnType<F> { | ||||
|         const asyncFn = async (...param: [D]) => { | ||||
|             return fn(...param); | ||||
|         }; | ||||
|         return asyncFn as any; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { Data }; | ||||
| export default Data; | ||||
| @ -13,21 +13,6 @@ export type EventHandlerMap<Events extends Record<EventType, any>> = Map< | ||||
| 	EventHandlerList<Events[keyof Events]> | ||||
| >; | ||||
| 
 | ||||
| // Emitter function type
 | ||||
| type IEmitParamType<E extends Record<EventType, any>, K extends keyof E> =  | ||||
| 	E[K] extends ( undefined | void ) ? [type: K] : [type: K, evt: E[K]]; | ||||
| 
 | ||||
| // Mixin to event object
 | ||||
| export type EventMixin<A extends Record<EventType, any>, B extends Record<EventType, any>> = { | ||||
| 	[P in (keyof A | keyof B)] : | ||||
| 		P extends (keyof A & keyof B) ?  | ||||
| 			A[P] :  | ||||
| 			P extends keyof A ?  | ||||
| 				A[P] :  | ||||
| 				P extends keyof B ? B[P] :  | ||||
| 			never; | ||||
| } | ||||
| 
 | ||||
| export class Emitter<Events extends Record<EventType, any>> { | ||||
| 	 | ||||
| 	/** | ||||
| @ -89,8 +74,7 @@ export class Emitter<Events extends Record<EventType, any>> { | ||||
| 	 * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler | ||||
| 	 * @memberOf mitt | ||||
| 	 */ | ||||
| 	public emit<Key extends keyof Events>(...param: IEmitParamType<Events, Key>): this { | ||||
| 		const [ type, evt ] = param; | ||||
| 	emit<Key extends keyof Events>(type: Key, evt: Events[Key]) { | ||||
| 		let handlers = this.all!.get(type); | ||||
| 		if (handlers) { | ||||
| 			(handlers as EventHandlerList<Events[keyof Events]>) | ||||
| @ -99,6 +83,5 @@ export class Emitter<Events extends Record<EventType, any>> { | ||||
| 					handler(evt!); | ||||
| 				}); | ||||
| 		} | ||||
| 		return this; | ||||
| 	} | ||||
| } | ||||
| @ -491,27 +491,6 @@ class Manager<WXC extends AnyWXContext = AnyWXContext> { | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 异步页面加载 | ||||
|      *  | ||||
|      * *注意* | ||||
|      * 页面模块加载后,必须手动执行 loadAllModule | ||||
|      * loadAllModule Modular 才会真正的被加载 | ||||
|      * 模块加载后可以处理逻辑绑定 | ||||
|      */ | ||||
|     public static async PageAsync(): Promise<{ | ||||
|         manager: Manager<AnyWXContext>,  | ||||
|         query: Record<string, string | undefined> | ||||
|     }> { | ||||
|         return new Promise((solve) => { | ||||
|             Page({ | ||||
|                 async onLoad(query) { | ||||
|                     let manager = new Manager(this); | ||||
|                     await solve({ manager, query }); | ||||
|                 } | ||||
|             }) | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { Manager, Modular, AnyWXContext, WXContext, ILifetime} | ||||
| @ -1,154 +0,0 @@ | ||||
| import { Data } from "../core/Data"; | ||||
| import { Storage } from "../core/Storage"; | ||||
| import { Login, ILoginOutput } from "../api/Login"; | ||||
| 
 | ||||
| /** | ||||
|  * 登录状态 | ||||
|  */ | ||||
| enum LoginStatus { | ||||
| 
 | ||||
|     /** | ||||
|      * 已认证 | ||||
|      */ | ||||
|     verified = 1, | ||||
| 
 | ||||
|     /** | ||||
|      * 失效的认证 | ||||
|      * 通常为用户名密码错误 | ||||
|      */ | ||||
|     invalid = 2, | ||||
| 
 | ||||
|     /** | ||||
|      * 没有登录信息 | ||||
|      */ | ||||
|     none = 3 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * API 返回数据 | ||||
|  */ | ||||
| type ILoginApiData = { | ||||
|     [P in keyof ILoginOutput]: { | ||||
|         type: ILoginOutput[P]; | ||||
|         getAsync: () => Promise<ILoginOutput[P]>; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Storage 缓存数据类型 | ||||
|  */ | ||||
| type IStudentInfoStorageData = ILoginOutput & { | ||||
|     [P in keyof IStudentInfoData]: IStudentInfoData[P]["type"]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 学生信息数据结构 | ||||
|  */ | ||||
| type IStudentInfoData = { | ||||
| 
 | ||||
|     /** | ||||
|      * 学号 | ||||
|      */ | ||||
|     studentId: { | ||||
|         type: string, | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * 教务处密码 | ||||
|      */ | ||||
|     password: { | ||||
|         type: string | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * 登录状态 | ||||
|      */ | ||||
|     loginStatus: { | ||||
|         type: LoginStatus | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 上次登录时间 | ||||
|      * 时间戳 | ||||
|      */ | ||||
|     lastLoginTime: { | ||||
|         type: number | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 距离上次登录后 | ||||
|      * 学号和密码是否发生过改变 | ||||
|      */ | ||||
|     isUserInfoChange: { | ||||
|         type: boolean | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 学生信息 | ||||
|  */ | ||||
| class StudentInfo extends Data<IStudentInfoData & ILoginApiData> { | ||||
| 
 | ||||
|     /** | ||||
|      * 学生信息缓存 | ||||
|      */ | ||||
|     private eduStorage = new Storage<IStudentInfoStorageData>("StudentInfo", { | ||||
|         idCardLast6: "", | ||||
|         eduService: "", | ||||
|         actualName: "", | ||||
|         eduSession: "", | ||||
|         studentId: "", | ||||
|         password: "", | ||||
|         loginStatus: LoginStatus.none, | ||||
|         lastLoginTime: 0, | ||||
|         isUserInfoChange: false | ||||
|     }); | ||||
| 
 | ||||
|     public override onLoad() { | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 用户登录 | ||||
|      */ | ||||
|     private async login(): Promise<boolean> { | ||||
| 
 | ||||
|         // 获取账号密码
 | ||||
|         const stuId = this.eduStorage.get("studentId"); | ||||
|         const pwd = this.eduStorage.get("password"); | ||||
| 
 | ||||
|         if (!stuId || !pwd) return false; | ||||
| 
 | ||||
|         // 发送请求
 | ||||
|         const data = await new Login().param({ | ||||
|             studentId: stuId, | ||||
|             password: pwd | ||||
|         }).request().wait(); | ||||
| 
 | ||||
|         // 请求成功
 | ||||
|         let res = data.data; | ||||
|         if (res) { | ||||
| 
 | ||||
|             // 保存数据
 | ||||
|             this.eduStorage.set("actualName", res.actualName); | ||||
|             this.eduStorage.set("eduService", res.eduService); | ||||
|             this.eduStorage.set("eduSession", res.eduSession); | ||||
|             this.eduStorage.set("idCardLast6", res.idCardLast6); | ||||
| 
 | ||||
|             // 记录时间
 | ||||
|             this.eduStorage.set("lastLoginTime", new Date().getTime()); | ||||
| 
 | ||||
|             return true; | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取状态 | ||||
|      */ | ||||
|     private async getStatus() {} | ||||
| } | ||||
| 
 | ||||
| export { StudentInfo }; | ||||
| export default StudentInfo; | ||||
| @ -1,99 +0,0 @@ | ||||
| @import "../app.scss"; | ||||
| 
 | ||||
| view.mask { | ||||
| 	position: fixed; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	background-color: rgba($color: #000000, $alpha: .2); | ||||
| 	z-index: 1; | ||||
| } | ||||
| 
 | ||||
| view.layer { | ||||
| 	position: fixed; | ||||
| 	@include container; | ||||
| 	height: 100%; | ||||
| 	z-index: 2; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
| } | ||||
| 
 | ||||
| view.occlude { | ||||
| 	position: fixed; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	z-index: 3; | ||||
| } | ||||
| 
 | ||||
| view.mask.block, view.layer.block, view.occlude.block { | ||||
|     display: flex; | ||||
| } | ||||
| 
 | ||||
| view.mask.none, view.layer.none, view.occlude.none { | ||||
|     display: none; | ||||
| } | ||||
| 
 | ||||
| view.mask.show-fade, view.layer.show-fade, view.occlude.show-fade { | ||||
|     animation: show-fade .1s cubic-bezier(0, 0, 1, 1) both; | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| view.mask.hide-fade, view.layer.hide-fade, view.occlude.hide-fade { | ||||
|     animation: hide-fade .1s cubic-bezier(0, 0, 1, 1) both; | ||||
|     opacity: 0; | ||||
| } | ||||
| 
 | ||||
| view.mask.show-scale, view.layer.show-scale, view.occlude.show-scale { | ||||
|     animation: show-scale .3s cubic-bezier(.1, .9, .2, 1) both, | ||||
|     show-fade .1s cubic-bezier(0, 0, 1, 1) both; | ||||
|     transform: scale3d(1, 1, 1); | ||||
|     opacity: 1; | ||||
| } | ||||
| 
 | ||||
| view.mask.hide-scale, view.layer.hide-scale, view.occlude.hide-scale { | ||||
|     animation: hide-scale .3s cubic-bezier(.1, .9, .2, 1) both,  | ||||
|     hide-fade .1s cubic-bezier(0, 0, 1, 1) both; | ||||
|     transform: scale3d(.9, .9, 1); | ||||
|     opacity: 0; | ||||
| } | ||||
| 
 | ||||
| @media (prefers-color-scheme: dark) { | ||||
|     view.mask { | ||||
|         background-color: rgba($color: #000000, $alpha: .5); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @keyframes show-fade{ | ||||
|     from { | ||||
|         opacity: 0; | ||||
|     } | ||||
|     to { | ||||
|         opacity: 1; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @keyframes hide-fade{ | ||||
|     from { | ||||
|         opacity: 1; | ||||
|     } | ||||
|     to { | ||||
|         opacity: 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @keyframes show-scale{ | ||||
|     from { | ||||
|         transform: scale3d(1.15, 1.15, 1); | ||||
|     } | ||||
|     to { | ||||
|         transform: scale3d(1, 1, 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @keyframes hide-scale{ | ||||
|     from { | ||||
|         transform: scale3d(1, 1, 1); | ||||
|     } | ||||
|     to { | ||||
|         transform: scale3d(.9, .9, 1); | ||||
|     } | ||||
| } | ||||
| @ -1,309 +0,0 @@ | ||||
| import { IAnyData } from "../core/Api"; | ||||
| import { Emitter } from "../core/Emitter"; | ||||
| import { Modular, Manager } from "../core/Module"; | ||||
| 
 | ||||
| /** | ||||
|  * 动画类型 | ||||
|  */ | ||||
| enum AnimateType { | ||||
|     fade = 1, | ||||
|     scale = 2, | ||||
|     none = 3, | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 显示层 | ||||
|  */ | ||||
| class DisplayLayer { | ||||
| 
 | ||||
|     /** | ||||
|      * 类名 | ||||
|      */ | ||||
|     public get className(): string { | ||||
|         let res = this.isDisplay ? "block" : "none"; | ||||
| 
 | ||||
|         switch (this.animateType) { | ||||
|             case AnimateType.fade: | ||||
|                 res += " mask"; | ||||
|                 break; | ||||
| 
 | ||||
|             case AnimateType.scale: | ||||
|                 res += " layer"; | ||||
|                 break; | ||||
| 
 | ||||
|             case AnimateType.none: | ||||
|                 res += " occlude"; | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         switch (this.animateType) { | ||||
|             case AnimateType.fade: | ||||
|                 res += this.isShow ? " show-fade" : " hide-fade"; | ||||
|                 break; | ||||
| 
 | ||||
|             case AnimateType.scale: | ||||
|                 res += this.isShow ? " show-scale" : " hide-scale"; | ||||
|                 break; | ||||
| 
 | ||||
|             case AnimateType.none: | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return res; | ||||
|     } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Layer 使用的 key | ||||
| 	 */ | ||||
| 	public key: string = ""; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 使用的动画类型 | ||||
| 	 */ | ||||
| 	public animateType: AnimateType = AnimateType.scale; | ||||
| 
 | ||||
|     /** | ||||
|      * 动画时间 | ||||
|      */ | ||||
|     public get animateTime(): number { | ||||
|         switch (this.animateType) { | ||||
|             case AnimateType.fade: | ||||
|                 return 100; | ||||
|             case AnimateType.scale: | ||||
|                 return 300; | ||||
|             case AnimateType.none: | ||||
|                 return -1; | ||||
|         } | ||||
|     }; | ||||
| 	 | ||||
| 	/** | ||||
| 	 * 是否显示 | ||||
| 	 */ | ||||
| 	public isDisplay: boolean = false; | ||||
| 
 | ||||
| 	 /** | ||||
| 	  * 是否显示 | ||||
| 	  */ | ||||
| 	public isShow: boolean = false; | ||||
| 
 | ||||
| 	/** | ||||
|      * 消失动画计时器 | ||||
|      */ | ||||
| 	public disappearTimer?: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 弹出层事件 | ||||
|  */ | ||||
| type IPopupLayerEvent<L extends string> = { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 点击蒙版时 | ||||
| 	 */ | ||||
| 	clickMask: void | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 显示层 | ||||
| 	 */ | ||||
| 	show: L; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 隐藏层 | ||||
| 	 */ | ||||
| 	hide: L; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 层状态改变 | ||||
| 	 */ | ||||
| 	change: Readonly<DisplayLayer>; | ||||
| } | ||||
| 
 | ||||
| type commonLayerType = "mask" | "occlude"; | ||||
| 
 | ||||
| /** | ||||
|  * 弹出层 | ||||
|  */ | ||||
| class PopupLayer< | ||||
| 	L extends string, | ||||
| 	M extends Manager = Manager | ||||
| > extends Modular<M, {}, IPopupLayerEvent<L | commonLayerType>> { | ||||
| 
 | ||||
|     /** | ||||
|      * 当点击时是否自动关闭蒙版 | ||||
|      */ | ||||
|     public autoCloseOnClick: boolean = true; | ||||
| 
 | ||||
|     /** | ||||
|      * 关闭动画执行时是否屏蔽用户点击 | ||||
|      */ | ||||
|     public showOccludeWhenHide: boolean = true; | ||||
| 
 | ||||
|     /** | ||||
|      * 显示 Layer 时自动关闭其他层 | ||||
|      */ | ||||
|     public hideOtherWhenShow: boolean = true; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 层列表 | ||||
| 	 */ | ||||
| 	private layers: Map<L | commonLayerType, DisplayLayer> = new Map(); | ||||
| 
 | ||||
|     /** | ||||
|      * 初始化层 | ||||
|      * @param key 初始化关键字 | ||||
|      */ | ||||
|     public initLayers(key: L[]) { | ||||
|         for (let i = 0; i < key.length; i++) { | ||||
|             this.render(this.getDisplayLayer(key[i])); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 	public onLoad(): void { | ||||
| 		this.on("show", this.handleShowLayer); | ||||
| 		this.on("hide", this.handleHideLayer); | ||||
|         this.setFunc(this.handleClickMask, "clickMask"); | ||||
| 
 | ||||
|         // 添加蒙版层
 | ||||
|         const maskLayer = this.getDisplayLayer("mask"); | ||||
|         maskLayer.animateType = AnimateType.fade; | ||||
|         this.render(maskLayer); | ||||
| 
 | ||||
|         // 添加遮蔽层
 | ||||
|         const occludeLayer = this.getDisplayLayer("occlude"); | ||||
|         occludeLayer.animateType = AnimateType.none; | ||||
|         this.render(occludeLayer); | ||||
| 	} | ||||
| 
 | ||||
|     /** | ||||
|      * 渲染 Layers | ||||
|      */ | ||||
|     private render(layer: DisplayLayer) { | ||||
|         this.setData({ [`${ layer.key }$className`]: layer.className }); | ||||
|     } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 获取显示层 | ||||
| 	 */ | ||||
| 	private getDisplayLayer<K extends L | commonLayerType>(e: K): DisplayLayer { | ||||
| 		let displayLayer = this.layers.get(e); | ||||
| 		if (!displayLayer) { | ||||
| 			displayLayer = new DisplayLayer(); | ||||
| 			displayLayer.key = e; | ||||
| 			this.layers.set(e, displayLayer); | ||||
| 		} | ||||
| 		return displayLayer; | ||||
| 	} | ||||
| 
 | ||||
|     /** | ||||
|      * 响应蒙版点击事件 | ||||
|      */ | ||||
|     private handleClickMask = () => { | ||||
| 
 | ||||
|         if (!this.autoCloseOnClick) return; | ||||
| 
 | ||||
|         // 关闭全部开启的层
 | ||||
|         this.layers.forEach((layer) => { | ||||
|             if (layer.isShow) (this as Emitter<IAnyData>).emit("hide", layer.key); | ||||
|         }); | ||||
| 
 | ||||
|         // 关闭蒙版
 | ||||
|         (this as Emitter<IAnyData>).emit("hide", "mask"); | ||||
|     } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 响应显示层事件 | ||||
| 	 */ | ||||
| 	private handleShowLayer = <K extends L | commonLayerType>(e: K) => { | ||||
| 		let displayLayer = this.getDisplayLayer(e); | ||||
| 
 | ||||
|         // 阻止未发生的变化
 | ||||
|         if (displayLayer.isShow) return; | ||||
| 
 | ||||
|         // 关闭其他层
 | ||||
|         if (e !== "mask" && e !== "occlude" && this.hideOtherWhenShow) { | ||||
|             this.layers.forEach((layer) => { | ||||
|                 if (layer.key === "mask" || layer.key === "occlude") return; | ||||
|                 if (layer.isShow) { | ||||
|                     (this as Emitter<IAnyData>).emit("hide", layer.key); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // 显示蒙版
 | ||||
|         if (e !== "mask" && e !== "occlude") | ||||
|         (this as Emitter<IAnyData>).emit("show", "mask"); | ||||
| 
 | ||||
| 		// 取消消失定时
 | ||||
| 		displayLayer.disappearTimer &&  | ||||
| 		clearTimeout(displayLayer.disappearTimer); | ||||
| 
 | ||||
|         displayLayer.isShow = true; | ||||
|         displayLayer.isDisplay = true; | ||||
|         this.render(displayLayer); | ||||
| 
 | ||||
|         this.emit("change", displayLayer); | ||||
| 	}; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 响应隐藏层事件 | ||||
| 	 */ | ||||
| 	private handleHideLayer = <K extends L | commonLayerType>(e: K) => { | ||||
| 		let displayLayer = this.getDisplayLayer(e); | ||||
| 
 | ||||
|         // 阻止未发生的变化
 | ||||
|         if (!displayLayer.isShow) return; | ||||
| 
 | ||||
|         if (displayLayer.animateTime <= 0) { | ||||
| 
 | ||||
|             displayLayer.isShow = false; | ||||
|             displayLayer.isDisplay = false; | ||||
|             this.render(displayLayer); | ||||
|         } else { | ||||
| 
 | ||||
|             displayLayer.isShow = false; | ||||
|             this.render(displayLayer); | ||||
| 
 | ||||
|             // 开启遮蔽
 | ||||
|             if (this.showOccludeWhenHide) { | ||||
|                 (this as Emitter<IAnyData>).emit("show", "occlude"); | ||||
|             } | ||||
| 
 | ||||
|             displayLayer.disappearTimer = setTimeout(() => { | ||||
|                 displayLayer.isDisplay = false; | ||||
|                 this.render(displayLayer); | ||||
| 
 | ||||
|                 // 取消 timer
 | ||||
|                 displayLayer.disappearTimer = undefined; | ||||
| 
 | ||||
|                 // 检测是否关闭遮蔽
 | ||||
|                 let needOcclude = true; | ||||
|                 this.layers.forEach((layer) => { | ||||
|                     if (layer === displayLayer) return; | ||||
|                     if (layer.disappearTimer) needOcclude = false; | ||||
|                 }); | ||||
| 
 | ||||
|                 // 关闭遮蔽
 | ||||
|                 if (needOcclude && this.showOccludeWhenHide) { | ||||
|                     (this as Emitter<IAnyData>).emit("hide", "occlude"); | ||||
|                 } | ||||
| 
 | ||||
|             }, displayLayer.animateTime); | ||||
|         } | ||||
| 
 | ||||
|         // 关闭蒙版
 | ||||
|         if (e !== "mask" && e !== "occlude") { | ||||
|             let needMask = true; | ||||
|             this.layers.forEach((layer) => { | ||||
|                 if (layer === displayLayer) return; | ||||
|                 if (layer.isShow) needMask = false; | ||||
|             }); | ||||
| 
 | ||||
|             if (needMask) (this as Emitter<IAnyData>).emit("hide", "mask"); | ||||
|         } | ||||
| 
 | ||||
|         this.emit("change", displayLayer); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export { PopupLayer }; | ||||
| export default PopupLayer; | ||||
| @ -1,7 +1,7 @@ | ||||
| @import "./UserCard.scss"; | ||||
| @import "./MainFunction.scss"; | ||||
| @import "./FunctionList.scss"; | ||||
| @import "../../modular/PopupLayer.scss"; | ||||
| 
 | ||||
| 
 | ||||
| view.container{ | ||||
|     padding-top: 50rpx; | ||||
|  | ||||
| @ -2,37 +2,9 @@ import { Manager } from "../../core/Module"; | ||||
| import { UserCard } from "./UserCard"; | ||||
| import { MainFunction } from "./MainFunction"; | ||||
| import { FunctionList } from "./FunctionList"; | ||||
| import { PopupLayer } from "../../modular/PopupLayer"; | ||||
| import { TestLayerA } from "./TestLayerA"; | ||||
| 
 | ||||
| (async () => { | ||||
| 
 | ||||
|     // 初始化页面
 | ||||
|     const { manager, query } = await Manager.PageAsync(); | ||||
| 
 | ||||
|     // 添加弹出层 Modular
 | ||||
|     const popupLayer: PopupLayer<"layerA" | "layerB"> = manager.addModule(PopupLayer, "mask") as any; | ||||
| 
 | ||||
|     // 添加 UserCard Modular
 | ||||
|     const userCard = manager.addModule(UserCard, "userCard"); | ||||
| 
 | ||||
|     //#region test layer
 | ||||
|     popupLayer.initLayers(["layerA", "layerB"]); | ||||
|     const testLayerA = manager.addModule(TestLayerA, "testLayerA"); | ||||
|     userCard.on("clickChangeTheme", () => { | ||||
|         popupLayer.emit("show", "layerA"); | ||||
|     }) | ||||
|     testLayerA.on("click", () => { | ||||
|         popupLayer.emit("show", "layerB"); | ||||
|     }) | ||||
|     //#endregion
 | ||||
| 
 | ||||
|     // 添加 MainFunction Modular
 | ||||
| Manager.Page((manager) => { | ||||
|     manager.addModule(UserCard, "userCard"); | ||||
|     manager.addModule(MainFunction, "mainFunction"); | ||||
| 
 | ||||
|     // 添加 FunctionList Modular
 | ||||
|     manager.addModule(FunctionList, "functionList"); | ||||
| 
 | ||||
|     // 初始化全部 Modular
 | ||||
|     await manager.loadAllModule(query); | ||||
| })(); | ||||
| }); | ||||
| @ -1,18 +1,3 @@ | ||||
| <!-- 层遮蔽层 --> | ||||
| <view class="{{ mask$occlude$className }}" bindtap="mask$clickMask"></view> | ||||
| 
 | ||||
| <!-- 蒙版 --> | ||||
| <view class="{{ mask$mask$className }}" bindtap="mask$clickMask"></view> | ||||
| 
 | ||||
| <!-- 层A --> | ||||
| <view class="{{ mask$layerA$className }}" bindtap="mask$clickMask"> | ||||
|     <view class="card" style="height: 300px; line-height: 300px; text-align:center" catchtap="testLayerA$click">layerA(点击显示layerB)</view> | ||||
| </view> | ||||
| 
 | ||||
| <!-- 层B --> | ||||
| <view class="{{ mask$layerB$className }}" bindtap="mask$clickMask"> | ||||
|     <view class="card" style="height: 200px; line-height: 200px; text-align:center" catchtap>layerB</view> | ||||
| </view> | ||||
| 
 | ||||
| <!-- 顶部的阴影 --> | ||||
| <view class="top-shadow"></view> | ||||
| @ -29,9 +14,7 @@ | ||||
| 
 | ||||
|             <!-- 主题变换按钮 --> | ||||
|             <view class="theme"> | ||||
|                 <view bindtap="userCard$changeTheme"> | ||||
|                     <image class="icon-sub" src="../../image/account/Account_Theme.svg" /> | ||||
|                 </view> | ||||
|                 <image class="icon-sub" src="../../image/account/Account_Theme.svg" /> | ||||
|             </view> | ||||
|              | ||||
|             <!-- 用户昵称 --> | ||||
| @ -44,7 +27,7 @@ | ||||
|                 <view class="name">秦浩轩</view> | ||||
|                 <view class="certified"> | ||||
|                     <view class="certifi-info">已认证</view> | ||||
|                     <image class="text-icon" src="../../image/account/Account_OK.svg"></image> | ||||
|                     <view class="text-icon">√</view> | ||||
|                 </view> | ||||
|             </view> | ||||
| 
 | ||||
| @ -57,27 +40,74 @@ | ||||
|     </view> | ||||
| 
 | ||||
|     <!--主要功能--> | ||||
|     <view class="card main-function"> | ||||
|     <view class="card main-function"><!--四个功能合在一起的容器--> | ||||
|         <view class="branch-funtion"> | ||||
|             <view><!--每个功能的容器--> | ||||
|                 <image class="icon" src="../../image/account/Account_UserInfo.svg"></image><!--每个功能的图片--> | ||||
|                 <view>账号信息</view><!--每个功能的文字--> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <!--每个功能的容器--> | ||||
|         <view class="branch-funtion" wx:for="{{ mainFunction$mainFunctionList }}" wx:key="index"> | ||||
|             <view style="{{ index == (mainFunction$mainFunctionList - 1) ? 'border-bottom: 0px' : '' }}"> | ||||
|                 <!--每个功能的图片--> | ||||
|                 <image class="icon" src="../../image/account/Account_{{ item.iconUrl }}.svg"></image> | ||||
|                 <!--每个功能的文字--> | ||||
|                 <view>{{ item.displayName }}</view> | ||||
|         <view class="branch-funtion"> | ||||
|             <view> | ||||
|                 <image class="icon" src="../../image/account/Account_DateList.svg"></image> | ||||
|                 <view>课表缓存</view> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <view class="branch-funtion"> | ||||
|             <view> | ||||
|                 <image class="icon" src="../../image/account/Account_Customer.svg"></image> | ||||
|                 <view>功能定制</view> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <view class="branch-funtion"> | ||||
|             <view style="border-right: 0px;"> | ||||
|                 <image class="icon" src="../../image/account/Account_Settings.svg"></image> | ||||
|                 <view>更多设置</view> | ||||
|             </view> | ||||
|         </view> | ||||
|     </view> | ||||
| 
 | ||||
|     <!-- 功能列表 --> | ||||
|     <view class="card function-list"> | ||||
|         <view class="function"> | ||||
|             <view> | ||||
|                 <image class="icon func-icon" src="../../image/account/Account_Sponsor.svg" /> | ||||
|                 <view>赞助计划</view> | ||||
|                 <image class="icon-sub arrow" src="../../image/account/Account_Arrow.svg" /> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <!-- 每一行 --> | ||||
|         <view class="function" wx:for="{{ functionList$functionList }}" wx:key="index"> | ||||
|             <view style="{{ index == (functionList$functionList.length - 1) ? 'border-bottom: 0px' : '' }}"> | ||||
|                 <image class="icon func-icon" src="../../image/account/Account_{{ item.iconUrl }}.svg" /> | ||||
|                 <view>{{ item.displayName }}</view> | ||||
|         <view class="function"> | ||||
|             <view> | ||||
|                 <image class="icon func-icon" src="../../image/account/Account_PubilcAccount.svg" /> | ||||
|                 <view>公众号</view> | ||||
|                 <image class="icon-sub arrow" src="../../image/account/Account_Arrow.svg" /> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <view class="function"> | ||||
|             <view> | ||||
|                 <image class="icon func-icon" src="../../image/account/Account_FAQ.svg" /> | ||||
|                 <view>自助问答</view> | ||||
|                 <image class="icon-sub arrow" src="../../image/account/Account_Arrow.svg" /> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <view class="function"> | ||||
|             <view> | ||||
|                 <image class="icon func-icon" src="../../image/account/Account_AboutUs.svg" /> | ||||
|                 <view>关于我们</view> | ||||
|                 <image class="icon-sub arrow" src="../../image/account/Account_Arrow.svg" /> | ||||
|             </view> | ||||
|         </view> | ||||
| 
 | ||||
|         <view class="function"> | ||||
|             <view style="border-bottom: 0px;"> | ||||
|                 <image class="icon func-icon" src="../../image/account/Account_Support.svg" /> | ||||
|                 <view>联系客服</view> | ||||
|                 <image class="icon-sub arrow" src="../../image/account/Account_Arrow.svg" /> | ||||
|             </view> | ||||
|         </view> | ||||
|  | ||||
| @ -1,32 +1,7 @@ | ||||
| import { Modular, Manager } from "../../core/Module"; | ||||
| 
 | ||||
| interface IFunctionListItem { | ||||
| 
 | ||||
|     /** | ||||
|      * 显示名称 | ||||
|      */ | ||||
|     displayName: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 图标路径 | ||||
|      */ | ||||
|     iconUrl: string; | ||||
| } | ||||
| 
 | ||||
| class FunctionList<M extends Manager> extends Modular<M> { | ||||
|      | ||||
|     public static readonly functionList: IFunctionListItem[] = [ | ||||
|         { displayName: "赞助计划", iconUrl: "Sponsor" }, | ||||
|         { displayName: "公众号", iconUrl: "PubilcAccount" }, | ||||
|         { displayName: "自助问答", iconUrl: "FAQ" }, | ||||
|         { displayName: "关于我们", iconUrl: "AboutUs" }, | ||||
|         { displayName: "联系客服", iconUrl: "Support" } | ||||
|     ]; | ||||
| 
 | ||||
|     public data = { | ||||
|         functionList: FunctionList.functionList | ||||
|     }; | ||||
|      | ||||
|     public override onLoad() { | ||||
|         // Do something
 | ||||
|     } | ||||
|  | ||||
| @ -1,31 +1,7 @@ | ||||
| import { Modular, Manager } from "../../core/Module"; | ||||
| 
 | ||||
| interface IMainFunctionItem { | ||||
| 
 | ||||
|     /** | ||||
|      * 显示名称 | ||||
|      */ | ||||
|     displayName: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 图标路径 | ||||
|      */ | ||||
|     iconUrl: string; | ||||
| } | ||||
| 
 | ||||
| class MainFunction<M extends Manager> extends Modular<M> { | ||||
|      | ||||
|     public static readonly MainFunctionList: IMainFunctionItem[] = [ | ||||
|         { displayName: "账号信息", iconUrl: "UserInfo" }, | ||||
|         { displayName: "课表缓存", iconUrl: "DateList" }, | ||||
|         { displayName: "功能定制", iconUrl: "Customer" }, | ||||
|         { displayName: "更多设置", iconUrl: "Settings" } | ||||
|     ]; | ||||
| 
 | ||||
|     public data? = { | ||||
|         mainFunctionList: MainFunction.MainFunctionList | ||||
|     } | ||||
|      | ||||
|     public override onLoad() { | ||||
|         // Do something
 | ||||
|     } | ||||
|  | ||||
| @ -1,26 +0,0 @@ | ||||
| import { Modular, Manager } from "../../core/Module"; | ||||
| 
 | ||||
| type IUserCardEvent = { | ||||
| 
 | ||||
|     /** | ||||
|      * 主题更换按钮点击事件 | ||||
|      */ | ||||
|     click: void; | ||||
| } | ||||
| 
 | ||||
| class TestLayerA<M extends Manager> extends Modular<M, {}, IUserCardEvent> { | ||||
| 
 | ||||
|     public override onLoad() { | ||||
|         this.setFunc(this.handleClick, "click"); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * 弹窗点击时 | ||||
|      */ | ||||
|     private handleClick() { | ||||
|         this.emit("click"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export { TestLayerA }; | ||||
| export default TestLayerA; | ||||
| @ -27,17 +27,9 @@ view.user-card { | ||||
|             display: flex; | ||||
|             justify-content: flex-end; | ||||
| 
 | ||||
|             view { | ||||
|             image { | ||||
|                 width: 23px; | ||||
|                 height: 23px; | ||||
|                 padding: 20px; | ||||
|                 margin: -20px; | ||||
|                 border-radius: 20px; | ||||
|                  | ||||
|                 image { | ||||
|                     width: 100%; | ||||
|                     height: 100%; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
| @ -49,7 +41,6 @@ view.user-card { | ||||
|             text-overflow: ellipsis; | ||||
|         } | ||||
| 
 | ||||
|         // 学生信息 | ||||
|         view.student { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
| @ -66,10 +57,8 @@ view.user-card { | ||||
|                 justify-content: center; | ||||
|                 align-items: center; | ||||
| 
 | ||||
|                 image.text-icon { | ||||
|                     margin-left: .25em; | ||||
|                     width: 10px; | ||||
|                     height: 10px; | ||||
|                 view.text-icon { | ||||
|                     margin-left: .3em; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -1,24 +1,9 @@ | ||||
| import { Modular, Manager } from "../../core/Module"; | ||||
| 
 | ||||
| type IUserCardEvent = { | ||||
| 
 | ||||
|     /** | ||||
|      * 主题更换按钮点击事件 | ||||
|      */ | ||||
|     clickChangeTheme: void; | ||||
| } | ||||
| 
 | ||||
| class UserCard<M extends Manager> extends Modular<M, {}, IUserCardEvent> { | ||||
| class UserCard<M extends Manager> extends Modular<M> { | ||||
|      | ||||
|     public override onLoad() { | ||||
|         this.setFunc(this.handleChangeTheme, "changeTheme"); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * 处理主题更换 | ||||
|      */ | ||||
|     private handleChangeTheme() { | ||||
|         this.emit("clickChangeTheme"); | ||||
|         // Do something
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import { Modular, Manager, ILifetime } from "../../core/Module"; | ||||
| import { Login } from "../../api/Login"; | ||||
| import { Schedlue } from "../../api/Schedule" | ||||
| import { Storage } from "../../core/Storage"; | ||||
| 
 | ||||
| /** | ||||
| @ -29,14 +28,12 @@ implements Partial<ILifetime> { | ||||
|             s.set("be", 12); | ||||
|         }, 1000) | ||||
|          | ||||
|         // new Login().param({studentId: "2017060129", password: ""})
 | ||||
|         // .request().wait({
 | ||||
|         //     ok: (w) => {console.log("ok", w)},
 | ||||
|         //     no: (w) => {console.log("no", w)},
 | ||||
|         //     done: (w) => {console.log("done", w)}
 | ||||
|         // });
 | ||||
|         // new Schedlue().param({cookie:"C729D1AB1B17077485ACCD9279135C22",semester:"2020-2021-2"})
 | ||||
|         // .request()
 | ||||
|         new Login().param({studentId: "1806240113", password: "qazxsw123"}) | ||||
|         .request().wait({ | ||||
|             ok: (w) => {console.log("ok", w)}, | ||||
|             no: (w) => {console.log("no", w)}, | ||||
|             done: (w) => {console.log("done", w)} | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -6,17 +6,7 @@ import { TestCore } from "./TestCore"; | ||||
|  * 此页面使用 Manager 进行模块化管理 | ||||
|  * 若要添加先功能请先定义 Modular 并添加至 Manager | ||||
|  */ | ||||
| (async () => { | ||||
| 
 | ||||
|     // 初始化页面
 | ||||
|     const { manager, query } = await Manager.PageAsync(); | ||||
| 
 | ||||
|     // 添加 StatusBar Modular
 | ||||
| Manager.Page((manager)=>{ | ||||
|     manager.addModule(StatusBar, "statusBar"); | ||||
|      | ||||
|     // 添加 TestCore Modular
 | ||||
|     manager.addModule(TestCore, "testCore"); | ||||
| 
 | ||||
|     // 初始化全部 Modular
 | ||||
|     await manager.loadAllModule(query); | ||||
| })() | ||||
| }) | ||||
| @ -1,8 +1,7 @@ | ||||
| { | ||||
|   "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", | ||||
|   "description": "项目配置文件", | ||||
|   "packOptions": { | ||||
|     "ignore": [], | ||||
|     "include": [] | ||||
|     "ignore": [] | ||||
|   }, | ||||
|   "miniprogramRoot": "miniprogram/", | ||||
|   "compileType": "miniprogram", | ||||
| @ -47,16 +46,13 @@ | ||||
|     "useCompilerPlugins": [ | ||||
|       "typescript", | ||||
|       "sass" | ||||
|     ], | ||||
|     "useStaticServer": true | ||||
|     ] | ||||
|   }, | ||||
|   "simulatorType": "wechat", | ||||
|   "simulatorPluginLibVersion": {}, | ||||
|   "appid": "wx7d809f5e8955843d", | ||||
|   "condition": {}, | ||||
|   "srcMiniprogramRoot": "miniprogram/", | ||||
|   "editorSetting": { | ||||
|     "tabIndent": "insertSpaces", | ||||
|     "tabSize": 2 | ||||
|   } | ||||
|   "scripts": { | ||||
|     "beforeCompile": "" | ||||
|   }, | ||||
|   "condition": {} | ||||
| } | ||||
| @ -1,5 +1,14 @@ | ||||
| { | ||||
|   "condition": { | ||||
|     "plugin": { | ||||
|       "list": [] | ||||
|     }, | ||||
|     "game": { | ||||
|       "list": [] | ||||
|     }, | ||||
|     "gamePlugin": { | ||||
|       "list": [] | ||||
|     }, | ||||
|     "miniprogram": { | ||||
|       "list": [ | ||||
|         { | ||||
| @ -10,10 +19,5 @@ | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "projectname": "mini-dlpu-v3", | ||||
|   "setting": { | ||||
|     "compileHotReLoad": true | ||||
|   }, | ||||
|   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html" | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user