Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b4cfe95a5 | ||
|
|
6937b93517 | ||
|
|
e1d4745ecb | ||
|
|
faa7ed0b0e | ||
|
|
77370eb3eb | ||
|
|
82f438056e | ||
|
|
8d08589b4c | ||
|
|
af2d88f651 | ||
|
|
0528c2865d | ||
|
|
df10c22a8d | ||
|
|
773da154ff | ||
|
|
55d52f446f | ||
|
|
da658cb371 | ||
|
|
ce7d6c74f0 | ||
|
|
7fa3a215cf | ||
|
|
cea68f5145 | ||
|
|
18b167266d | ||
|
|
84b927a71b |
@@ -0,0 +1,59 @@
|
|||||||
|
# 第三代掌上教务处小程序
|
||||||
|
|
||||||
|
掌上教务处作为工业大学的社区开源项目,自从2017年开始已有近5年岁月,在无数同学的贡献之下,为大家打造便捷的校园服务。
|
||||||
|
|
||||||
|
## 小程序社区
|
||||||
|
|
||||||
|
掌上教务处小程序诞生于2017年中旬,此时微信小程序刚刚公测。
|
||||||
|
|
||||||
|
2017年,信息学院吴学长因兴趣设计了一款方便课表查询的小工具,在班级范围内使用,就这样掌上教务处诞生了。
|
||||||
|
|
||||||
|
随着大家的好评和认可,小程序使用范围慢慢扩大,从班级到专业到学院,同时,吴学长也面临着越来越大的责任和压力。
|
||||||
|
|
||||||
|
正当小程序维护困难之际,很多热心的同学,为吴学长贡献代码,创意,UI设计图纸,从此"成绩查询"、"考试日程"、"空教室"这样的小功能如雨后春笋,慢慢诞生......
|
||||||
|
|
||||||
|
此时,作为大家努力的结晶,小程序开放了源代码。
|
||||||
|
|
||||||
|
> PS:
|
||||||
|
> 哈哈,你可能会好奇为什么小程序的LOGO看起来很奇怪,其实这个 LOGO 是一个热心的同学在2018年贡献的,为了纪念小程序最初的贡献者们一直沿用至今。
|
||||||
|
|
||||||
|
毕竟服务器是有成本的,随着用户的增多,吴学长自掏腰包租赁的服务器,已不再能维持小程序流畅运行,小程序再次陷入困境。
|
||||||
|
|
||||||
|
这时有其他热心同学,赞助了自己租赁的服务器...
|
||||||
|
越来越多,越来越多,不愿透露名字的热心人给服务器取了有趣的名字。
|
||||||
|
|
||||||
|
就这样,"笔芯"、"妲己"、"猪蹄"、"MOS"...一台台自租赁的服务器支撑起了大家的日常使用。
|
||||||
|
|
||||||
|
> PS:
|
||||||
|
> 如果你愿意考古的话,去看看老版本小程序的切换服务器页面,它们就这样静静的挺拨在那里,为大家默默的服务着...
|
||||||
|
|
||||||
|
后来随着开发者们的加入,小程序有了更多的功能、更好的技术、更高的性能。
|
||||||
|
|
||||||
|
2019年期间,在吴学长、秦学长、梁学长、潘学姐、隋学长以及其他社区贡献者的共同努力下,第二代小程序诞生了,并更名为"掌上教务处"。
|
||||||
|
|
||||||
|
重构后的小程序,犹如脱胎换骨,性能提高很多倍,得到了很多同学的关注,用户数量达到19000人。
|
||||||
|
|
||||||
|
2020年随着小程序的稳定,大家也已经习惯了目前的设计,但是小程序仍有部分设计缺陷,导致可拓展性降低,难以拓展新功能,无法和其他商业产品竞争。
|
||||||
|
|
||||||
|
老一代社区贡献者们开始逐渐毕业,社区迎来前所未有的空窗期。
|
||||||
|
社区团队逐渐消退,仅剩梁学长、秦学长在日常爱心维护小程序代码,供大家日常使用。
|
||||||
|
|
||||||
|
## 第三代小程序
|
||||||
|
|
||||||
|
突破内容:
|
||||||
|
|
||||||
|
1. 小程序在技术上突破更高的性能瓶颈
|
||||||
|
|
||||||
|
2. 更漂亮的UI,和更好的交互体验
|
||||||
|
|
||||||
|
3. 更好的拓展性,加入更多大家喜欢的功能
|
||||||
|
|
||||||
|
## 社区贡献规范
|
||||||
|
|
||||||
|
请先邮件联系 ```mrkbear@mrkbear.com``` 获得 Gitea 平台账号
|
||||||
|
|
||||||
|
在仓库中创建自己的分支,分支命名规范为 ```dev-你的昵称```
|
||||||
|
|
||||||
|
克隆此储库,在本地 ```checkout``` 到自己的分支
|
||||||
|
|
||||||
|
开发完成后 ```push``` 到自己的远程分支,并发起合并请求,等待 review
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
$theme-color-blue: #3EA3D8;
|
||||||
|
$theme-color-red: #FF9393;
|
||||||
|
|
||||||
|
$theme-color-light-layout: #F8F8F8;
|
||||||
|
$theme-color-light-background: #f4f0f1;
|
||||||
|
$theme-color-light-title: rgba(0, 0, 0, .65);
|
||||||
|
$theme-color-light-text: rgba(0, 0, 0, .55);
|
||||||
|
|
||||||
|
$theme-color-dark-layout: #191919;
|
||||||
|
$theme-color-dark-background: #1f1f1f;
|
||||||
|
$theme-color-dark-title: rgba(255, 255, 255, .65);
|
||||||
|
$theme-color-dark-text: rgba(255, 255, 255, .55);
|
||||||
|
|
||||||
|
$black-filter: brightness(0) opacity(.65);
|
||||||
|
$white-filter: brightness(100) opacity(.65);
|
||||||
|
$blue-filter: opacity(.65);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
page {
|
||||||
|
background-color: #f4f0f1;
|
||||||
|
color: $theme-color-light-text;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: .9em;
|
||||||
|
font-family: Hiragino Sans GB, MicroSoft YaHei;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
page {
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
color: $theme-color-dark-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-23
@@ -1,36 +1,30 @@
|
|||||||
import { Logger, LevelLogLabel } from "./logger/index";
|
import { Logger } from "./core/Logger";
|
||||||
|
import { LevelLogLabel, LifeCycleLogLabel } from "./core/PresetLogLabel";
|
||||||
|
|
||||||
|
import { Storage } from "./core/Storage";
|
||||||
|
|
||||||
|
|
||||||
App<IAppOption>({
|
App({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局数据
|
* 存储缓存键值
|
||||||
*/
|
*/
|
||||||
globalData: {},
|
storageCache: new Set<string>(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 小程序加载时
|
* 小程序加载时
|
||||||
*/
|
*/
|
||||||
onLaunch() {
|
onLaunch() {
|
||||||
|
Logger.log("小程序启动...",
|
||||||
|
LevelLogLabel.TraceLabel, LifeCycleLogLabel.OnLaunchLabel);
|
||||||
|
|
||||||
console.log(Logger.l({val:"hh"},
|
let s = new Storage("test", {
|
||||||
LevelLogLabel.FatalLabel,LevelLogLabel.ErrorLabel,LevelLogLabel.WarnLabel,
|
a: new Date(),
|
||||||
LevelLogLabel.InfoLabel,LevelLogLabel.DebugLabel,LevelLogLabel.TraceLabel
|
be: 2
|
||||||
));
|
});
|
||||||
|
|
||||||
console.log(Logger.m(
|
|
||||||
[LevelLogLabel.FatalLabel,LevelLogLabel.ErrorLabel,LevelLogLabel.WarnLabel,
|
|
||||||
LevelLogLabel.InfoLabel,LevelLogLabel.DebugLabel,LevelLogLabel.TraceLabel], {val:"hh"}, "hh"
|
|
||||||
));
|
|
||||||
|
|
||||||
console.log(Logger.ll({val:"hh"},
|
setTimeout(() => {
|
||||||
LevelLogLabel.FatalLabel,LevelLogLabel.ErrorLabel,LevelLogLabel.WarnLabel,
|
s.set("be", 12);
|
||||||
LevelLogLabel.InfoLabel,LevelLogLabel.DebugLabel,LevelLogLabel.TraceLabel
|
}, 1000)
|
||||||
));
|
}
|
||||||
|
|
||||||
console.log(Logger.lm(
|
|
||||||
[LevelLogLabel.FatalLabel,LevelLogLabel.ErrorLabel,LevelLogLabel.WarnLabel,
|
|
||||||
LevelLogLabel.InfoLabel,LevelLogLabel.DebugLabel,LevelLogLabel.TraceLabel], {val:"hh"}, "hh"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
@@ -10,12 +10,19 @@
|
|||||||
*/
|
*/
|
||||||
export const LOGGER_CONSOLE:boolean = true;
|
export const LOGGER_CONSOLE:boolean = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在控制台输出时启用标签样式
|
||||||
|
* 如果在手机上调试可以关闭此选项
|
||||||
|
* 因为手机上的控制台无法正确显示样式
|
||||||
|
*/
|
||||||
|
export const LOGGER_STYLE:boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调试过滤器
|
* 调试过滤器
|
||||||
* 按照 LogLabel 进行过滤
|
* 按照 LogLabel 进行过滤
|
||||||
*
|
*
|
||||||
* 注意:
|
* 注意:
|
||||||
* 1、行与行是之间是 || 关系
|
* 1、行与行之间是 || 关系
|
||||||
* 2、每行的元素之间是 && 关系
|
* 2、每行的元素之间是 && 关系
|
||||||
* 3、支持正则表达式
|
* 3、支持正则表达式
|
||||||
* 4、字符串使用 === 严格匹配
|
* 4、字符串使用 === 严格匹配
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
export type EventType = string | symbol;
|
||||||
|
|
||||||
|
// An event handler can take an optional event argument
|
||||||
|
// and should not return a value
|
||||||
|
export type Handler<T = unknown> = (event: T) => void;
|
||||||
|
export type WildcardHandler<T = Record<string, unknown>> = (
|
||||||
|
type: keyof T,
|
||||||
|
event: T[keyof T]
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
// An array of all currently registered event handlers for a type
|
||||||
|
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
|
||||||
|
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>;
|
||||||
|
|
||||||
|
// A map of event types and their corresponding event handlers.
|
||||||
|
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
|
||||||
|
keyof Events | '*',
|
||||||
|
EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface Emitter<Events extends Record<EventType, unknown>> {
|
||||||
|
all: EventHandlerMap<Events>;
|
||||||
|
|
||||||
|
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
|
||||||
|
on(type: '*', handler: WildcardHandler<Events>): void;
|
||||||
|
|
||||||
|
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void;
|
||||||
|
off(type: '*', handler: WildcardHandler<Events>): void;
|
||||||
|
|
||||||
|
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
|
||||||
|
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mitt: Tiny (~200b) functional event emitter / pubsub.
|
||||||
|
* @name mitt
|
||||||
|
* @returns {Mitt}
|
||||||
|
*/
|
||||||
|
export default function mitt<Events extends Record<EventType, unknown>>(
|
||||||
|
all?: EventHandlerMap<Events>
|
||||||
|
): Emitter<Events> {
|
||||||
|
type GenericEventHandler =
|
||||||
|
| Handler<Events[keyof Events]>
|
||||||
|
| WildcardHandler<Events>;
|
||||||
|
all = all || new Map();
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Map of event names to registered handler functions.
|
||||||
|
*/
|
||||||
|
all,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an event handler for the given type.
|
||||||
|
* @param {string|symbol} type Type of event to listen for, or `'*'` for all events
|
||||||
|
* @param {Function} handler Function to call in response to given event
|
||||||
|
* @memberOf mitt
|
||||||
|
*/
|
||||||
|
on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
|
||||||
|
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
|
||||||
|
if (handlers) {
|
||||||
|
handlers.push(handler);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event handler for the given type.
|
||||||
|
* If `handler` is omitted, all handlers of the given type are removed.
|
||||||
|
* @param {string|symbol} type Type of event to unregister `handler` from, or `'*'`
|
||||||
|
* @param {Function} [handler] Handler function to remove
|
||||||
|
* @memberOf mitt
|
||||||
|
*/
|
||||||
|
off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
|
||||||
|
const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
|
||||||
|
if (handlers) {
|
||||||
|
if (handler) {
|
||||||
|
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
all!.set(type, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke all handlers for the given type.
|
||||||
|
* If present, `'*'` handlers are invoked after type-matched handlers.
|
||||||
|
*
|
||||||
|
* Note: Manually firing '*' handlers is not supported.
|
||||||
|
*
|
||||||
|
* @param {string|symbol} type The event type to invoke
|
||||||
|
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
|
||||||
|
* @memberOf mitt
|
||||||
|
*/
|
||||||
|
emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
|
||||||
|
let handlers = all!.get(type);
|
||||||
|
if (handlers) {
|
||||||
|
(handlers as EventHandlerList<Events[keyof Events]>)
|
||||||
|
.slice()
|
||||||
|
.map((handler) => {
|
||||||
|
handler(evt!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers = all!.get('*');
|
||||||
|
if (handlers) {
|
||||||
|
(handlers as WildCardEventHandlerList<Events>)
|
||||||
|
.slice()
|
||||||
|
.map((handler) => {
|
||||||
|
handler(type, evt!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -134,5 +134,94 @@ class LogStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogStyle;
|
/**
|
||||||
export {LogStyle};
|
* 日志标签
|
||||||
|
*/
|
||||||
|
class LogLabel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关键字
|
||||||
|
* 用于标识这个类别
|
||||||
|
*/
|
||||||
|
public key:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文字样式
|
||||||
|
*/
|
||||||
|
public style:LogStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否受到过滤器影响
|
||||||
|
*/
|
||||||
|
public checked:boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否输出
|
||||||
|
*/
|
||||||
|
public display:boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为附件标签
|
||||||
|
* 例如回车、时间、代码位置
|
||||||
|
*/
|
||||||
|
public attach:boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key 关键字
|
||||||
|
* @param style 文字样式
|
||||||
|
*/
|
||||||
|
constructor(key:string, style:LogStyle,
|
||||||
|
checked?:boolean, display?:boolean, attach?:boolean) {
|
||||||
|
this.key = key;
|
||||||
|
this.style = style;
|
||||||
|
this.checked = checked ?? true;
|
||||||
|
this.display = display ?? true;
|
||||||
|
this.attach = attach ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得 Logger 输出使用的内容
|
||||||
|
*/
|
||||||
|
public getLoggerOutput():string {
|
||||||
|
if(!this.display) return "";
|
||||||
|
return `%c${ this.key }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得 Text 输出内容
|
||||||
|
*/
|
||||||
|
public getTextOutput():string {
|
||||||
|
if(!this.display) return "";
|
||||||
|
return `[${ this.key }]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得 style 格式化
|
||||||
|
*/
|
||||||
|
public getStyleOutput():string {
|
||||||
|
if(!this.display) return "";
|
||||||
|
return this.style.stringify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验
|
||||||
|
*/
|
||||||
|
public checking(src:RegExp | string):boolean {
|
||||||
|
|
||||||
|
let pass = false;
|
||||||
|
|
||||||
|
// 关闭校验
|
||||||
|
if(!this.checked) return pass;
|
||||||
|
|
||||||
|
if(src instanceof RegExp) {
|
||||||
|
pass = (src as RegExp).test(this.key)
|
||||||
|
} else {
|
||||||
|
pass = (src as string) === this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogLabel;
|
||||||
|
export {LogLabel, LogStyle}
|
||||||
@@ -1,7 +1,33 @@
|
|||||||
import {LOGGER_CONSOLE, LOGGER_FILTER} from "../Config";
|
import { LOGGER_FILTER, LOGGER_CONSOLE, LOGGER_STYLE } from "./Config";
|
||||||
import { InternalLogLabel } from "./InternalLogLabel";
|
import { StackLogLabel } from "./PresetLogLabel";
|
||||||
import { LogLabel } from "./LogLabel";
|
import { LogLabel } from "./LogLabel";
|
||||||
import { MultipleLogContent } from "./MultipleLogContent";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多重内容捆绑
|
||||||
|
* 用于 log 输出
|
||||||
|
*/
|
||||||
|
class MultipleLogContent<T extends Array<any>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出内容
|
||||||
|
*/
|
||||||
|
private readonly content:T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param content 输出内容
|
||||||
|
*/
|
||||||
|
public constructor(...content:T) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容
|
||||||
|
*/
|
||||||
|
public getContent():T {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化日志输出
|
* 格式化日志输出
|
||||||
@@ -56,30 +82,41 @@ class Logger {
|
|||||||
*/
|
*/
|
||||||
public static calcStyle(...labels:LogLabel[]):[string[], string[]] {
|
public static calcStyle(...labels:LogLabel[]):[string[], string[]] {
|
||||||
|
|
||||||
// 过滤出需要显示的 Labels
|
|
||||||
let labelsNeedRender:LogLabel[] = labels.filter((label:LogLabel)=>{
|
|
||||||
return label.display
|
|
||||||
});
|
|
||||||
|
|
||||||
let consoleLabels:string[] = [];
|
let consoleLabels:string[] = [];
|
||||||
let consoleStyles:string[] = [];
|
let consoleStyles:string[] = [];
|
||||||
|
|
||||||
// 放置标签
|
// 放置标签
|
||||||
for(let i = 0; i < labelsNeedRender.length; i++) {
|
for(let i = 0; i < labels.length; i++) {
|
||||||
consoleLabels.push(labels[i].getLoggerOutput());
|
consoleLabels.push(labels[i].getLoggerOutput());
|
||||||
|
|
||||||
if (i !== ( labelsNeedRender.length - 1))
|
if (i !== ( labels.length - 1))
|
||||||
consoleLabels.push("%c ");
|
consoleLabels.push("%c ");
|
||||||
|
|
||||||
consoleStyles.push(labelsNeedRender[i].getStyleOutput());
|
consoleStyles.push(labels[i].getStyleOutput());
|
||||||
|
|
||||||
if (i !== ( labelsNeedRender.length - 1))
|
if (i !== ( labels.length - 1))
|
||||||
consoleStyles.push("");
|
consoleStyles.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
return [consoleLabels, consoleStyles];
|
return [consoleLabels, consoleStyles];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集计算标签没有样式
|
||||||
|
* @param labels 标签
|
||||||
|
*/
|
||||||
|
public static calcLabel(...labels:LogLabel[]):string[] {
|
||||||
|
|
||||||
|
let consoleLabels:string[] = [];
|
||||||
|
|
||||||
|
// 放置标签
|
||||||
|
for(let i = 0; i < labels.length; i++) {
|
||||||
|
consoleLabels.push(labels[i].getTextOutput());
|
||||||
|
}
|
||||||
|
|
||||||
|
return consoleLabels
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础调试输出
|
* 基础调试输出
|
||||||
* 其他的 Log 函数都是基于此封装
|
* 其他的 Log 函数都是基于此封装
|
||||||
@@ -93,14 +130,33 @@ class Logger {
|
|||||||
// TODO: 这里可以添加一些钩子作为中间件处理日志输出
|
// TODO: 这里可以添加一些钩子作为中间件处理日志输出
|
||||||
|
|
||||||
// 测试是否输出内容
|
// 测试是否输出内容
|
||||||
if(!Logger.testLog(...labels, ...attachLabel, InternalLogLabel.filterUrlLabel))
|
if(!Logger.testLog(...labels, ...attachLabel, StackLogLabel.filterUrlLabel))
|
||||||
return content.getContent();
|
return content.getContent();
|
||||||
|
|
||||||
// 计算收集样式
|
// 过滤出需要渲染的 Labels
|
||||||
let [consoleLabels, consoleStyles]= Logger.calcStyle(...labels, ...attachLabel);
|
let labelsNeedRender:LogLabel[] = [...labels, ...attachLabel].filter((label:LogLabel)=>{
|
||||||
|
return true
|
||||||
|
});
|
||||||
|
|
||||||
// 调试输出
|
// 使用样式输出
|
||||||
console.log(consoleLabels.join(""), ...consoleStyles, ...content.getContent());
|
if(LOGGER_STYLE) {
|
||||||
|
|
||||||
|
// 计算收集样式
|
||||||
|
let [consoleLabels, consoleStyles]= Logger.calcStyle(...labelsNeedRender);
|
||||||
|
|
||||||
|
// 调试输出
|
||||||
|
console.log(consoleLabels.join(""), ...consoleStyles, ...content.getContent());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 计算收集标签
|
||||||
|
let consoleLabels= Logger.calcLabel(...labelsNeedRender);
|
||||||
|
|
||||||
|
// 输出
|
||||||
|
console.log(consoleLabels.join(" "), ...content.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return content.getContent();
|
return content.getContent();
|
||||||
}
|
}
|
||||||
@@ -113,7 +169,7 @@ class Logger {
|
|||||||
public static log<T>(content:T, ...labels:LogLabel[]):T {
|
public static log<T>(content:T, ...labels:LogLabel[]):T {
|
||||||
return Logger.logBase<Array<T>>(
|
return Logger.logBase<Array<T>>(
|
||||||
new MultipleLogContent<Array<T>>(content), labels,
|
new MultipleLogContent<Array<T>>(content), labels,
|
||||||
[InternalLogLabel.fileNameLabel]
|
[StackLogLabel.fileNameLabel]
|
||||||
)[0];
|
)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +186,7 @@ class Logger {
|
|||||||
public static logMultiple<T extends Array<any>>(labels:LogLabel[], ...content:T):T {
|
public static logMultiple<T extends Array<any>>(labels:LogLabel[], ...content:T):T {
|
||||||
return Logger.logBase<T>(
|
return Logger.logBase<T>(
|
||||||
new MultipleLogContent<T>(...content), labels,
|
new MultipleLogContent<T>(...content), labels,
|
||||||
[InternalLogLabel.fileNameLabel]
|
[StackLogLabel.fileNameLabel]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,12 +203,12 @@ class Logger {
|
|||||||
public static logLine<T>(content:T, ...labels:LogLabel[]):T {
|
public static logLine<T>(content:T, ...labels:LogLabel[]):T {
|
||||||
return Logger.logBase<Array<T>>(
|
return Logger.logBase<Array<T>>(
|
||||||
new MultipleLogContent<Array<T>>(content), labels,
|
new MultipleLogContent<Array<T>>(content), labels,
|
||||||
[InternalLogLabel.urlLabel, InternalLogLabel.blankLabel]
|
[StackLogLabel.fileNameLabel, StackLogLabel.blankLabel]
|
||||||
)[0];
|
)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 函数 Logger.logMultiple 的别名
|
* 函数 Logger.logLine 的别名
|
||||||
*/
|
*/
|
||||||
public static ll:typeof Logger.logLine = Logger.logLine;
|
public static ll:typeof Logger.logLine = Logger.logLine;
|
||||||
|
|
||||||
@@ -164,7 +220,7 @@ class Logger {
|
|||||||
public static logLineMultiple<T extends Array<any>>(labels:LogLabel[], ...content:T):T {
|
public static logLineMultiple<T extends Array<any>>(labels:LogLabel[], ...content:T):T {
|
||||||
return Logger.logBase<T>(
|
return Logger.logBase<T>(
|
||||||
new MultipleLogContent<T>(...content), labels,
|
new MultipleLogContent<T>(...content), labels,
|
||||||
[InternalLogLabel.urlLabel, InternalLogLabel.blankLabel]
|
[StackLogLabel.fileNameLabel, StackLogLabel.blankLabel]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
import mitt, { Emitter, EventHandlerMap, EventType, Handler, WildcardHandler } from "./EventEmitter";
|
||||||
|
import { LogLabel, LogStyle } from "./LogLabel";
|
||||||
|
import { Logger } from "./Logger";
|
||||||
|
import { LevelLogLabel } from "./PresetLogLabel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义对象类型
|
||||||
|
*/
|
||||||
|
type IAnyTypeObject<T = any> = {
|
||||||
|
[x:string]: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 微信 Data 类型
|
||||||
|
type Data<D> = WechatMiniprogram.Page.Data<D>;
|
||||||
|
|
||||||
|
// 微信生命周期类型
|
||||||
|
type ILifetime = WechatMiniprogram.Page.ILifetime;
|
||||||
|
|
||||||
|
// 继承的方法
|
||||||
|
type InstanceMethods<D> = WechatMiniprogram.Page.InstanceMethods<D>;
|
||||||
|
|
||||||
|
// 继承的属性
|
||||||
|
type InstanceProperties = WechatMiniprogram.Page.InstanceProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义微信微信上下文类型
|
||||||
|
*
|
||||||
|
* @template TD data 结构类型
|
||||||
|
* @template TC 用户 MinIn 类型
|
||||||
|
*
|
||||||
|
* Partial<ILifetime> 可选的微信生命周期
|
||||||
|
* Partial<Data<TD>> 可选的 data 结构
|
||||||
|
* InstanceMethods<TD> 必选的微信继承方法
|
||||||
|
* InstanceProperties 必选的微信继承属性
|
||||||
|
*/
|
||||||
|
type WXContext<TD, TC> = Partial<ILifetime> & Partial<Data<TD>>
|
||||||
|
& InstanceMethods<TD> & InstanceProperties & TC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任意的微信上下文
|
||||||
|
* 当我们不关心具体是哪个上下文时
|
||||||
|
* 使用这个类型
|
||||||
|
*/
|
||||||
|
type AnyWXContext = WXContext<IAnyTypeObject, IAnyTypeObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 依赖关系图谱
|
||||||
|
* 注意: 这是一个递给类型
|
||||||
|
* @template M 限制了所有依赖必须使用同一个 manager
|
||||||
|
*/
|
||||||
|
type Depends<M extends Manager<AnyWXContext>> = {
|
||||||
|
[x:string]: Modular<M, Depends<M>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面模组
|
||||||
|
* @template M 所属 Manager
|
||||||
|
* @template DEP 模组依赖
|
||||||
|
* @template E 模组事件
|
||||||
|
* @template TD 模组 Data 类型
|
||||||
|
*/
|
||||||
|
class Modular<
|
||||||
|
M extends Manager<AnyWXContext> = Manager<AnyWXContext>,
|
||||||
|
DEP extends Depends<M> = Depends<M>,
|
||||||
|
E extends Record<EventType, unknown> = Record<EventType, unknown>,
|
||||||
|
TD extends IAnyTypeObject = IAnyTypeObject>
|
||||||
|
implements WXContext<TD, IAnyTypeObject>, Emitter<E> {
|
||||||
|
|
||||||
|
// [x:string]: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存页面上下文
|
||||||
|
*/
|
||||||
|
private manager:M;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射 Manager 上下文
|
||||||
|
*/
|
||||||
|
private get context():M["context"] {
|
||||||
|
return this.manager.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模组依赖
|
||||||
|
*/
|
||||||
|
protected depends?:DEP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模组数据
|
||||||
|
*/
|
||||||
|
public data?:TD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模组使用的函数列表
|
||||||
|
*/
|
||||||
|
public functionList:Set<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数使用的参数列表
|
||||||
|
*/
|
||||||
|
public paramList:Set<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命名空间
|
||||||
|
*/
|
||||||
|
public nameSpace:string;
|
||||||
|
|
||||||
|
// 映射主上下文属性
|
||||||
|
public get is():string { return this.context.is };
|
||||||
|
public get route():string { return this.context.route };
|
||||||
|
public get options():Record<string, string | undefined> {
|
||||||
|
return this.context.options;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一旦被类被构造,整个页面的全部申明周期将
|
||||||
|
* 由该类实例控制,此后请勿修改任何生命周期函数
|
||||||
|
* @param manager 页面上下文
|
||||||
|
* @param nameSpace 模组命名空间
|
||||||
|
* @param depend 模组依赖
|
||||||
|
*/
|
||||||
|
public constructor(manager:M, nameSpace:string, depend?: DEP) {
|
||||||
|
|
||||||
|
// 保存微信上下文
|
||||||
|
this.manager = manager;
|
||||||
|
|
||||||
|
// 保存模块依赖
|
||||||
|
this.depends = depend;
|
||||||
|
|
||||||
|
// 初始化内部属性
|
||||||
|
this.functionList = new Set<string>();
|
||||||
|
this.paramList = new Set<string>();
|
||||||
|
this.nameSpace = nameSpace;
|
||||||
|
|
||||||
|
this.emitter = mitt<E>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部事件控制器
|
||||||
|
*/
|
||||||
|
private emitter:Emitter<E>;
|
||||||
|
|
||||||
|
public get all():EventHandlerMap<E> { return this.emitter.all };
|
||||||
|
|
||||||
|
on<Key extends keyof E>(type: Key, handler: Handler<E[Key]>): void;
|
||||||
|
on(type: "*", handler: WildcardHandler<E>): void;
|
||||||
|
on(type: any, handler: any): void {
|
||||||
|
return this.emitter.on(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
off<Key extends keyof E>(type: Key, handler?: Handler<E[Key]>): void;
|
||||||
|
off(type: "*", handler: WildcardHandler<E>): void;
|
||||||
|
off(type: any, handler?: any): void {
|
||||||
|
return this.emitter.off(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit<Key extends keyof E>(type: Key, event: E[Key]): void;
|
||||||
|
emit<Key extends keyof E>(type: undefined extends E[Key] ? Key : never): void;
|
||||||
|
emit(type: any, event?: any): void {
|
||||||
|
return this.emitter.emit(type, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setData(data:Partial<TD>, callback?: () => void):void {
|
||||||
|
|
||||||
|
if(this.data === void 0) {
|
||||||
|
this.data = {} as TD;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reportData:IAnyTypeObject = {};
|
||||||
|
|
||||||
|
for(let key in data) {
|
||||||
|
(this.data as IAnyTypeObject)[key] = data[key];
|
||||||
|
reportData[`${ this.nameSpace }$${ key }`] = data[key];
|
||||||
|
this.paramList.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.context.setData(reportData, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向上层上下文传递函数
|
||||||
|
* @param fn 传递的函数
|
||||||
|
* @param name 函数名字
|
||||||
|
*/
|
||||||
|
public setFunc(fn:Function, name:string):void {
|
||||||
|
|
||||||
|
this.functionList.add(name);
|
||||||
|
(this.context as IAnyTypeObject)
|
||||||
|
[`${ this.nameSpace }$${ name }`] = fn.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 映射微信的继承函数
|
||||||
|
|
||||||
|
public hasBehavior(behavior: string): void {
|
||||||
|
return this.context.hasBehavior(behavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerEvent<DetailType>(name: string, detail?: DetailType,
|
||||||
|
options?: WechatMiniprogram.Component.TriggerEventOption): void {
|
||||||
|
return this.context.triggerEvent<DetailType>(name, detail, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createSelectorQuery(): WechatMiniprogram.SelectorQuery {
|
||||||
|
return this.context.createSelectorQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
public createIntersectionObserver(options: WechatMiniprogram.CreateIntersectionObserverOption):
|
||||||
|
WechatMiniprogram.IntersectionObserver {
|
||||||
|
return this.context.createIntersectionObserver(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectComponent(selector: string): WechatMiniprogram.Component.TrivialInstance {
|
||||||
|
return this.context.selectComponent(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectAllComponents(selector: string): WechatMiniprogram.Component.TrivialInstance[] {
|
||||||
|
return this.context.selectAllComponents(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectOwnerComponent(): WechatMiniprogram.Component.TrivialInstance {
|
||||||
|
return this.context.selectOwnerComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRelationNodes(relationKey: string): WechatMiniprogram.Component.TrivialInstance[] {
|
||||||
|
return this.context.getRelationNodes(relationKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public groupSetData(callback?: () => void): void {
|
||||||
|
return this.context.groupSetData(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTabBar(): WechatMiniprogram.Component.TrivialInstance {
|
||||||
|
return this.context.getTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPageId(): string {
|
||||||
|
return this.context.getPageId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public animate(selector: string, keyFrames: WechatMiniprogram.Component.KeyFrame[],
|
||||||
|
duration: number, callback?: () => void): void;
|
||||||
|
public animate(selector: string, keyFrames: WechatMiniprogram.Component.ScrollTimelineKeyframe[],
|
||||||
|
duration: number, scrollTimeline: WechatMiniprogram.Component.ScrollTimelineOption): void;
|
||||||
|
public animate(selector: any, keyFrames: any, duration: any, scrollTimeline?: any): void {
|
||||||
|
return this.context.animate(selector, keyFrames, duration, scrollTimeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearAnimation(selector: string, callback: () => void): void;
|
||||||
|
public clearAnimation(selector: string, options?: WechatMiniprogram.Component.ClearAnimationOptions,
|
||||||
|
callback?: () => void): void;
|
||||||
|
public clearAnimation(selector: any, options?: any, callback?: any): void {
|
||||||
|
return this.context.clearAnimation(selector, options, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOpenerEventChannel(): WechatMiniprogram.EventChannel {
|
||||||
|
return this.context.getOpenerEventChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面控件
|
||||||
|
* 用于管理页面的所有模组
|
||||||
|
*/
|
||||||
|
class Manager<WXC extends AnyWXContext = AnyWXContext> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信生命周期
|
||||||
|
*/
|
||||||
|
static readonly WxLifeCycle:(keyof ILifetime)[] = [
|
||||||
|
"onShow",
|
||||||
|
"onReady",
|
||||||
|
"onHide",
|
||||||
|
"onUnload",
|
||||||
|
"onPullDownRefresh",
|
||||||
|
"onReachBottom",
|
||||||
|
"onShareAppMessage",
|
||||||
|
"onShareTimeline",
|
||||||
|
"onAddToFavorites",
|
||||||
|
"onPageScroll",
|
||||||
|
"onResize",
|
||||||
|
"onTabItemTap"
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存页面上下文
|
||||||
|
*/
|
||||||
|
public context:WXC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一旦被类被构造,整个页面的全部申明周期将
|
||||||
|
* 由该类实例控制,此后请勿修改任何生命周期函数
|
||||||
|
*/
|
||||||
|
public constructor(context:WXC) {
|
||||||
|
|
||||||
|
// 保存微信上下文
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
// 初始化模组列表
|
||||||
|
this.modules = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模组列表
|
||||||
|
*/
|
||||||
|
public modules:Modular[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个模块
|
||||||
|
* @param mode 模块类
|
||||||
|
* @param nameSpace 命名空间
|
||||||
|
* @param depend 模块依赖
|
||||||
|
* @returns 模块实例
|
||||||
|
*/
|
||||||
|
public addModule<DEP extends Depends<this>, M extends Modular<this, DEP>>
|
||||||
|
(mode: new (manager:Manager<WXC>, nameSpace:string, depend?:DEP) => M,
|
||||||
|
nameSpace:string, depend?:DEP):M {
|
||||||
|
let mod = new mode(this, nameSpace, depend);
|
||||||
|
this.modules.push(mod);
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定生命周期的钩子
|
||||||
|
* @param key 生命周期键值
|
||||||
|
*/
|
||||||
|
public creatHooks(key:keyof ILifetime):(...arg: any[]) => Promise<any> {
|
||||||
|
return async (...arg: any[]) => {
|
||||||
|
|
||||||
|
let hooks:Promise<any>[] = [];
|
||||||
|
|
||||||
|
for(let i = 0; i < this.modules.length; i++) {
|
||||||
|
|
||||||
|
let fn:Function = (this.modules[i] as IAnyTypeObject)[key];
|
||||||
|
|
||||||
|
if(fn === void 0) continue;
|
||||||
|
let res: Promise<any> | any = fn.apply(this.modules[i], arg);
|
||||||
|
|
||||||
|
if (res instanceof Promise) {
|
||||||
|
hooks.push(res);
|
||||||
|
} else {
|
||||||
|
hooks.push(Promise.resolve(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
key === "onShareAppMessage" ||
|
||||||
|
key === "onShareTimeline" ||
|
||||||
|
key === "onAddToFavorites"
|
||||||
|
) {
|
||||||
|
|
||||||
|
// 如果返回值有特殊含义在处理时进行 MinIn
|
||||||
|
return Promise.all(hooks).then((res) => {
|
||||||
|
|
||||||
|
let simple:IAnyTypeObject = {};
|
||||||
|
|
||||||
|
for(let i = 0; i < res.length; i++) {
|
||||||
|
simple = Object.assign({}, simple, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(simple);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(hooks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建全部生命周期钩子
|
||||||
|
*/
|
||||||
|
public creatAllHooks() {
|
||||||
|
for(let i = 0; i < Manager.WxLifeCycle.length; i++) {
|
||||||
|
(this.context as IAnyTypeObject)[Manager.WxLifeCycle[i]] =
|
||||||
|
this.creatHooks(Manager.WxLifeCycle[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载全部的模块
|
||||||
|
* @param query onLoad 接收的参数
|
||||||
|
*/
|
||||||
|
public loadAllModule(query:Record<string, string | undefined>) {
|
||||||
|
|
||||||
|
// 创建全部钩子
|
||||||
|
this.creatAllHooks();
|
||||||
|
|
||||||
|
// 加载全部模组数据
|
||||||
|
for(let i = 0; i < this.modules.length; i++) {
|
||||||
|
|
||||||
|
if(this.modules[i].data)
|
||||||
|
for(let key in this.modules[i].data) {
|
||||||
|
|
||||||
|
if(this.context.data === void 0) this.context.data = {};
|
||||||
|
|
||||||
|
if(this.modules[i].data !== void 0) {
|
||||||
|
this.context.data[`${ this.modules[i].nameSpace }$${ key }`] =
|
||||||
|
( this.modules[i].data as IAnyTypeObject )[key];
|
||||||
|
this.modules[i].paramList.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将全部数据发布到视图层
|
||||||
|
if(this.context.data !== void 0)
|
||||||
|
this.context.setData(this.context.data);
|
||||||
|
|
||||||
|
// 调用全部模块的 onLoad 周期
|
||||||
|
let res = this.creatHooks("onLoad")(query as any);
|
||||||
|
|
||||||
|
// 打印每个模块的键值对使用情况
|
||||||
|
for(let i = 0; i < this.modules.length; i++) {
|
||||||
|
let data:string[] = [];
|
||||||
|
let func:string[] = [];
|
||||||
|
|
||||||
|
for(let key of this.modules[i].paramList) {
|
||||||
|
data.push(`[${ key }]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let key of this.modules[i].functionList) {
|
||||||
|
func.push(`[${ key }]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let log:string = `模块 [${ this.modules[i].nameSpace }] 完成绑定...\n`;
|
||||||
|
if(data.length > 0) log += `Using Props: ${ data.join(", ") }\n`;
|
||||||
|
if(func.length > 0) log += `Using Function: ${ func.join(", ") }\n`;
|
||||||
|
|
||||||
|
Logger.log(log, LevelLogLabel.TraceLabel, Manager.AddModuleLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块被添加时的标签
|
||||||
|
*/
|
||||||
|
public static readonly AddModuleLabel = new LogLabel("addModule",
|
||||||
|
new LogStyle().setBorder("4px", `1px solid #8600FF`)
|
||||||
|
.setColor("#FF00FF", "rgba(54, 0, 255, .2)").setBlank("0 5px")
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载 Manager 控件
|
||||||
|
* @param fn 约束后调用的函数用来添加模块
|
||||||
|
*/
|
||||||
|
public static Page(fn:(manager:Manager<AnyWXContext>) => void) {
|
||||||
|
Page({
|
||||||
|
async onLoad(query) {
|
||||||
|
let manager = new Manager(this);
|
||||||
|
fn(manager);
|
||||||
|
manager.loadAllModule(query);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Manager, Modular, AnyWXContext, WXContext, ILifetime}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
import { LogLabel, LogStyle } from "./LogLabel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 栈信息
|
||||||
|
*/
|
||||||
|
class StackInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数名
|
||||||
|
*/
|
||||||
|
public functionName:string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
*/
|
||||||
|
public fileName:string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件路径
|
||||||
|
*/
|
||||||
|
public url:string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名和行号
|
||||||
|
*/
|
||||||
|
public fileNameLine: string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置信息
|
||||||
|
* @param functionName 函数名
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @param url 文件路径
|
||||||
|
*/
|
||||||
|
public setInfo(functionName:string, fileNameLine:string, url:string):StackInfo {
|
||||||
|
this.functionName = functionName;
|
||||||
|
this.fileNameLine = fileNameLine;
|
||||||
|
this.url = url;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算文件名
|
||||||
|
*/
|
||||||
|
public calcFileName():string | undefined {
|
||||||
|
|
||||||
|
let replaceToTs = this.fileNameLine?.replace(".js", ".ts");
|
||||||
|
let matched = replaceToTs?.match(/^(.+\.(js|ts)):\d+:\d+$/);
|
||||||
|
|
||||||
|
return matched ? matched[1] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算路径名
|
||||||
|
*/
|
||||||
|
public calcPathName():string | undefined {
|
||||||
|
|
||||||
|
let replaceToTs = this.url?.replace(".js", ".ts");
|
||||||
|
let matched = replaceToTs?.match(/^https?:\/\/(\d+\.){3}\d+:\d+\/(.+):\d+:\d+$/);
|
||||||
|
|
||||||
|
return matched ? matched[2] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取函数调用栈列表
|
||||||
|
*/
|
||||||
|
public static getCallStack():StackInfo[] {
|
||||||
|
|
||||||
|
// 获取堆栈信息
|
||||||
|
let stack:string | undefined = new Error().stack;
|
||||||
|
|
||||||
|
if (stack === void 0) return [];
|
||||||
|
|
||||||
|
// 去除 Error
|
||||||
|
stack = stack.replace(/^(Error)\s/, "");
|
||||||
|
|
||||||
|
// 获取堆栈信息
|
||||||
|
let stackList:string[] = stack.split(/\n/);
|
||||||
|
|
||||||
|
let callStack:StackInfo[] = [];
|
||||||
|
|
||||||
|
for(let i = 0; i < stackList.length; i++) {
|
||||||
|
|
||||||
|
let matcher = stackList[i].match(/^\s+at\s+(.+)\s(\(.+\))/);
|
||||||
|
if (matcher === null || matcher.length < 3) continue;
|
||||||
|
|
||||||
|
let fileName = matcher[2].match(/.+\/(.+\..+:\d+:\d+)\)/);
|
||||||
|
if (fileName === null || matcher.length < 2) continue;
|
||||||
|
|
||||||
|
callStack.push(new StackInfo().setInfo(
|
||||||
|
matcher[1], fileName[1], matcher[2]?.replace(/(\(|\))/g, "")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(callStack);
|
||||||
|
|
||||||
|
return callStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除的
|
||||||
|
*/
|
||||||
|
public static readonly excludeFile:RegExp = /^.*(\\|\/)core(\\|\/)(.*Log.*).js:\d+:\d+/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第一个调用栈
|
||||||
|
*/
|
||||||
|
public static getFirstStack():StackInfo | undefined {
|
||||||
|
|
||||||
|
let callStack = this.getCallStack();
|
||||||
|
|
||||||
|
for(let i = 0; i < callStack.length; i++) {
|
||||||
|
|
||||||
|
if(!callStack[i].url) continue;
|
||||||
|
|
||||||
|
if(!StackInfo.excludeFile.test(callStack[i].url ?? "")) {
|
||||||
|
return callStack[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部预定义的 LogLabel
|
||||||
|
*/
|
||||||
|
class StackLogLabel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 堆栈路径样式
|
||||||
|
*/
|
||||||
|
public static readonly normalStyle:LogStyle = new LogStyle()
|
||||||
|
.setColor("#979797").setBorder("4px", "1px solid #979797").setBlank("0 5px");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个回车
|
||||||
|
*/
|
||||||
|
public static readonly blankLabel = new LogLabel("\n\r",
|
||||||
|
new LogStyle(), false, true, true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包含文件名和行号的 label
|
||||||
|
*/
|
||||||
|
public static get fileNameLabel():LogLabel {
|
||||||
|
|
||||||
|
// 获得调用堆栈
|
||||||
|
let stack = StackInfo.getFirstStack();
|
||||||
|
|
||||||
|
return new LogLabel(stack?.calcFileName() ?? "Unknown file name",
|
||||||
|
StackLogLabel.normalStyle, false, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包含 URL 链接的 label
|
||||||
|
*/
|
||||||
|
public static get urlLabel():LogLabel {
|
||||||
|
|
||||||
|
// 获得调用堆栈
|
||||||
|
let stack = StackInfo.getFirstStack();
|
||||||
|
|
||||||
|
return new LogLabel(stack?.calcPathName() ?? "Unknown url",
|
||||||
|
StackLogLabel.normalStyle, false, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅仅用来 filter 的 URL 链接的 label
|
||||||
|
*/
|
||||||
|
public static get filterUrlLabel():LogLabel {
|
||||||
|
|
||||||
|
// 获得调用堆栈
|
||||||
|
let stack = StackInfo.getFirstStack();
|
||||||
|
|
||||||
|
return new LogLabel(stack?.calcPathName() ?? "Unknown url",
|
||||||
|
StackLogLabel.normalStyle, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成圆角颜色标签样式
|
||||||
|
*/
|
||||||
|
const normalLevelStyleGen = (color:string):LogStyle => {
|
||||||
|
return new LogStyle().setBorder("4px", `1px solid ${color}`)
|
||||||
|
.setColor(color).setBlank("0 5px");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试等级标签
|
||||||
|
*/
|
||||||
|
class LevelLogLabel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 致命
|
||||||
|
*/
|
||||||
|
static readonly FatalLabel = new LogLabel(
|
||||||
|
"FATAL", normalLevelStyleGen("#FF00CC")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误
|
||||||
|
*/
|
||||||
|
static readonly ErrorLabel = new LogLabel(
|
||||||
|
"ERROR", normalLevelStyleGen("#FF0000")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 警告
|
||||||
|
*/
|
||||||
|
static readonly WarnLabel = new LogLabel(
|
||||||
|
"WARN", normalLevelStyleGen("#FF9900")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息
|
||||||
|
*/
|
||||||
|
static readonly InfoLabel = new LogLabel(
|
||||||
|
"INFO", normalLevelStyleGen("#99FF00")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试
|
||||||
|
*/
|
||||||
|
static readonly DebugLabel = new LogLabel(
|
||||||
|
"DEBUG", normalLevelStyleGen("#00FF99")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪
|
||||||
|
*/
|
||||||
|
static readonly TraceLabel = new LogLabel(
|
||||||
|
"TRACE", normalLevelStyleGen("#00CCFF")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成圆角颜色标签样式
|
||||||
|
*/
|
||||||
|
const normalLifeStyleGen = (r:number, g:number, b:number):LogStyle => {
|
||||||
|
return new LogStyle().setBorder("4px", `1px solid rgb(${ r }, ${ g }, ${ b })`)
|
||||||
|
.setColor(`rgb(${ r }, ${ g }, ${ b })`, `rgba(${ r }, ${ g }, ${ b }, .1)`)
|
||||||
|
.setBlank("0 5px");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期标签
|
||||||
|
*/
|
||||||
|
class LifeCycleLogLabel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序加载时
|
||||||
|
*/
|
||||||
|
static readonly OnLaunchLabel = new LogLabel(
|
||||||
|
"onLaunch", normalLifeStyleGen(160, 32, 240)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期函数--监听页面加载
|
||||||
|
*/
|
||||||
|
static readonly OnLoadLabel = new LogLabel(
|
||||||
|
"onLoad", normalLifeStyleGen(255, 140, 105)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期函数--监听页面初次渲染完成
|
||||||
|
*/
|
||||||
|
static readonly OnReadyLabel = new LogLabel(
|
||||||
|
"onReady", normalLifeStyleGen(255, 127, 36)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期函数--监听页面显示
|
||||||
|
*/
|
||||||
|
static readonly OnShowLabel = new LogLabel(
|
||||||
|
"onShow", normalLifeStyleGen(255, 215, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期函数--监听页面隐藏
|
||||||
|
*/
|
||||||
|
static readonly OnHideLabel = new LogLabel(
|
||||||
|
"onHide", normalLifeStyleGen(173, 255, 47)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命周期函数--监听页面卸载
|
||||||
|
*/
|
||||||
|
static readonly OnUnloadLabel = new LogLabel(
|
||||||
|
"onUnload", normalLifeStyleGen(127, 255, 212)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面相关事件处理函数--监听用户下拉动作
|
||||||
|
*/
|
||||||
|
static readonly OnPullDownRefreshLabel = new LogLabel(
|
||||||
|
"onPullDownRefresh", normalLifeStyleGen(0, 191, 255)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面上拉触底事件的处理函数
|
||||||
|
*/
|
||||||
|
static readonly OnReachBottomLabel = new LogLabel(
|
||||||
|
"onReachBottom", normalLifeStyleGen(84, 255, 159)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户点击右上角分享
|
||||||
|
*/
|
||||||
|
static readonly OnShareAppMessageLabel = new LogLabel(
|
||||||
|
"onShareAppMessage", normalLifeStyleGen(147, 112, 219)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NormalStyle = StackLogLabel.normalStyle;
|
||||||
|
|
||||||
|
export {
|
||||||
|
NormalStyle,
|
||||||
|
StackInfo,
|
||||||
|
StackLogLabel,
|
||||||
|
LevelLogLabel,
|
||||||
|
LifeCycleLogLabel,
|
||||||
|
normalLifeStyleGen as colorRadio,
|
||||||
|
};
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { LogLabel } from "./LogLabel";
|
||||||
|
import { Logger } from "./Logger";
|
||||||
|
import { LevelLogLabel, colorRadio } from "./PresetLogLabel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
enum StorageState {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有 waiter 存在的工作状态
|
||||||
|
*/
|
||||||
|
WORK = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无 waiter 存在的完成状态
|
||||||
|
*/
|
||||||
|
DONE = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务等待列表
|
||||||
|
*/
|
||||||
|
class Waiter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
public get state():StorageState {
|
||||||
|
if(this.waiterList.length <= 0) {
|
||||||
|
return StorageState.DONE;
|
||||||
|
} else {
|
||||||
|
return StorageState.WORK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数列表
|
||||||
|
*/
|
||||||
|
private waiterList:Function[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加等待者
|
||||||
|
* @param fn 等待者
|
||||||
|
*/
|
||||||
|
public addWaiter(fn:Function) {
|
||||||
|
this.waiterList.push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成
|
||||||
|
* 清空 waiterList
|
||||||
|
* 并返回合并的 waiterList
|
||||||
|
*/
|
||||||
|
public done():Function {
|
||||||
|
|
||||||
|
// 复制 waiterList 生成闭包
|
||||||
|
let fnList = this.waiterList.concat([]);
|
||||||
|
|
||||||
|
let runAllWaiter = () => {
|
||||||
|
for(let i = 0; i < fnList.length; i++) {
|
||||||
|
fnList[i]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.waiterList = [];
|
||||||
|
return runAllWaiter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储数据类型
|
||||||
|
*/
|
||||||
|
type IStorageData = {
|
||||||
|
[x:string]:any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储库
|
||||||
|
* @template T 数据类型
|
||||||
|
*
|
||||||
|
* 特点:
|
||||||
|
* 1. 该类封装了 wxStorage 操作
|
||||||
|
* 2. 全异步获取无阻塞
|
||||||
|
* 3. 使用数据缓缓冲区,优化高频存取
|
||||||
|
* 4.
|
||||||
|
*/
|
||||||
|
class Storage<T extends IStorageData> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger 使用的标签
|
||||||
|
*/
|
||||||
|
public StorageLogLabel:LogLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据唯一索引
|
||||||
|
*/
|
||||||
|
public key:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据键值的默认数据类型
|
||||||
|
*/
|
||||||
|
private readonly defaultData:T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存数据
|
||||||
|
*/
|
||||||
|
private cache:T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cache 到 storage 等待列表
|
||||||
|
*/
|
||||||
|
private saveWaiter:Waiter = new Waiter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据从 cache 同步到 storage
|
||||||
|
*/
|
||||||
|
public async save():Promise<void> {
|
||||||
|
|
||||||
|
// 如果没有开始存储
|
||||||
|
// 发起一次异步读取
|
||||||
|
if(this.saveWaiter.state === StorageState.DONE)
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
// 获取函数绑定列表
|
||||||
|
let fnList = this.saveWaiter.done();
|
||||||
|
|
||||||
|
wx.setStorage<T>({
|
||||||
|
key: this.key,
|
||||||
|
data: this.cache,
|
||||||
|
|
||||||
|
success: (data) => {
|
||||||
|
Logger.log(`数据保存成功! errMsg: ${ data.errMsg }`,
|
||||||
|
LevelLogLabel.TraceLabel, this.StorageLogLabel);
|
||||||
|
},
|
||||||
|
|
||||||
|
fail: (data) => {
|
||||||
|
Logger.log(`数据保存失败! errMsg: ${ data.errMsg }`,
|
||||||
|
LevelLogLabel.FatalLabel, this.StorageLogLabel);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: () => {
|
||||||
|
fnList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((r) => {
|
||||||
|
|
||||||
|
// 加入等待队列
|
||||||
|
this.saveWaiter.addWaiter(r);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置全部数据
|
||||||
|
*/
|
||||||
|
public async reset() {
|
||||||
|
this.cache = this.defaultData;
|
||||||
|
Logger.logMultiple([LevelLogLabel.InfoLabel, this.StorageLogLabel],
|
||||||
|
`正在重置为默认数据... 数据内容:\n`, this.cache
|
||||||
|
);
|
||||||
|
return this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param defaultData 键值默认数据
|
||||||
|
*/
|
||||||
|
public constructor(key:string, defaultData:T) {
|
||||||
|
this.key = key;
|
||||||
|
this.defaultData = defaultData;
|
||||||
|
this.StorageLogLabel = new LogLabel(
|
||||||
|
`Storage:${ this.key }`, colorRadio(34, 230, 258)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 读取数据到缓存
|
||||||
|
this.cache = wx.getStorageSync<T>(this.key);
|
||||||
|
|
||||||
|
// 使用默认值
|
||||||
|
if(!this.cache) {
|
||||||
|
Logger.log(`找不到 Storage 数据!`,
|
||||||
|
LevelLogLabel.InfoLabel, this.StorageLogLabel);
|
||||||
|
this.reset();
|
||||||
|
} else {
|
||||||
|
Logger.logMultiple([LevelLogLabel.InfoLabel, this.StorageLogLabel],
|
||||||
|
`数据成功从 Storage 读取, 数据内容:\n`, this.cache
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取键值对应数据
|
||||||
|
* @param key 键值
|
||||||
|
*/
|
||||||
|
public get<M extends keyof T>(key:M):T[M] {
|
||||||
|
return this.cache[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置键值对应数据
|
||||||
|
* @param key 键值
|
||||||
|
*/
|
||||||
|
public async set<M extends keyof T>(key:M, value:T[M]):Promise<void> {
|
||||||
|
this.cache[key] = value;
|
||||||
|
return this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Storage;
|
||||||
|
export { Storage, StorageState, Waiter };
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// import { Logger } from "../logger/Logger";
|
||||||
|
import { LogStyle, LogLabel } from "./LogLabel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试结果
|
||||||
|
*/
|
||||||
|
class TestResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用例名称
|
||||||
|
*/
|
||||||
|
public caseName:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试结果
|
||||||
|
*/
|
||||||
|
public result:boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息
|
||||||
|
*/
|
||||||
|
public message:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 附加消息
|
||||||
|
*/
|
||||||
|
public attach:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
* @param caseName 用例名称
|
||||||
|
*/
|
||||||
|
constructor(caseName:string) {
|
||||||
|
this.caseName = caseName;
|
||||||
|
this.result = false;
|
||||||
|
this.message = "";
|
||||||
|
this.attach = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置结果
|
||||||
|
*/
|
||||||
|
public setResult(result:boolean, message?:string, attach?:string) {
|
||||||
|
this.result = result;
|
||||||
|
this.message = message ?? (result ? "success!" : "failed!");
|
||||||
|
this.attach = attach ?? this.attach;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试函数结构
|
||||||
|
*/
|
||||||
|
type TestFunction = () => TestResult | Promise<TestResult>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集测试函数结构
|
||||||
|
*/
|
||||||
|
class CaseCollect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用例键名
|
||||||
|
*/
|
||||||
|
public key:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用例测试函数
|
||||||
|
*/
|
||||||
|
public caseFunction:TestFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试结果
|
||||||
|
*/
|
||||||
|
result: Promise<TestResult> | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param key 测试用例键名
|
||||||
|
* @param caseFunction 测试函数
|
||||||
|
*/
|
||||||
|
public constructor(key:string, caseFunction:TestFunction) {
|
||||||
|
this.key = key;
|
||||||
|
this.caseFunction = caseFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行测试用例
|
||||||
|
*/
|
||||||
|
public async runTestCase():Promise<CaseCollect> {
|
||||||
|
|
||||||
|
let result = this.caseFunction();
|
||||||
|
|
||||||
|
if(result instanceof Promise) {
|
||||||
|
this.result = result;
|
||||||
|
} else {
|
||||||
|
this.result = Promise.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly baseStyle = new LogStyle().setBlank();
|
||||||
|
|
||||||
|
public static readonly successLabel:LogLabel = new LogLabel("√",
|
||||||
|
new LogStyle().setBlank("0 4px").setBorder("1000px", "1px solid green")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印结果
|
||||||
|
* @param current 当前进度
|
||||||
|
* @param total 总进度
|
||||||
|
*/
|
||||||
|
public printResult(current?:number, total?:number) {
|
||||||
|
|
||||||
|
// 如果测试没有运行,先运行它
|
||||||
|
if(this.result === void 0) this.runTestCase();
|
||||||
|
|
||||||
|
this.result?.then((res) => {
|
||||||
|
|
||||||
|
if(res.result) {
|
||||||
|
console.log(
|
||||||
|
`%c√%c %c1/1%c %c${ this.key }%c ` + res.message,
|
||||||
|
"padding:0 4px; border-radius:1000px; border:1px solid green; color:green",
|
||||||
|
"", "padding:0 4px; border-radius:4px; border:1px solid green; color:green",
|
||||||
|
"", "padding:0 4px; border-radius:4px; border:1px solid #979797; color:#979797",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`%c√%c %c1/1%c %c${ this.key }%c ` + res.message,
|
||||||
|
"padding:0 4px; border-radius:1000px; border:1px solid red; color:red",
|
||||||
|
"", "padding:0 4px; border-radius:4px; border:1px solid red; color:red",
|
||||||
|
"", "padding:0 4px; border-radius:4px; border:1px solid #979797; color:#979797",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
console.log(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集测试用例
|
||||||
|
* @param testCaseClass 测试用例表
|
||||||
|
*/
|
||||||
|
public static collectCase(testCaseClass:ITestCase):CaseCollect[] {
|
||||||
|
|
||||||
|
// 获取静态方法 key
|
||||||
|
let key = Object.getOwnPropertyNames(testCaseClass);
|
||||||
|
|
||||||
|
// 过滤掉通用的方法和属性
|
||||||
|
key = key.filter((key) => !/(length|name|prototype)/.test(key) );
|
||||||
|
|
||||||
|
// 生成 CaseCollect
|
||||||
|
let caseCollect = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < key.length; i++) {
|
||||||
|
caseCollect.push(new CaseCollect(key[i], testCaseClass[key[i]]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return caseCollect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行测试样例
|
||||||
|
*/
|
||||||
|
public static async runCollectCase(cases:CaseCollect[]):Promise<CaseCollect[]> {
|
||||||
|
|
||||||
|
let running:Promise<CaseCollect>[] = [];
|
||||||
|
|
||||||
|
for(let i = 0; i < cases.length; i++) {
|
||||||
|
running.push(cases[i].runTestCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(running);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动单元测试
|
||||||
|
*/
|
||||||
|
public static runUnitTest(testCaseClass:ITestCase) {
|
||||||
|
|
||||||
|
let caseCollect = this.collectCase(testCaseClass);
|
||||||
|
|
||||||
|
CaseCollect.runCollectCase(caseCollect).then((caseCollect:CaseCollect[]) => {
|
||||||
|
|
||||||
|
for(let i = 0; i < caseCollect.length; i++) {
|
||||||
|
caseCollect[i].printResult()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例接口
|
||||||
|
*/
|
||||||
|
interface ITestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用例函数
|
||||||
|
*/
|
||||||
|
[key:string]:TestFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ITestCase;
|
||||||
|
export { ITestCase, TestResult, TestFunction, CaseCollect };
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#3EA3D8;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M839.8,71.8H715.5c-18.5-34.5-55.1-58.2-96.9-58.2H414.1c-41.9,0-78.3,23.6-96.9,58.2H192.9
|
||||||
|
C131,71.8,80.8,122.2,80.8,184v711.4c0,61.9,50.2,112.1,112.1,112.1h646.9c61.9,0,112.1-50.2,112.1-112.1V184
|
||||||
|
C951.8,122.1,901.6,71.8,839.8,71.8L839.8,71.8z M414.1,72.2h204.5c28.3,0,51.4,23.1,51.4,51.4S646.9,175,618.6,175H414.1
|
||||||
|
c-28.3,0-51.4-23.1-51.4-51.4C362.6,95.3,385.8,72.2,414.1,72.2L414.1,72.2z M893.4,895.4c0,29.6-24,53.6-53.6,53.6H192.9
|
||||||
|
c-29.6,0-53.6-24-53.6-53.6V184c0-29.6,24-53.6,53.6-53.6h111.6c3.5,57.5,51.4,103.1,109.6,103.1h204.5
|
||||||
|
c58.3,0,106.1-45.6,109.6-103.1h111.6c29.6,0,53.6,24,53.6,53.6L893.4,895.4L893.4,895.4z"/>
|
||||||
|
<path class="st0" d="M314.6,730.6H459v-350c0-15.8,9.9-24.4,29.7-25.7c19.8,1.3,30.3,9.9,31.6,25.7v96.9h172
|
||||||
|
c17.1,1.3,26.4,11.2,27.7,29.7c-1.3,19.8-10.6,29.7-27.7,29.7h-172v193.8H722c14.5,1.3,22.4,9.9,23.7,25.7
|
||||||
|
c-1.3,18.4-9.2,28.3-23.7,29.7H314.6c-15.8-1.3-24.4-11.2-25.7-29.7C290.2,740.4,298.8,731.9,314.6,730.6L314.6,730.6z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#3EA3D8;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M838.4,71.7H713.8c-18.5-34.6-55.2-58.3-97.1-58.3H412c-42,0-78.4,23.7-97,58.3H190.4
|
||||||
|
c-62,0-112.2,50.5-112.2,112.3v712.6c0,62,50.3,112.2,112.2,112.2h648c62,0,112.2-50.3,112.2-112.2V184.1
|
||||||
|
C950.6,122.1,900.3,71.7,838.4,71.7z M412,72.1h204.8c28.4,0,51.4,23.1,51.4,51.4c0,28.4-23.1,51.5-51.4,51.5H412
|
||||||
|
c-28.4,0-51.4-23.1-51.4-51.5C360.4,95.2,383.6,72.1,412,72.1z M892,896.6c0,29.6-24.1,53.7-53.7,53.7h-648
|
||||||
|
c-29.6,0-53.7-24.1-53.7-53.7V184.1c0-29.6,24.1-53.7,53.7-53.7h111.8c3.5,57.6,51.4,103.3,109.8,103.3h204.8
|
||||||
|
c58.4,0,106.3-45.7,109.8-103.3h111.8c29.6,0,53.7,24.1,53.7,53.7C892,184.1,892,896.6,892,896.6z"/>
|
||||||
|
<path class="st0" d="M308.4,358h411.9c15.8,1.3,25.1,11.2,27.7,29.7c-1.3,18.5-9.9,27.7-25.7,27.7H528.2V461
|
||||||
|
c52.8,33,103.6,72,152.5,116.8c18.5,19.8,21.8,38.3,9.9,55.4c-13.2,11.9-30.4,8.6-51.5-9.9c-42.3-39.6-79.2-71.3-110.9-95.1V764
|
||||||
|
c-1.3,14.5-11.2,22.4-29.7,23.8c-18.5-1.3-27.7-9.3-27.7-23.8V415.4H310.4c-15.8,0-24.4-9.2-25.7-27.7
|
||||||
|
C284.7,369.2,292.6,359.3,308.4,358z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="D" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#3EA3D8;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M474.1,995.9l5.4,4.9c33.6,27.3,86.1,4,86.1-41.4V729.7h371.2c29.3,0,53-23.7,53-53V346.7l-0.5-7.1
|
||||||
|
c-3.6-26.3-26-45.9-52.5-45.9l-371.2,0.1V64c0-21.7-13.2-41.2-33.4-49.2c-20.2-8-43.2-3-58.1,12.8L49.9,475.3
|
||||||
|
c-19.3,20.4-19.3,52.4,0,72.9L474.1,995.9z M161.4,511.7l298.1-314.7v149.7l0.5,7.2c3.6,26.3,26,45.8,52.5,45.8l371.1,0.1v223.8
|
||||||
|
H512.5l-7.2,0.5c-26.3,3.6-45.8,26-45.8,52.5v149.8L161.4,511.7z M161.4,511.7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 837 B |
@@ -1,61 +0,0 @@
|
|||||||
import { LogStyle } from "./LogStyle";
|
|
||||||
import { LogLabel } from "./LogLabel";
|
|
||||||
import { StackInfo } from "./StackInfo";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内部预定义的 LogLabel
|
|
||||||
*/
|
|
||||||
class InternalLogLabel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 堆栈路径样式
|
|
||||||
*/
|
|
||||||
public static readonly normalStyle:LogStyle = new LogStyle()
|
|
||||||
.setColor("#CCCCCC").setBorder("4px", "1px solid #979797").setBlank("0 5px");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一个回车
|
|
||||||
*/
|
|
||||||
public static readonly blankLabel = new LogLabel("\n\r",
|
|
||||||
new LogStyle(), false, true, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 包含文件名和行号的 label
|
|
||||||
*/
|
|
||||||
public static get fileNameLabel():LogLabel {
|
|
||||||
|
|
||||||
// 获得调用堆栈
|
|
||||||
let stack = StackInfo.getFirstStack();
|
|
||||||
|
|
||||||
return new LogLabel(stack?.fileName ?? "Unknown file name",
|
|
||||||
InternalLogLabel.normalStyle, false, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 包含 URL 链接的 label
|
|
||||||
*/
|
|
||||||
public static get urlLabel():LogLabel {
|
|
||||||
|
|
||||||
// 获得调用堆栈
|
|
||||||
let stack = StackInfo.getFirstStack();
|
|
||||||
|
|
||||||
return new LogLabel(stack?.url ?? "Unknown url",
|
|
||||||
InternalLogLabel.normalStyle, false, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅仅用来 filter 的 URL 链接的 label
|
|
||||||
*/
|
|
||||||
public static get filterUrlLabel():LogLabel {
|
|
||||||
|
|
||||||
// 获得调用堆栈
|
|
||||||
let stack = StackInfo.getFirstStack();
|
|
||||||
|
|
||||||
return new LogLabel(stack?.url ?? "Unknown url",
|
|
||||||
InternalLogLabel.normalStyle, true, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InternalLogLabel;
|
|
||||||
export {InternalLogLabel};
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { LogStyle } from "./LogStyle";
|
|
||||||
import { LogLabel } from "./LogLabel";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成圆角颜色标签样式
|
|
||||||
*/
|
|
||||||
const normalStyleGen = (color:string):LogStyle => {
|
|
||||||
return new LogStyle().setBorder("4px", `1px solid ${color}`)
|
|
||||||
.setColor(color).setBlank("0 5px");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试等级标签
|
|
||||||
*/
|
|
||||||
class LevelLogLabel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 致命
|
|
||||||
*/
|
|
||||||
static readonly FatalLabel = new LogLabel(
|
|
||||||
"FATAL", normalStyleGen("#FF00CC")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误
|
|
||||||
*/
|
|
||||||
static readonly ErrorLabel = new LogLabel(
|
|
||||||
"ERROR", normalStyleGen("#FF0000")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 警告
|
|
||||||
*/
|
|
||||||
static readonly WarnLabel = new LogLabel(
|
|
||||||
"WARN", normalStyleGen("#FF9900")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息
|
|
||||||
*/
|
|
||||||
static readonly InfoLabel = new LogLabel(
|
|
||||||
"INFO", normalStyleGen("#99FF00")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试
|
|
||||||
*/
|
|
||||||
static readonly DebugLabel = new LogLabel(
|
|
||||||
"DEBUG", normalStyleGen("#00FF99")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 追踪
|
|
||||||
*/
|
|
||||||
static readonly TraceLabel = new LogLabel(
|
|
||||||
"TRACE", normalStyleGen("#00CCFF")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LevelLogLabel;
|
|
||||||
export { LevelLogLabel };
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import { LogStyle } from "./LogStyle";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志标签
|
|
||||||
*/
|
|
||||||
class LogLabel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关键字
|
|
||||||
* 用于标识这个类别
|
|
||||||
*/
|
|
||||||
public key:string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文字样式
|
|
||||||
*/
|
|
||||||
public style:LogStyle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否受到过滤器影响
|
|
||||||
*/
|
|
||||||
public checked:boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否输出
|
|
||||||
*/
|
|
||||||
public display:boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为附件标签
|
|
||||||
* 例如回车、时间、代码位置
|
|
||||||
*/
|
|
||||||
public attach:boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key 关键字
|
|
||||||
* @param style 文字样式
|
|
||||||
*/
|
|
||||||
constructor(key:string, style:LogStyle,
|
|
||||||
checked?:boolean, display?:boolean, attach?:boolean) {
|
|
||||||
this.key = key;
|
|
||||||
this.style = style;
|
|
||||||
this.checked = checked ?? true;
|
|
||||||
this.display = display ?? true;
|
|
||||||
this.attach = attach ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 Logger 输出使用的内容
|
|
||||||
*/
|
|
||||||
public getLoggerOutput():string {
|
|
||||||
if(!this.display) return "";
|
|
||||||
return `%c${ this.key }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 Text 输出内容
|
|
||||||
*/
|
|
||||||
public getTextOutput():string {
|
|
||||||
if(!this.display) return "";
|
|
||||||
return `[${ this.key }]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 style 格式化
|
|
||||||
*/
|
|
||||||
public getStyleOutput():string {
|
|
||||||
if(!this.display) return "";
|
|
||||||
return this.style.stringify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验
|
|
||||||
*/
|
|
||||||
public checking(src:RegExp | string):boolean {
|
|
||||||
|
|
||||||
let pass = false;
|
|
||||||
|
|
||||||
// 关闭校验
|
|
||||||
if(!this.checked) return pass;
|
|
||||||
|
|
||||||
if(src instanceof RegExp) {
|
|
||||||
pass = (src as RegExp).test(this.key)
|
|
||||||
} else {
|
|
||||||
pass = (src as string) === this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LogLabel;
|
|
||||||
export {LogLabel}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* 多重内容捆绑
|
|
||||||
* 用于 log 输出
|
|
||||||
*/
|
|
||||||
class MultipleLogContent<T extends Array<any>> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 输出内容
|
|
||||||
*/
|
|
||||||
private readonly content:T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param content 输出内容
|
|
||||||
*/
|
|
||||||
public constructor(...content:T) {
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取内容
|
|
||||||
*/
|
|
||||||
public getContent():T {
|
|
||||||
return this.content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MultipleLogContent;
|
|
||||||
export { MultipleLogContent };
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* 栈信息
|
|
||||||
*/
|
|
||||||
class StackInfo {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 函数名
|
|
||||||
*/
|
|
||||||
public functionName:string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件名
|
|
||||||
*/
|
|
||||||
public fileName:string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件路径
|
|
||||||
*/
|
|
||||||
public url:string | undefined;
|
|
||||||
|
|
||||||
public setInfo(functionName:string, fileName:string, url:string):StackInfo {
|
|
||||||
this.functionName = functionName;
|
|
||||||
this.fileName = fileName;
|
|
||||||
this.url = url;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取函数调用栈列表
|
|
||||||
*/
|
|
||||||
public static getCallStack():StackInfo[] {
|
|
||||||
|
|
||||||
// 获取堆栈信息
|
|
||||||
let stack:string | undefined = new Error().stack;
|
|
||||||
|
|
||||||
if (stack === void 0) return [];
|
|
||||||
|
|
||||||
// 去除 Error
|
|
||||||
stack = stack.replace(/^(Error)\s/, "");
|
|
||||||
|
|
||||||
// 获取堆栈信息
|
|
||||||
let stackList:string[] = stack.split(/\n/);
|
|
||||||
|
|
||||||
let callStack:StackInfo[] = [];
|
|
||||||
|
|
||||||
for(let i = 0; i < stackList.length; i++) {
|
|
||||||
|
|
||||||
let matcher = stackList[i].match(/^\s+at\s+(.+)\s(\(.+\))/);
|
|
||||||
if (matcher === null || matcher.length < 3) continue;
|
|
||||||
|
|
||||||
let fileName = matcher[2].match(/.+\/(.+\..+:\d+:\d+)\)/);
|
|
||||||
if (fileName === null || matcher.length < 2) continue;
|
|
||||||
|
|
||||||
callStack.push(new StackInfo().setInfo(
|
|
||||||
matcher[1], fileName[1], matcher[2]?.replace(/(\(|\))/g, "")
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(callStack);
|
|
||||||
|
|
||||||
return callStack;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 排除的
|
|
||||||
*/
|
|
||||||
public static readonly excludeFile:RegExp = /^Logger\.js:\d+:\d+/;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取第一个调用栈
|
|
||||||
*/
|
|
||||||
public static getFirstStack():StackInfo | undefined {
|
|
||||||
|
|
||||||
let callStack = this.getCallStack();
|
|
||||||
|
|
||||||
for(let i = 0; i < callStack.length; i++) {
|
|
||||||
|
|
||||||
if(!callStack[i].fileName) continue;
|
|
||||||
|
|
||||||
if(!StackInfo.excludeFile.test(callStack[i].fileName ?? "")) {
|
|
||||||
return callStack[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StackInfo;
|
|
||||||
export { StackInfo };
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import Logger from "./Logger";
|
|
||||||
|
|
||||||
export default Logger;
|
|
||||||
export { Logger };
|
|
||||||
|
|
||||||
export { InternalLogLabel } from "./InternalLogLabel";
|
|
||||||
export { MultipleLogContent } from "./MultipleLogContent";
|
|
||||||
export { LevelLogLabel } from "./LevelLogLabel";
|
|
||||||
export { LogLabel } from "./LogLabel";
|
|
||||||
export { LogStyle } from "./LogStyle";
|
|
||||||
export { StackInfo } from "./StackInfo";
|
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
@import "../../app.scss";
|
||||||
|
|
||||||
|
$status-bar-left-top-icon-width: 30px;
|
||||||
|
$status-bar-middle-icon-width: 15px;
|
||||||
|
|
||||||
|
view.status-bar {
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: #F8F8F8;
|
||||||
|
|
||||||
|
view.select {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 18px;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: $status-bar-left-top-icon-width;
|
||||||
|
height: $status-bar-left-top-icon-width;
|
||||||
|
filter: $black-filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.semester {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 5px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
view.semester-title {
|
||||||
|
@extend %status-bar-title;
|
||||||
|
font-size: .9em;
|
||||||
|
line-height: .9em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.semester-intro {
|
||||||
|
@extend %status-bar-subtitle;
|
||||||
|
font-size: .9em;
|
||||||
|
line-height: .9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.capsule-holder {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.content {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: $status-bar-middle-icon-width;
|
||||||
|
height: $status-bar-middle-icon-width;
|
||||||
|
filter: $black-filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.week {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
view.week-title {
|
||||||
|
@extend %status-bar-title;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.15em;
|
||||||
|
line-height: 1.15em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.week-intro {
|
||||||
|
@extend %status-bar-subtitle;
|
||||||
|
font-size: .85em;
|
||||||
|
line-height: .85em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.status-bar-blank {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
%status-bar-title {
|
||||||
|
color: $theme-color-light-title;
|
||||||
|
}
|
||||||
|
|
||||||
|
%status-bar-subtitle {
|
||||||
|
color: $theme-color-light-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
|
||||||
|
%status-bar-title {
|
||||||
|
color: $theme-color-dark-title;
|
||||||
|
}
|
||||||
|
|
||||||
|
%status-bar-subtitle {
|
||||||
|
color: $theme-color-dark-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.status-bar {
|
||||||
|
background-color: #191919;
|
||||||
|
|
||||||
|
view.select image {
|
||||||
|
filter: $white-filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { Modular, Manager, ILifetime } from "../../core/Module";
|
||||||
|
import { LevelLogLabel, LifeCycleLogLabel, NormalStyle } from "../../core/PresetLogLabel";
|
||||||
|
import { LogLabel } from "../../core/LogLabel";
|
||||||
|
import { Logger } from "../../core/Logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 UI 中显示的数据
|
||||||
|
*/
|
||||||
|
type DisplayData = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示内容
|
||||||
|
*/
|
||||||
|
val:string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 唯一键值 用来做判断
|
||||||
|
*/
|
||||||
|
key:string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模组事件
|
||||||
|
*/
|
||||||
|
type StatusBarEvent = {
|
||||||
|
m: DisplayData
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顶部状态栏
|
||||||
|
*/
|
||||||
|
class StatusBar<M extends Manager> extends Modular<M, {}, StatusBarEvent>
|
||||||
|
implements Partial<ILifetime> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航栏高度补偿
|
||||||
|
*/
|
||||||
|
public static readonly StatusBarHeightExtend:number = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面日志输出标签
|
||||||
|
*/
|
||||||
|
public static readonly StatusBarLabel = new LogLabel(
|
||||||
|
"StatusBar", NormalStyle
|
||||||
|
);
|
||||||
|
|
||||||
|
data = {
|
||||||
|
|
||||||
|
// 导航栏高度
|
||||||
|
barHeight: 65,
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
barTop: 20,
|
||||||
|
|
||||||
|
// 胶囊占位
|
||||||
|
capsule: 94
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置顶部状态栏高度
|
||||||
|
*/
|
||||||
|
public setHeight() {
|
||||||
|
|
||||||
|
let systemInfo = wx.getSystemInfoSync();
|
||||||
|
let headerPos = wx.getMenuButtonBoundingClientRect();
|
||||||
|
|
||||||
|
//状态栏高度
|
||||||
|
let statusBarHeight = Number(systemInfo.statusBarHeight);
|
||||||
|
|
||||||
|
// 胶囊实际位置,坐标信息不是左上角原点
|
||||||
|
let btnPosI = {
|
||||||
|
|
||||||
|
// 胶囊实际高度
|
||||||
|
height: headerPos.height,
|
||||||
|
width: headerPos.width,
|
||||||
|
|
||||||
|
// 胶囊top - 状态栏高度
|
||||||
|
top: headerPos.top - statusBarHeight,
|
||||||
|
|
||||||
|
// 胶囊bottom - 胶囊height - 状态栏height (胶囊实际bottom 为距离导航栏底部的长度)
|
||||||
|
bottom: headerPos.bottom - headerPos.height - statusBarHeight,
|
||||||
|
|
||||||
|
// 这里不能获取 屏幕宽度,PC端打开小程序会有BUG,要获取窗口高度 - 胶囊right
|
||||||
|
right: systemInfo.windowWidth - headerPos.right
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算顶部导航栏高度
|
||||||
|
let barHeight = btnPosI.height + btnPosI.top + btnPosI.bottom;
|
||||||
|
|
||||||
|
// 计算胶囊展位
|
||||||
|
let capsule = btnPosI.right + btnPosI.width;
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
|
||||||
|
// 不知道为什么总是差 4px 距离
|
||||||
|
// 别问为什么 加上就对了
|
||||||
|
barHeight: barHeight + 4 + StatusBar.StatusBarHeightExtend,
|
||||||
|
barTop: statusBarHeight - StatusBar.StatusBarHeightExtend,
|
||||||
|
capsule: capsule
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.log(`计算并设置 StatusBar 的高度为: ${ barHeight + 4 }px, ` +
|
||||||
|
`状态栏高度: ${ statusBarHeight }px, 胶囊占位: ${ capsule }px`,
|
||||||
|
LevelLogLabel.DebugLabel, StatusBar.StatusBarLabel);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public onLoad() {
|
||||||
|
|
||||||
|
Logger.log("StatusBar 模块加载...",
|
||||||
|
LevelLogLabel.TraceLabel, LifeCycleLogLabel.OnLoadLabel, StatusBar.StatusBarLabel);
|
||||||
|
|
||||||
|
this.setHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatusBar;
|
||||||
|
export { StatusBar };
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"usingComponents": {}
|
"usingComponents": {},
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
@import "./StatusBar.scss"
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +1,10 @@
|
|||||||
// pages/Timetable/Timetable.ts
|
import { Manager} from "../../core/Module";
|
||||||
Page({
|
import { StatusBar } from "./StatusBar";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 页面的初始数据
|
* 此页面使用 Manager 进行模块化管理
|
||||||
*/
|
* 若要添加先功能请先定义 Modular 并添加至 Manager
|
||||||
data: {
|
*/
|
||||||
|
Manager.Page((manager)=>{
|
||||||
},
|
manager.addModule(StatusBar, "statusBar");
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期函数--监听页面加载
|
|
||||||
*/
|
|
||||||
onLoad() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期函数--监听页面初次渲染完成
|
|
||||||
*/
|
|
||||||
onReady() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期函数--监听页面显示
|
|
||||||
*/
|
|
||||||
onShow() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期函数--监听页面隐藏
|
|
||||||
*/
|
|
||||||
onHide() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生命周期函数--监听页面卸载
|
|
||||||
*/
|
|
||||||
onUnload() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 页面相关事件处理函数--监听用户下拉动作
|
|
||||||
*/
|
|
||||||
onPullDownRefresh() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 页面上拉触底事件的处理函数
|
|
||||||
*/
|
|
||||||
onReachBottom() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户点击右上角分享
|
|
||||||
*/
|
|
||||||
onShareAppMessage() {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
@@ -1 +1,30 @@
|
|||||||
<text>pages/Timetable/Timetable.wxml</text>
|
|
||||||
|
<!-- 顶部状态栏 -->
|
||||||
|
<view class="status-bar" style="height:{{ statusBar$barHeight }}px; padding-top:{{ statusBar$barTop }}px;">
|
||||||
|
|
||||||
|
<!-- 学期选择 -->
|
||||||
|
<view class="select">
|
||||||
|
<image src="/image/ui/last_semester.svg"/>
|
||||||
|
<view class="semester">
|
||||||
|
<view class="semester-title">大四-上</view>
|
||||||
|
<view class="semester-intro">2021-2022</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 周选择 -->
|
||||||
|
<view class="content">
|
||||||
|
<view class="week">
|
||||||
|
<view class="week-title">第1周</view>
|
||||||
|
<view class="week-intro">双击返回本周</view>
|
||||||
|
</view>
|
||||||
|
<image src="/image/ui/selectArror.svg"/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 胶囊占位 -->
|
||||||
|
<view class="capsule-holder" style="width: {{ statusBar$capsule }}px"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 状态栏占位 -->
|
||||||
|
<view class="status-bar-blank" style="height: {{ statusBar$barHeight + statusBar$barTop }}px"></view>
|
||||||
|
|
||||||
|
<!-- <text>afdff</text> -->
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"light": {
|
"light": {
|
||||||
"navigationBarBackgroundColor": "#f6f6f6",
|
"navigationBarBackgroundColor": "#f8f8f8",
|
||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"backgroundColor": "#f4f0f1",
|
"backgroundColor": "#f4f0f1",
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user