Merge branch 'dev-mrkbear' of http://git.mrkbear.com/MrKBear/mini-dlpu-v3 into dev-mrkbear

This commit is contained in:
MrKBear 2022-01-17 20:47:32 +08:00
commit 77f9bc5223
13 changed files with 468 additions and 156 deletions

View File

@ -4,7 +4,7 @@
__*!!!警告!!!*__ __*!!!警告!!!*__
请在主仓库提交代码,而非镜像仓库!在镜像仓库提交的代码将会在同步时被覆盖! *请在主仓库提交代码,而非镜像仓库!在镜像仓库提交的代码将会在同步时被覆盖!*
主仓库: http://git.mrkbear.com/MrKBear/mini-dlpu-v3 主仓库: http://git.mrkbear.com/MrKBear/mini-dlpu-v3
@ -15,8 +15,9 @@ __*!!!警告!!!*__
- [社区介绍](#小程序社区) - [社区介绍](#小程序社区)
- [项目设计](#第三代小程序) - [项目设计](#第三代小程序)
- [贡献规范](#社区贡献规范) - [贡献规范](#社区贡献规范)
- [社区福利](#贡献者分配制度)
- [API文档](https://docs.apipost.cn/preview/e737de418d4ef150/419d45d8c97d6a9f) - [API文档](https://docs.apipost.cn/preview/e737de418d4ef150/419d45d8c97d6a9f)
- [入门文档(等待撰写)](#第三代掌上教务处小程序) - [入门文档(等待撰写)](#快速入门)
- [设计架构(等待撰写)](#第三代掌上教务处小程序) - [设计架构(等待撰写)](#第三代掌上教务处小程序)
## 小程序社区 ## 小程序社区
@ -98,6 +99,65 @@ __*!!!警告!!!*__
5. 一个文件不要超过 1000 行代码,尽量保证代码可读性 5. 一个文件不要超过 1000 行代码,尽量保证代码可读性
## 贡献者分配制度
作为公益的开源项目:
第三代开发时,将计划加入一个赞助功能,每个月赞助累计到达一定数额,将在下个月去除开屏广告。
第三代上线后,我们将在每个月公示小程序的账目流水,去除服务器成本和其他费用(微信认证,微信支付,域名,...)后,若有剩余数额将按照开发时大家的贡献分配。
小程序的广告位将计划外包给其他组织管理,我们也会得到一定收入,此收入也将按上面的规则处理。
小程序开屏广告收入也同样按上面的规则处理。
以上内容请大家仔细阅读,另外有意向负责项目财务的同学,处理财务账目也算做贡献。
## 快速入门
下面对大家的小问题进行解答:
> 我在参与贡献之前,我需要先会哪些知识?他们好学吗?
下面列出此项目使用的全部技术,从上到下是推荐学习顺序和学习重点,也是难度顺序:
- HTML
- 标签结构和语法
- CSS
- 基础样式
- 选择器
- 盒模型
- 布局和定位
- 行内元素和块级元素
- JS
- 数据类型
- 基础运算符
- 流程控制语句
- 函数与闭包 (瓶颈)
- 原型和对象 (突破)
- Vue (不用深入了解)
- 组件化设计思想
- 小程序 API
- 了解小程序如何编写页面
- 了解小程序大概的 API
- 不用深入了解,随时查阅
- TypeScript (只要JS数据结构玩的6TS五分钟学会)
- 类型约束
- 接口
- 泛型
- 类型运算
- Sass (拓展了CSS语法实际上没有任何新知识)
- 语法
> 小程序和主流前端技术差别在哪?对我以后职业发展帮助大嘛?
如果你已经掌握了前端主流技术,例如 VueReact那么上手小程序只是 __1__ 天的事情
换句话来说小程序开发用到技术和主流前端技术,有大概 __90%__ 是重叠的。
小程序学了可以成为加分项,参与贡献拥有 __20000__ 人的项目,丰富项目经历,稳赚不亏。
掌上教务处前端项目采用了很多创新的架构设计,虽然不一定优秀,但是一定是值得学习的。
## 贡献者 ## 贡献者
@MrKBear (熊鲜森) @MrKBear (熊鲜森)

149
miniprogram/api/Login.ts Normal file
View File

@ -0,0 +1,149 @@
import { API, HTTPMethod, IParamSetting, GeneralCallbackResult } from "../core/Api";
interface ILoginInput {
/**
*
*/
studentId: string;
/**
*
*/
password: string;
}
interface ILoginOutput {
/**
*
*
*/
idCardLast6: string;
/**
* 使
*/
eduService: string;
/**
*
*/
actualName: string;
/**
*
*/
isSubscribeWxAccount: boolean;
/**
* session
*/
eduSession: string;
}
interface ILoginEvent {
/**
* session
*/
expire: GeneralCallbackResult;
/**
*
*/
unauthorized: GeneralCallbackResult;
/**
*
*/
error: GeneralCallbackResult;
/**
*
*/
badData: GeneralCallbackResult;
}
/**
* Login API
* API
* session
*/
class Login extends API<ILoginInput, ILoginOutput, ILoginEvent> {
public override url: string = "/login";
public override method: HTTPMethod = HTTPMethod.POST;
public override params: IParamSetting<ILoginInput> = {
studentId: {
mapKey: "id",
tester: /^\d{8,12}$/,
},
password: {
mapKey: "pwd"
}
};
public constructor() {
super();
this.initDebugLabel("Login");
this.addFailedCallBack();
this.on("success", (data) => {
let isSuccess = true;
let errMsg = "";
let res: ILoginOutput | undefined;
const info: any = data.data;
// 数据缺失检测
if(!info) {
isSuccess = false;
errMsg = "Bad Data";
this.emit("badData", { errMsg });
}
if (isSuccess) switch (info.code) {
case (1):
res = {
idCardLast6: info.data.idCard,
eduService: info.data.ip,
actualName: info.data.name,
isSubscribeWxAccount: info.data.official,
eduSession: info.data.session
};
errMsg = info.err_msg ?? "Success";
this.emit("ok", res);
break;
case (2):
isSuccess = false;
errMsg = info.err_msg ?? "Session Expire";
this.emit("expire", { errMsg });
break;
case (3):
isSuccess = false;
errMsg = info.err_msg ?? "Unauthorized";
this.emit("unauthorized", { errMsg });
break;
case (4):
isSuccess = false;
errMsg = info.err_msg ?? "Error";
this.emit("error", { errMsg });
break;
}
if (!isSuccess) this.emit("no", { errMsg });
this.emit("done", { errMsg, data: res });
});
}
}
export { Login, ILoginInput, ILoginOutput };

View File

@ -1,4 +1,5 @@
import { Emitter } from "./Emitter"; import { Emitter, EventType } from "./Emitter";
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 {
api: { api: {
@ -32,6 +33,11 @@ type DeepReadonly<T> = {
type IParamSetting<T extends IAnyData> = { type IParamSetting<T extends IAnyData> = {
[P in keyof T]: { [P in keyof T]: {
/**
*
*/
mapKey?: string,
/** /**
* *
*/ */
@ -116,16 +122,57 @@ type IAPIEvent<I extends IAnyData, O extends IAnyData> = {
} }
/** /**
* *
*/ */
class API<I extends IAnyData, O extends IAnyData> extends Emitter<IAPIEvent<I, O>> { type IAPIResultEvent<O extends IAnyData, U extends IAnyData> = {
/**
*
*/
ok: O,
/**
*
*
*/
no: GeneralCallbackResult & U,
/**
*
*
*/
done: { data?: O } & GeneralCallbackResult & U
}
/**
* API
* @template I API
* @template O API
* @template E API
* @template U
*/
class API<
I extends IAnyData = IAnyData,
O extends IAnyData = IAnyData,
E extends Record<EventType, any> = Record<EventType, any>,
U extends IAnyData = IAnyData
> extends Emitter <
{
// 这个复杂的泛型是为了 MixIn 用户自定义事件类型
// 懂得如何使用就可以了
// 不要试图去理解下面这三行代码,真正的恶魔在等着你
[P in (keyof (IAPIEvent<I, O> & IAPIResultEvent<O, U>) | keyof E)] :
P extends keyof IAPIEvent<I, O> ? IAPIEvent<I, O>[P] :
P extends keyof IAPIResultEvent<O, U> ? IAPIResultEvent<O, U>[P] : E[P]
}
> {
/** /**
* URL * URL
* TODO: 这里可能涉及负载均衡 * TODO: 这里可能涉及负载均衡
*/ */
public static get baseUrl():string { public static get baseUrl():string {
return "https://blog.mrkbear.com"; return "https://jwc.nogg.cn";
} }
public static defaultLogLabel:LogLabel = new LogLabel( public static defaultLogLabel:LogLabel = new LogLabel(
@ -301,14 +348,15 @@ class API<I extends IAnyData, O extends IAnyData> extends Emitter<IAPIEvent<I, O
for (let key in this.params) { for (let key in this.params) {
let data = this.data[key]; let data = this.data[key];
let { isHeader, isTemplate } = this.params[key]; let { isHeader, isTemplate, mapKey } = this.params[key];
let useKey = mapKey ?? key;
// 加载数据 // 加载数据
if (!isTemplate) { if (!isTemplate) {
if (isHeader) { if (isHeader) {
requestData.header![key] = data; requestData.header![useKey] = data;
} else { } else {
(requestData.data as IAnyData)[key] = data; (requestData.data as IAnyData)[useKey] = data;
} }
} }
} }
@ -366,7 +414,7 @@ class API<I extends IAnyData, O extends IAnyData> extends Emitter<IAPIEvent<I, O
// 判断 API 是否相似 // 判断 API 是否相似
app.api.pool.forEach((api) => { app.api.pool.forEach((api) => {
if (api === this) return; if ((api as API | this) === this) return;
if (!api.requestData) return; if (!api.requestData) return;
if (api.requestData!.url !== this.requestData!.url) return; if (api.requestData!.url !== this.requestData!.url) return;
if (api.requestData!.method !== this.requestData!.method) return; if (api.requestData!.method !== this.requestData!.method) return;
@ -471,9 +519,9 @@ class API<I extends IAnyData, O extends IAnyData> extends Emitter<IAPIEvent<I, O
/** /**
* *
*/ */
public wait(): Promise<IRespondData<O>>; public waitRequest(): Promise<IRespondData<O>>;
public wait(callBack?: ICallBack<O>): this; public waitRequest(callBack: ICallBack<O>): this;
public wait(callBack?: ICallBack<O>): Promise<IRespondData<O>> | this { public waitRequest(callBack?: ICallBack<O>): Promise<IRespondData<O>> | this {
// 存在 callback 使用传统回调 // 存在 callback 使用传统回调
if (callBack) { if (callBack) {
@ -491,6 +539,96 @@ class API<I extends IAnyData, O extends IAnyData> extends Emitter<IAPIEvent<I, O
}); });
} }
} }
public wait(): Promise<IAPIResultEvent<O, U>["done"]>;
public wait(callBack: IResCallBack<O, U>): this;
public wait(callBack?: IResCallBack<O, U>): Promise<IAPIResultEvent<O, U>["done"]> | this {
// 存在 callback 使用传统回调
if (callBack) {
callBack.ok && this.on("ok", callBack.ok);
callBack.no && this.on("no", callBack.no);
callBack.done && this.on("done", callBack.done);
return this;
}
// 不存在 callback 使用 Promise 对象
else {
return new Promise((r) => {
this.on("done", r);
});
}
}
/**
*
* @param message
* @param mask 使穿
*/
public showLoading(message: string | ((data?: Partial<I>) => string), mask: boolean = false): this {
// 获取标题
let title: string = message instanceof Function ? message(this.data) : message;
this.on("request", () => {
wx.showLoading({
title, mask
})
});
this.on("complete", () => {
wx.hideLoading();
});
return this;
};
/**
*
*/
public showNavigationBarLoading(): this {
this.on("request", () => {
wx.showNavigationBarLoading()
});
this.on("complete", () => {
wx.hideNavigationBarLoading();
});
return this;
}
/**
*
*/
public addFailedCallBack(): this {
this.on("fail", (e) => {
this.emit("no", e as any);
this.emit("done", e as any);
});
return this;
}
/**
*
*/
public showFailed(): this {
// 生成随机索引值
let randomIndex = Math.floor(
Math.random() * API_FAILED_SHOW_MESSAGE.length
);
this.on("fail", () => {
wx.showToast({
title: API_FAILED_SHOW_MESSAGE[randomIndex],
icon: "none"
});
});
return this;
};
} }
/** /**
@ -507,6 +645,12 @@ interface ICallBack<O extends IAnyData> {
complete?: (data: IAPIEvent<{}, O>["complete"]) => any; complete?: (data: IAPIEvent<{}, O>["complete"]) => any;
} }
interface IResCallBack<O extends IAnyData, U extends IAnyData> {
ok?: (data: IAPIResultEvent<O, U>["ok"]) => any;
no?: (data: IAPIResultEvent<O, U>["no"]) => any;
done?: (data: IAPIResultEvent<O, U>["done"]) => any;
}
/** /**
* Request * Request
* *
@ -553,4 +697,4 @@ enum HTTPMethod {
} }
export default API; export default API;
export { API, IParamSetting, IAppAPIParam, ICallBack, HTTPMethod, RequestPolicy } export { API, IParamSetting, IAppAPIParam, ICallBack, HTTPMethod, RequestPolicy, GeneralCallbackResult }

View File

@ -36,3 +36,14 @@ export const LOGGER_FILTER:Array<RegExp | string>[] = [
// 输出警告和错误 // 输出警告和错误
// ["WARN", "ERROR", "FATAL"], // ["WARN", "ERROR", "FATAL"],
]; ];
/**
*
*
*
*/
export const API_FAILED_SHOW_MESSAGE: string[] = [
"失败啦(ó_ò。)", "服务器睡着了", "数据移民火星了",
"数据在半路走丢了", "服务器打了个瞌睡", "服务器被玩坏了",
"服务器在扶老奶奶过马路", "服务器累了", "服务器在拯救世界"
]

View File

@ -1,39 +1,3 @@
@import "../../app.scss"; @import "./UserCard.scss";
@import "./MainFunction.scss";
view.container { @import "./FunctionList.scss";
// 用户卡片
view.user-card {
margin-top: 20px;
height: 100px;
display: flex;
padding-top: 20px;
padding-bottom: 20px;
view.avatar {
width: 80px;
height: 80px;
border-radius: 1000px;
overflow: hidden;
}
view.info {
width: calc(100% - 80px - 20px);
padding-left: 20px;
view.nick {
margin: 4px 0;
}
view.student {
margin: 1px 0;
}
}
}
}
@media (prefers-color-scheme: dark) {
view.container view.user-card view.avatar{
filter: brightness(.8);
}
}

View File

@ -1,66 +1,10 @@
// pages/Account/Account.ts import { Manager } from "../../core/Module";
Page({ import { UserCard } from "./UserCard";
import { MainFunction } from "./MainFunction";
import { FunctionList } from "./FunctionList";
/** Manager.Page((manager) => {
* manager.addModule(UserCard, "userCard");
*/ manager.addModule(MainFunction, "mainFunction");
data: { manager.addModule(FunctionList, "functionList");
});
},
/**
* --
*/
onLoad() {
},
/**
* --
*/
onReady() {
},
/**
* --
*/
onShow() {
},
/**
* --
*/
onHide() {
},
/**
* --
*/
onUnload() {
},
/**
* --
*/
onPullDownRefresh() {
},
/**
*
*/
onReachBottom() {
},
/**
*
*/
onShareAppMessage() {
}
})

View File

@ -0,0 +1 @@
@import "../../app.scss";

View File

@ -0,0 +1,11 @@
import { Modular, Manager } from "../../core/Module";
class FunctionList<M extends Manager> extends Modular<M> {
public override onLoad() {
// Do something
}
}
export { FunctionList };
export default FunctionList;

View File

@ -0,0 +1 @@
@import "../../app.scss";

View File

@ -0,0 +1,11 @@
import { Modular, Manager } from "../../core/Module";
class MainFunction<M extends Manager> extends Modular<M> {
public override onLoad() {
// Do something
}
}
export { MainFunction };
export default MainFunction;

View File

@ -0,0 +1,36 @@
@import "../../app.scss";
// 用户卡片
view.user-card {
margin-top: 20px;
height: 100px;
display: flex;
padding-top: 20px;
padding-bottom: 20px;
view.avatar {
width: 80px;
height: 80px;
border-radius: 1000px;
overflow: hidden;
}
view.info {
width: calc(100% - 80px - 20px);
padding-left: 20px;
view.nick {
margin: 4px 0;
}
view.student {
margin: 1px 0;
}
}
}
@media (prefers-color-scheme: dark) {
view.container view.user-card view.avatar{
filter: brightness(.8);
}
}

View File

@ -0,0 +1,11 @@
import { Modular, Manager } from "../../core/Module";
class UserCard<M extends Manager> extends Modular<M> {
public override onLoad() {
// Do something
}
}
export { UserCard };
export default UserCard;

View File

@ -1,5 +1,5 @@
import { Modular, Manager, ILifetime } from "../../core/Module"; import { Modular, Manager, ILifetime } from "../../core/Module";
import { API, IParamSetting } from "../../core/Api"; import { Login } from "../../api/Login";
import { Storage } from "../../core/Storage"; import { Storage } from "../../core/Storage";
/** /**
@ -28,43 +28,12 @@ implements Partial<ILifetime> {
s.set("be", 12); s.set("be", 12);
}, 1000) }, 1000)
interface ITestApiInput { new Login().param({studentId: "1806240113", password: "qazxsw123"})
name: string, .request().wait({
id: number, ok: (w) => {console.log("ok", w)},
info: { no: (w) => {console.log("no", w)},
data: string done: (w) => {console.log("done", w)}
} });
}
class TestApi extends API<ITestApiInput, {}> {
public override params: IParamSetting<ITestApiInput> = {
name: {
tester: "123",
isHeader: true
},
id: {
parse: (i) => ++i,
},
info: {}
}
public constructor() {
super();
this.initDebugLabel("TestApi");
}
}
let api = new TestApi();
api.param({
name: "123",
id: 456,
info: {
data: "abc"
}
}).request().wait({
success: (d) => console.log(d)
})
} }
} }