Compare commits

..

45 Commits

Author SHA1 Message Date
bb962cb8cf Merge pull request 'Update project config' (#68) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/68
2022-06-09 17:25:22 +08:00
d733c75371 Update project config 2022-06-09 17:23:09 +08:00
2fd3f5a0db (#22) Add Data Modular. 2022-01-28 08:41:22 +08:00
f2f68c1f4a Merge branch 'master' into dev-mrkbear 2022-01-26 14:51:03 +08:00
1917828eab Merge pull request '(#22)(#56) Add student info data ware.' (#67) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/67
2022-01-25 17:13:12 +08:00
afae6b7903 (#22)(#56) Add student info data ware. 2022-01-25 17:11:29 +08:00
b2854908a4 Merge pull request '(#56) Data prototype use custrmers function.' (#65) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/65
2022-01-25 13:05:55 +08:00
7f86ae9fd2 Merge branch 'master' into dev-mrkbear 2022-01-25 13:05:42 +08:00
22188907c2 (#56) Data prototype use custrmers function. 2022-01-25 13:04:41 +08:00
33e49cb8be Merge pull request '(#36) Optimization popup layer modular.' (#64) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/64
2022-01-24 16:57:00 +08:00
1dcc09980c (#36) Optimization popup layer modular. 2022-01-24 16:55:58 +08:00
6a4cae757d Merge pull request '(#36) Merge mask modular & popup modular in popup layer modular.' (#63) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/63
2022-01-24 14:59:33 +08:00
18381aa0c4 (#36) Merge mask modular & popup modular in popup layer modular. 2022-01-24 14:58:33 +08:00
943798f53c (#36) Add popuplayer. 2022-01-23 20:34:52 +08:00
a5dc26cd17 Merge pull request '(#60) Remove mixin generic paradigm.' (#62) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/62
2022-01-23 12:10:05 +08:00
464a9d3e59 (#60) Remove mixin generic paradigm. 2022-01-23 12:08:38 +08:00
292a4c26c5 (#60) Core emitter generic optimization 2022-01-22 17:50:25 +08:00
e97514a67f (#60) Core emitter generic optimization 2022-01-22 17:49:03 +08:00
27ac19141f Merge pull request '(#60) Core emitter generic optimization' (#61) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/61
2022-01-22 16:13:40 +08:00
c385965655 (#60) Core emitter generic optimization 2022-01-22 16:10:24 +08:00
43d87b40f7 Merge pull request '(#21) Mixin IAnyData in app' (#59) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/59
2022-01-21 17:39:13 +08:00
660f6921de (#21) Mixin IAnyData in app 2022-01-21 17:38:41 +08:00
86101e01d1 Merge pull request '(#22) Add StudentInfo file' (#58) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/58
2022-01-21 17:37:05 +08:00
1f2a2ad6bd (#22) Add StudentInfo file 2022-01-21 17:36:16 +08:00
b91a9271a7 Merge pull request '(#56) Add Core Data.' (#57) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/57
2022-01-21 17:33:10 +08:00
ec635b9ae6 (#56) Add Core Data. 2022-01-21 17:30:08 +08:00
6f96a69900 Merge pull request '(#35)Add mask modular event.' (#55) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/55
2022-01-21 10:43:24 +08:00
58d8d74158 (#35)Add mask modular event. 2022-01-21 10:38:37 +08:00
96b3414d22 Merge pull request '(#28)Add a api frame (incomplete file)' (#51) from dev-tamako into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/51
2022-01-20 23:52:35 +08:00
529011b0dd (#28)Add Schedule API 2022-01-20 23:47:02 +08:00
4da257c2e2 (#28)Add Schedule API 2022-01-20 22:19:10 +08:00
f11eb63ee1 Merge branch 'master' into dev-tamako 2022-01-20 21:45:30 +08:00
49c4f7f0d4 Merge pull request '(#52) Add EduBase API' (#53) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/53
2022-01-20 21:43:41 +08:00
4158887d2e (#52) Add EduBase API 2022-01-20 21:42:44 +08:00
a8e16f5972 (#28) Add output data mod 2022-01-20 20:57:41 +08:00
d9825d6d68 (#28)Add a api frame (incomplete file) 2022-01-20 20:38:57 +08:00
fab73f00ab Merge branch 'master' into dev-tamako 2022-01-20 20:33:02 +08:00
edb26b3d8b Merge branch 'dev-tamako' of http://git.mrkbear.com/MrKBear/mini-dlpu-v3 into dev-tamako 2022-01-20 20:31:52 +08:00
443f82ea75 (#28)Add a api frame (incomplete file) 2022-01-20 20:30:36 +08:00
75adb97abb (#28)Add a api frame (incomplete file) 2022-01-20 20:17:32 +08:00
b36996e352 Merge pull request '(#35) Add mask show hide motion' (#50) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/50
2022-01-20 17:20:42 +08:00
6bef08f12e (#35) Add mask show hide motion 2022-01-20 17:18:44 +08:00
1f24a59bd9 Merge pull request '(#48) Core Api baseUrl param can be overridden' (#49) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/mini-dlpu-v3/pulls/49
2022-01-20 16:11:42 +08:00
14d0c9c123 (#48) Core Api baseUrl param can be overridden 2022-01-20 16:10:55 +08:00
7a5d43281b Add Schedule Api file 2022-01-17 22:52:51 +08:00
21 changed files with 1191 additions and 201 deletions

View File

@ -0,0 +1,95 @@
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;

View File

@ -1,4 +1,5 @@
import { API, HTTPMethod, IParamSetting, GeneralCallbackResult } from "../core/Api"; import { HTTPMethod, IParamSetting } from "../core/Api";
import { EduBase } from "./EduBase";
interface ILoginInput { interface ILoginInput {
@ -31,46 +32,18 @@ interface ILoginOutput {
*/ */
actualName: string; actualName: string;
/**
*
*/
isSubscribeWxAccount: boolean;
/** /**
* session * session
*/ */
eduSession: string; eduSession: string;
} }
interface ILoginEvent {
/**
* session
*/
expire: GeneralCallbackResult;
/**
*
*/
unauthorized: GeneralCallbackResult;
/**
*
*/
error: GeneralCallbackResult;
/**
*
*/
badData: GeneralCallbackResult;
}
/** /**
* Login API * Login API
* API * API
* session * session
*/ */
class Login extends API<ILoginInput, ILoginOutput, ILoginEvent> { class Login extends EduBase<ILoginInput, ILoginOutput> {
public override url: string = "/login"; public override url: string = "/login";
@ -92,57 +65,13 @@ class Login extends API<ILoginInput, ILoginOutput, ILoginEvent> {
super(); super();
this.initDebugLabel("Login"); this.initDebugLabel("Login");
this.addFailedCallBack(); this.useEduCallback((data) => ({
idCardLast6: data.idCard,
this.on("success", (data) => { eduService: data.ip,
actualName: data.name,
let isSuccess = true; isSubscribeWxAccount: data.official,
let errMsg = ""; eduSession: data.session
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 });
});
} }
} }

110
miniprogram/api/Schedule.ts Normal file
View File

@ -0,0 +1,110 @@
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;

View File

@ -16,12 +16,16 @@ $black-filter: brightness(0) opacity(.65);
$white-filter: brightness(100) opacity(.65); $white-filter: brightness(100) opacity(.65);
$blue-filter: opacity(1); $blue-filter: opacity(1);
// 页面容器外边距 @mixin container {
view.container {
width: 88%; width: 88%;
padding: 0 6%; padding: 0 6%;
} }
// 页面容器外边距
view.container {
@include container;
}
// 带阴影的 card // 带阴影的 card
view.card { view.card {
width: calc( 100% - 40px ); width: calc( 100% - 40px );

View File

@ -1,9 +1,9 @@
import { IAppAPIParam } from "./core/Api"; import { IAppAPIParam, IAnyData } from "./core/Api";
import { IAppStorageParam, Storage, IStorageData } from "./core/Storage"; import { IAppStorageParam, Storage, IStorageData } from "./core/Storage";
import { Logger, LevelLogLabel, LifeCycleLogLabel } from "./core/Logger"; import { Logger, LevelLogLabel, LifeCycleLogLabel } from "./core/Logger";
App<IAppAPIParam & IAppStorageParam>({ App<IAppAPIParam & IAppStorageParam & IAnyData>({
/** /**
* API * API

View File

@ -1,4 +1,4 @@
import { Emitter, EventType } from "./Emitter"; import { Emitter, EventType, EventMixin } from "./Emitter";
import { API_FAILED_SHOW_MESSAGE } from "./Config"; import { API_FAILED_SHOW_MESSAGE } from "./Config";
import { Logger, LogLabel, LevelLogLabel, colorRadio, StatusLabel } from "./Logger"; import { Logger, LogLabel, LevelLogLabel, colorRadio, StatusLabel } from "./Logger";
interface IAppAPIParam { interface IAppAPIParam {
@ -156,28 +156,20 @@ class API<
O extends IAnyData = IAnyData, O extends IAnyData = IAnyData,
E extends Record<EventType, any> = Record<EventType, any>, E extends Record<EventType, any> = Record<EventType, any>,
U extends IAnyData = IAnyData U extends IAnyData = IAnyData
> extends Emitter < > extends Emitter<EventMixin<IAPIEvent<I, O> & IAPIResultEvent<O, U>, E>> {
{
// 这个复杂的泛型是为了 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]
}
> {
/**
*
*/
public static defaultLogLabel:LogLabel = new LogLabel(
`API:API`, colorRadio(200, 120, 222)
);
/** /**
* URL * URL
* TODO: 这里可能涉及负载均衡 * TODO: 这里可能涉及负载均衡
*/ */
public static get baseUrl():string { public baseUrl: string = "https://jwc.nogg.cn";
return "https://jwc.nogg.cn";
}
public static defaultLogLabel:LogLabel = new LogLabel(
`API:API`, colorRadio(200, 120, 222)
);
/** /**
* Logger 使 * Logger 使
@ -315,11 +307,11 @@ class API<
} }
// 触发数据初始化事件 // 触发数据初始化事件
this.emit("initData", this.data); (this as Emitter<IAnyData>).emit("initData", this.data);
// 重置请求数据 // 重置请求数据
const requestData:IWxRequestOption<O> = this.requestData = { const requestData:IWxRequestOption<O> = this.requestData = {
url: API.baseUrl + this.url, url: this.baseUrl + this.url,
data: {}, header: {}, data: {}, header: {},
timeout: this.timeout, timeout: this.timeout,
method: this.method, method: this.method,
@ -342,7 +334,7 @@ class API<
} }
// 触发数据解析 // 触发数据解析
this.emit("parseRequestData", this.data); (this as Emitter<IAnyData>).emit("parseRequestData", this.data);
// 数据收集 // 数据收集
for (let key in this.params) { for (let key in this.params) {
@ -460,18 +452,18 @@ class API<
let request = () => { let request = () => {
// 触发请求发送事件 // 触发请求发送事件
this.emit("request", this.requestData!) (this as Emitter<IAnyData>).emit("request", this.requestData!)
wx.request<O>({ wx.request<O>({
...this.requestData!, ...this.requestData!,
success: (e) => { success: (e) => {
this.emit("success", e); (this as Emitter<IAnyData>).emit("success", e);
}, },
fail: (e) => { fail: (e) => {
this.emit("fail", e); (this as Emitter<IAnyData>).emit("fail", e);
}, },
complete: (e) => { complete: (e) => {
this.emit("complete", e); (this as Emitter<IAnyData>).emit("complete", e);
} }
}); });
} }
@ -489,12 +481,12 @@ class API<
// 使用上次请求结果 // 使用上次请求结果
if (this.policy === RequestPolicy.useLastRequest) { if (this.policy === RequestPolicy.useLastRequest) {
lastAPI.on("success", (e) => { lastAPI.on("success", (e) => {
this.emit("success", e as SuccessCallbackResult<O>); (this as Emitter<IAnyData>).emit("success", e as SuccessCallbackResult<O>);
this.emit("complete", {errMsg: e.errMsg}); (this as Emitter<IAnyData>).emit("complete", {errMsg: e.errMsg});
}); });
lastAPI.on("fail", (e) => { lastAPI.on("fail", (e) => {
this.emit("fail", e); (this as Emitter<IAnyData>).emit("fail", e);
this.emit("complete", {errMsg: e.errMsg}); (this as Emitter<IAnyData>).emit("complete", {errMsg: e.errMsg});
}); });
} }
@ -604,8 +596,8 @@ class API<
*/ */
public addFailedCallBack(): this { public addFailedCallBack(): this {
this.on("fail", (e) => { this.on("fail", (e) => {
this.emit("no", e as any); (this as Emitter<IAnyData>).emit("no", e as any);
this.emit("done", e as any); (this as Emitter<IAnyData>).emit("done", e as any);
}); });
return this; return this;
} }
@ -697,4 +689,4 @@ enum HTTPMethod {
} }
export default API; export default API;
export { API, IParamSetting, IAppAPIParam, ICallBack, HTTPMethod, RequestPolicy, GeneralCallbackResult } export { API, IParamSetting, IAppAPIParam, IAnyData, ICallBack, HTTPMethod, RequestPolicy, GeneralCallbackResult }

277
miniprogram/core/Data.ts Normal file
View File

@ -0,0 +1,277 @@
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;

View File

@ -13,6 +13,21 @@ export type EventHandlerMap<Events extends Record<EventType, any>> = Map<
EventHandlerList<Events[keyof Events]> 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>> { export class Emitter<Events extends Record<EventType, any>> {
/** /**
@ -74,7 +89,8 @@ export class Emitter<Events extends Record<EventType, any>> {
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
* @memberOf mitt * @memberOf mitt
*/ */
emit<Key extends keyof Events>(type: Key, evt: Events[Key]) { public emit<Key extends keyof Events>(...param: IEmitParamType<Events, Key>): this {
const [ type, evt ] = param;
let handlers = this.all!.get(type); let handlers = this.all!.get(type);
if (handlers) { if (handlers) {
(handlers as EventHandlerList<Events[keyof Events]>) (handlers as EventHandlerList<Events[keyof Events]>)
@ -83,5 +99,6 @@ export class Emitter<Events extends Record<EventType, any>> {
handler(evt!); handler(evt!);
}); });
} }
return this;
} }
} }

View File

@ -0,0 +1,154 @@
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;

View File

@ -1,8 +0,0 @@
view.mask {
position: fixed;
width: 100%;
height: 100%;
background-color: rgba($color: #000000, $alpha: .2);
z-index: 1;
}

View File

@ -1,45 +0,0 @@
import { Modular, Manager } from "../../core/Module";
class Mask<M extends Manager> extends Modular<M> {
public data? = {
/**
*
*/
zIndex: 1,
/**
*
*/
isShow: false
};
private disappearTimer?: number;
/**
*
*/
public showMask() {
this.setData({ isShow: true });
}
/**
*
*/
public hideMask() {
this.setData({ isShow: false });
}
public override onLoad() {
this.setFunc(this.handleClickMask, "handleClickMask");
// Do something
}
private handleClickMask() {
this.hideMask();
}
}
export { Mask };
export default Mask;

View File

@ -0,0 +1,99 @@
@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);
}
}

View File

@ -0,0 +1,309 @@
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;

View File

@ -1,7 +1,7 @@
@import "./UserCard.scss"; @import "./UserCard.scss";
@import "./MainFunction.scss"; @import "./MainFunction.scss";
@import "./FunctionList.scss"; @import "./FunctionList.scss";
@import "../../modular/Mask/Mask.scss"; @import "../../modular/PopupLayer.scss";
view.container{ view.container{
padding-top: 50rpx; padding-top: 50rpx;

View File

@ -2,18 +2,30 @@ import { Manager } from "../../core/Module";
import { UserCard } from "./UserCard"; import { UserCard } from "./UserCard";
import { MainFunction } from "./MainFunction"; import { MainFunction } from "./MainFunction";
import { FunctionList } from "./FunctionList"; import { FunctionList } from "./FunctionList";
import { Mask } from "../../modular/Mask/Mask"; import { PopupLayer } from "../../modular/PopupLayer";
import { TestLayerA } from "./TestLayerA";
(async () => { (async () => {
// 初始化页面 // 初始化页面
const { manager, query } = await Manager.PageAsync(); const { manager, query } = await Manager.PageAsync();
// 添加蒙版 Modular // 添加弹出层 Modular
const mask = manager.addModule(Mask, "mask"); const popupLayer: PopupLayer<"layerA" | "layerB"> = manager.addModule(PopupLayer, "mask") as any;
// 添加 UserCard Modular // 添加 UserCard Modular
manager.addModule(UserCard, "userCard", { mask }); 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 // 添加 MainFunction Modular
manager.addModule(MainFunction, "mainFunction"); manager.addModule(MainFunction, "mainFunction");

View File

@ -1,5 +1,18 @@
<!-- 层遮蔽层 -->
<view class="{{ mask$occlude$className }}" bindtap="mask$clickMask"></view>
<!-- 蒙版 --> <!-- 蒙版 -->
<view class="mask" bindtap="mask$handleClickMask" style="display:{{mask$isShow ? 'block' : 'none'}}"></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> <view class="top-shadow"></view>

View File

@ -0,0 +1,26 @@
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;

View File

@ -1,21 +1,24 @@
import { Modular, Manager } from "../../core/Module"; import { Modular, Manager } from "../../core/Module";
import { Mask } from "../../modular/Mask/Mask";
type IUserCardDependent<M extends Manager> = { type IUserCardEvent = {
mask: Mask<M>
/**
*
*/
clickChangeTheme: void;
} }
class UserCard<M extends Manager> extends Modular<M, IUserCardDependent<M>> { class UserCard<M extends Manager> extends Modular<M, {}, IUserCardEvent> {
public override onLoad() { public override onLoad() {
this.setFunc(this.handleChangeTheme, "changeTheme") this.setFunc(this.handleChangeTheme, "changeTheme");
} }
/** /**
* *
*/ */
private handleChangeTheme() { private handleChangeTheme() {
this.depends?.mask.showMask(); this.emit("clickChangeTheme");
} }
} }

View File

@ -1,5 +1,6 @@
import { Modular, Manager, ILifetime } from "../../core/Module"; import { Modular, Manager, ILifetime } from "../../core/Module";
import { Login } from "../../api/Login"; import { Login } from "../../api/Login";
import { Schedlue } from "../../api/Schedule"
import { Storage } from "../../core/Storage"; import { Storage } from "../../core/Storage";
/** /**
@ -28,12 +29,14 @@ implements Partial<ILifetime> {
s.set("be", 12); s.set("be", 12);
}, 1000) }, 1000)
// new Login().param({studentId: "1806240113", password: ""}) // new Login().param({studentId: "2017060129", password: ""})
// .request().wait({ // .request().wait({
// ok: (w) => {console.log("ok", w)}, // ok: (w) => {console.log("ok", w)},
// no: (w) => {console.log("no", w)}, // no: (w) => {console.log("no", w)},
// done: (w) => {console.log("done", w)} // done: (w) => {console.log("done", w)}
// }); // });
// new Schedlue().param({cookie:"C729D1AB1B17077485ACCD9279135C22",semester:"2020-2021-2"})
// .request()
} }
} }

View File

@ -1,7 +1,8 @@
{ {
"description": "项目配置文件", "description": "项目配置文件详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"packOptions": { "packOptions": {
"ignore": [] "ignore": [],
"include": []
}, },
"miniprogramRoot": "miniprogram/", "miniprogramRoot": "miniprogram/",
"compileType": "miniprogram", "compileType": "miniprogram",
@ -46,13 +47,16 @@
"useCompilerPlugins": [ "useCompilerPlugins": [
"typescript", "typescript",
"sass" "sass"
] ],
"useStaticServer": true
}, },
"simulatorType": "wechat", "simulatorType": "wechat",
"simulatorPluginLibVersion": {}, "simulatorPluginLibVersion": {},
"appid": "wx7d809f5e8955843d", "appid": "wx7d809f5e8955843d",
"scripts": { "condition": {},
"beforeCompile": "" "srcMiniprogramRoot": "miniprogram/",
}, "editorSetting": {
"condition": {} "tabIndent": "insertSpaces",
"tabSize": 2
}
} }

View File

@ -1,14 +1,5 @@
{ {
"condition": { "condition": {
"plugin": {
"list": []
},
"game": {
"list": []
},
"gamePlugin": {
"list": []
},
"miniprogram": { "miniprogram": {
"list": [ "list": [
{ {
@ -19,5 +10,10 @@
} }
] ]
} }
} },
"projectname": "mini-dlpu-v3",
"setting": {
"compileHotReLoad": true
},
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
} }