Compare commits
65 Commits
dev-gia_ji
...
master
Author | SHA1 | Date | |
---|---|---|---|
bb962cb8cf | |||
d733c75371 | |||
2fd3f5a0db | |||
f2f68c1f4a | |||
1917828eab | |||
afae6b7903 | |||
b2854908a4 | |||
7f86ae9fd2 | |||
22188907c2 | |||
33e49cb8be | |||
1dcc09980c | |||
6a4cae757d | |||
18381aa0c4 | |||
943798f53c | |||
a5dc26cd17 | |||
464a9d3e59 | |||
292a4c26c5 | |||
e97514a67f | |||
27ac19141f | |||
c385965655 | |||
43d87b40f7 | |||
660f6921de | |||
86101e01d1 | |||
1f2a2ad6bd | |||
b91a9271a7 | |||
ec635b9ae6 | |||
6f96a69900 | |||
58d8d74158 | |||
96b3414d22 | |||
529011b0dd | |||
4da257c2e2 | |||
f11eb63ee1 | |||
49c4f7f0d4 | |||
4158887d2e | |||
a8e16f5972 | |||
d9825d6d68 | |||
fab73f00ab | |||
edb26b3d8b | |||
443f82ea75 | |||
75adb97abb | |||
b36996e352 | |||
6bef08f12e | |||
1f24a59bd9 | |||
14d0c9c123 | |||
b99a3412e2 | |||
63300f68f8 | |||
acf4f94798 | |||
87f4d220e5 | |||
cf4dd727c5 | |||
49055e892c | |||
afedb81633 | |||
04b8bf365f | |||
874c64829a | |||
82e1c0941e | |||
3119c862b1 | |||
23f46a24c9 | |||
fc8aa19c67 | |||
72448038d1 | |||
95f1f6d484 | |||
54d87fc5db | |||
f434ad531c | |||
![]() |
a92f2d06ea | ||
![]() |
45e8ef9322 | ||
![]() |
7c1e4e441a | ||
7a5d43281b |
95
miniprogram/api/EduBase.ts
Normal file
95
miniprogram/api/EduBase.ts
Normal 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;
|
@ -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
110
miniprogram/api/Schedule.ts
Normal 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;
|
@ -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 );
|
||||||
|
@ -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 模块需要的全局数据
|
||||||
|
@ -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
277
miniprogram/core/Data.ts
Normal 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;
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -491,6 +491,27 @@ 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}
|
export { Manager, Modular, AnyWXContext, WXContext, ILifetime}
|
154
miniprogram/data/StudentInfo.ts
Normal file
154
miniprogram/data/StudentInfo.ts
Normal 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;
|
99
miniprogram/modular/PopupLayer.scss
Normal file
99
miniprogram/modular/PopupLayer.scss
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
309
miniprogram/modular/PopupLayer.ts
Normal file
309
miniprogram/modular/PopupLayer.ts
Normal 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;
|
@ -1,7 +1,10 @@
|
|||||||
@import "./UserCard.scss";
|
@import "./UserCard.scss";
|
||||||
@import "./MainFunction.scss";
|
@import "./MainFunction.scss";
|
||||||
@import "./FunctionList.scss";
|
@import "./FunctionList.scss";
|
||||||
|
@import "../../modular/PopupLayer.scss";
|
||||||
|
|
||||||
|
view.container{
|
||||||
|
padding-top: 50rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
view.container {
|
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
@ -2,9 +2,37 @@ 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 { PopupLayer } from "../../modular/PopupLayer";
|
||||||
|
import { TestLayerA } from "./TestLayerA";
|
||||||
|
|
||||||
Manager.Page((manager) => {
|
(async () => {
|
||||||
manager.addModule(UserCard, "userCard");
|
|
||||||
|
// 初始化页面
|
||||||
|
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.addModule(MainFunction, "mainFunction");
|
manager.addModule(MainFunction, "mainFunction");
|
||||||
|
|
||||||
|
// 添加 FunctionList Modular
|
||||||
manager.addModule(FunctionList, "functionList");
|
manager.addModule(FunctionList, "functionList");
|
||||||
});
|
|
||||||
|
// 初始化全部 Modular
|
||||||
|
await manager.loadAllModule(query);
|
||||||
|
})();
|
@ -1,3 +1,18 @@
|
|||||||
|
<!-- 层遮蔽层 -->
|
||||||
|
<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>
|
<view class="top-shadow"></view>
|
||||||
@ -14,7 +29,9 @@
|
|||||||
|
|
||||||
<!-- 主题变换按钮 -->
|
<!-- 主题变换按钮 -->
|
||||||
<view class="theme">
|
<view class="theme">
|
||||||
<image class="icon-sub" src="../../image/account/Account_Theme.svg" />
|
<view bindtap="userCard$changeTheme">
|
||||||
|
<image class="icon-sub" src="../../image/account/Account_Theme.svg" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 用户昵称 -->
|
<!-- 用户昵称 -->
|
||||||
@ -27,7 +44,7 @@
|
|||||||
<view class="name">秦浩轩</view>
|
<view class="name">秦浩轩</view>
|
||||||
<view class="certified">
|
<view class="certified">
|
||||||
<view class="certifi-info">已认证</view>
|
<view class="certifi-info">已认证</view>
|
||||||
<view class="text-icon">√</view>
|
<image class="text-icon" src="../../image/account/Account_OK.svg"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -40,26 +57,29 @@
|
|||||||
</view>
|
</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 class="branch-funtion" wx:for="{{ mainFunction$mainFunctionList }}" wx:key="index">
|
||||||
</view></view>
|
<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>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="branch-funtion"><view>
|
<!-- 功能列表 -->
|
||||||
<image class="icon" src="../../image/account/Account_DateList.svg"></image>
|
<view class="card function-list">
|
||||||
<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 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>
|
||||||
|
<image class="icon-sub arrow" src="../../image/account/Account_Arrow.svg" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
@ -1 +1,43 @@
|
|||||||
@import "../../app.scss";
|
@import "../../app.scss";
|
||||||
|
|
||||||
|
view.function-list {
|
||||||
|
margin-top: 50rpx;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
padding: 0 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
|
||||||
|
view.function {
|
||||||
|
padding: 0 20px;
|
||||||
|
width: calc( 100% - 40px );
|
||||||
|
height: 55px;
|
||||||
|
|
||||||
|
> view {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid rgba($color: #000000, $alpha: .1);
|
||||||
|
|
||||||
|
image.func-icon {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
margin-left: 15px;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
image.arrow {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
view.function-list view.function > view {
|
||||||
|
border-bottom: 1px solid rgba($color: #ffffff, $alpha: .1);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,31 @@
|
|||||||
import { Modular, Manager } from "../../core/Module";
|
import { Modular, Manager } from "../../core/Module";
|
||||||
|
|
||||||
|
interface IFunctionListItem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示名称
|
||||||
|
*/
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标路径
|
||||||
|
*/
|
||||||
|
iconUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
class FunctionList<M extends Manager> extends Modular<M> {
|
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() {
|
public override onLoad() {
|
||||||
// Do something
|
// Do something
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
//主要功能
|
//主要功能
|
||||||
view.main-function {
|
view.main-function {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 20px;
|
margin-top: 50rpx;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ view.main-function {
|
|||||||
view {
|
view {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
font-size: .8em;
|
font-size: .9em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,4 +36,4 @@ view.main-function {
|
|||||||
view.main-function view.branch-funtion > view {
|
view.main-function view.branch-funtion > view {
|
||||||
border-right: 1px solid rgba($color: #ffffff, $alpha: .1);
|
border-right: 1px solid rgba($color: #ffffff, $alpha: .1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,30 @@
|
|||||||
import { Modular, Manager } from "../../core/Module";
|
import { Modular, Manager } from "../../core/Module";
|
||||||
|
|
||||||
|
interface IMainFunctionItem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示名称
|
||||||
|
*/
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图标路径
|
||||||
|
*/
|
||||||
|
iconUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
class MainFunction<M extends Manager> extends Modular<M> {
|
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() {
|
public override onLoad() {
|
||||||
// Do something
|
// Do something
|
||||||
|
26
miniprogram/pages/Account/TestLayerA.ts
Normal file
26
miniprogram/pages/Account/TestLayerA.ts
Normal 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;
|
@ -27,9 +27,17 @@ view.user-card {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
image {
|
view {
|
||||||
width: 23px;
|
width: 23px;
|
||||||
height: 23px;
|
height: 23px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: -20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +49,7 @@ view.user-card {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 学生信息
|
||||||
view.student {
|
view.student {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -57,8 +66,10 @@ view.user-card {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
view.text-icon {
|
image.text-icon {
|
||||||
margin-left: .3em;
|
margin-left: .25em;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
import { Modular, Manager } from "../../core/Module";
|
import { Modular, Manager } from "../../core/Module";
|
||||||
|
|
||||||
class UserCard<M extends Manager> extends Modular<M> {
|
type IUserCardEvent = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题更换按钮点击事件
|
||||||
|
*/
|
||||||
|
clickChangeTheme: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserCard<M extends Manager> extends Modular<M, {}, IUserCardEvent> {
|
||||||
|
|
||||||
public override onLoad() {
|
public override onLoad() {
|
||||||
// Do something
|
this.setFunc(this.handleChangeTheme, "changeTheme");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理主题更换
|
||||||
|
*/
|
||||||
|
private handleChangeTheme() {
|
||||||
|
this.emit("clickChangeTheme");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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: "qazxsw123"})
|
// 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,17 @@ import { TestCore } from "./TestCore";
|
|||||||
* 此页面使用 Manager 进行模块化管理
|
* 此页面使用 Manager 进行模块化管理
|
||||||
* 若要添加先功能请先定义 Modular 并添加至 Manager
|
* 若要添加先功能请先定义 Modular 并添加至 Manager
|
||||||
*/
|
*/
|
||||||
Manager.Page((manager)=>{
|
(async () => {
|
||||||
|
|
||||||
|
// 初始化页面
|
||||||
|
const { manager, query } = await Manager.PageAsync();
|
||||||
|
|
||||||
|
// 添加 StatusBar Modular
|
||||||
manager.addModule(StatusBar, "statusBar");
|
manager.addModule(StatusBar, "statusBar");
|
||||||
|
|
||||||
|
// 添加 TestCore Modular
|
||||||
manager.addModule(TestCore, "testCore");
|
manager.addModule(TestCore, "testCore");
|
||||||
})
|
|
||||||
|
// 初始化全部 Modular
|
||||||
|
await manager.loadAllModule(query);
|
||||||
|
})()
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user