form-driver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/m3.css +310 -0
- package/dist/m3.js +1 -0
- package/es/m3.css +310 -0
- package/es/m3.js +20919 -0
- package/lib/m3.css +310 -0
- package/lib/m3.js +20959 -0
- package/package.json +132 -0
- package/src/.DS_Store +0 -0
- package/src/framework/Ajax.ts +96 -0
- package/src/framework/Assembly.tsx +165 -0
- package/src/framework/Init.tsx +196 -0
- package/src/framework/M3.tsx +94 -0
- package/src/framework/MContext.ts +15 -0
- package/src/framework/MFieldViewer.tsx +32 -0
- package/src/framework/MUtil.tsx +653 -0
- package/src/framework/MViewer.less +128 -0
- package/src/framework/MViewer.tsx +180 -0
- package/src/framework/MViewerDebug.tsx +95 -0
- package/src/framework/Persistant.ts +90 -0
- package/src/framework/Schema.ts +386 -0
- package/src/framework/SchemaFunc.ts +30 -0
- package/src/framework/Validator.ts +160 -0
- package/src/framework/editorMap.ts +109 -0
- package/src/index.ts +33 -0
- package/src/types/MArrayType.ts +73 -0
- package/src/types/MCascadeType.ts +35 -0
- package/src/types/MCnAddressType.ts +54 -0
- package/src/types/MDateRangeType.ts +52 -0
- package/src/types/MDateTimeType.ts +53 -0
- package/src/types/MDecorationType.ts +6 -0
- package/src/types/MEnumType.ts +65 -0
- package/src/types/MExperienceType.ts +81 -0
- package/src/types/MFloatType.ts +10 -0
- package/src/types/MGB2260Type.ts +56 -0
- package/src/types/MIntDiffType.ts +6 -0
- package/src/types/MIntType.ts +44 -0
- package/src/types/MKvSetType.ts +50 -0
- package/src/types/MMatrixType.ts +52 -0
- package/src/types/MObjectType.ts +89 -0
- package/src/types/MSetType.ts +220 -0
- package/src/types/MStringType.ts +27 -0
- package/src/types/MTelType.ts +14 -0
- package/src/types/MType.ts +77 -0
- package/src/types/MVLPairType.ts +35 -0
- package/src/types/gb2260.json +1 -0
- package/src/ui/BaseViewer.tsx +110 -0
- package/src/ui/editor/.DS_Store +0 -0
- package/src/ui/editor/basic/.DS_Store +0 -0
- package/src/ui/editor/basic/ACascadePicker.tsx +114 -0
- package/src/ui/editor/basic/ACheckBox.tsx +104 -0
- package/src/ui/editor/basic/ADatetimePicker.tsx +76 -0
- package/src/ui/editor/basic/AGB2260.tsx +52 -0
- package/src/ui/editor/basic/AInputBox.tsx +59 -0
- package/src/ui/editor/basic/AIntBox.tsx +39 -0
- package/src/ui/editor/basic/AKvSet.less +9 -0
- package/src/ui/editor/basic/AKvSet.tsx +90 -0
- package/src/ui/editor/basic/ARadio.tsx +86 -0
- package/src/ui/editor/basic/ARangePicker.tsx +129 -0
- package/src/ui/editor/basic/ARate.less +8 -0
- package/src/ui/editor/basic/ARate.tsx +37 -0
- package/src/ui/editor/basic/ARemoteSelector.tsx +116 -0
- package/src/ui/editor/basic/ASelector.tsx +88 -0
- package/src/ui/editor/basic/ASetSelector.tsx +65 -0
- package/src/ui/editor/basic/ASpecInputBox.tsx +20 -0
- package/src/ui/editor/basic/ATreeSelect.tsx +41 -0
- package/src/ui/editor/basic/AUpload.tsx +119 -0
- package/src/ui/editor/basic/NPS.less +21 -0
- package/src/ui/editor/basic/NPS.tsx +47 -0
- package/src/ui/editor/complex/AArray.less +10 -0
- package/src/ui/editor/complex/AArray.tsx +104 -0
- package/src/ui/editor/complex/AArrayGrid.tsx +115 -0
- package/src/ui/editor/complex/ACnAddress.less +15 -0
- package/src/ui/editor/complex/ACnAddress.tsx +61 -0
- package/src/ui/editor/complex/ADialogForm.tsx +45 -0
- package/src/ui/editor/complex/AExperience.tsx +85 -0
- package/src/ui/editor/complex/AForm.less +35 -0
- package/src/ui/editor/complex/AForm.tsx +340 -0
- package/src/ui/editor/complex/AIntDiff.tsx +77 -0
- package/src/ui/editor/complex/AMatrix.less +18 -0
- package/src/ui/editor/complex/AMatrix.tsx +242 -0
- package/src/ui/editor/complex/ATable.less +4 -0
- package/src/ui/editor/complex/ATable.tsx +33 -0
- package/src/ui/editor/complex/JsonEditor.tsx +37 -0
- package/src/ui/readable/A.tsx +33 -0
- package/src/ui/readable/ArrayViewer.tsx +46 -0
- package/src/ui/readable/DecorationViewer.tsx +76 -0
- package/src/ui/readable/DivViewer.tsx +11 -0
- package/src/ui/widget/Collapsible.tsx +156 -0
- package/src/ui/widget/Segment.less +39 -0
- package/src/ui/widget/Segment.tsx +40 -0
- package/src/ui/widget/SegmentEditSwitch.tsx +46 -0
- package/src/ui/widget/SelectBox.tsx +43 -0
- package/src/ui/widget/UnderlineInputBox.less +47 -0
- package/src/ui/widget/UnderlineInputBox.tsx +10 -0
- package/types/framework/Ajax.d.ts +5 -0
- package/types/framework/Assembly.d.ts +59 -0
- package/types/framework/Init.d.ts +4 -0
- package/types/framework/M3.d.ts +6 -0
- package/types/framework/MContext.d.ts +11 -0
- package/types/framework/MFieldViewer.d.ts +8 -0
- package/types/framework/MUtil.d.ts +180 -0
- package/types/framework/MViewer.d.ts +75 -0
- package/types/framework/MViewerDebug.d.ts +11 -0
- package/types/framework/Persistant.d.ts +17 -0
- package/types/framework/Schema.d.ts +306 -0
- package/types/framework/SchemaFunc.d.ts +14 -0
- package/types/framework/Validator.d.ts +53 -0
- package/types/framework/editorMap.d.ts +107 -0
- package/types/index.d.ts +21 -0
- package/types/types/MArrayType.d.ts +2 -0
- package/types/types/MCascadeType.d.ts +11 -0
- package/types/types/MCnAddressType.d.ts +2 -0
- package/types/types/MDateRangeType.d.ts +7 -0
- package/types/types/MDateTimeType.d.ts +11 -0
- package/types/types/MDecorationType.d.ts +7 -0
- package/types/types/MEnumType.d.ts +2 -0
- package/types/types/MExperienceType.d.ts +5 -0
- package/types/types/MFloatType.d.ts +2 -0
- package/types/types/MGB2260Type.d.ts +9 -0
- package/types/types/MIntDiffType.d.ts +2 -0
- package/types/types/MIntType.d.ts +2 -0
- package/types/types/MKvSetType.d.ts +11 -0
- package/types/types/MMatrixType.d.ts +5 -0
- package/types/types/MObjectType.d.ts +11 -0
- package/types/types/MSetType.d.ts +7 -0
- package/types/types/MStringType.d.ts +2 -0
- package/types/types/MTelType.d.ts +4 -0
- package/types/types/MType.d.ts +46 -0
- package/types/types/MVLPairType.d.ts +5 -0
- package/types/ui/BaseViewer.d.ts +45 -0
- package/types/ui/editor/basic/ACascadePicker.d.ts +11 -0
- package/types/ui/editor/basic/ACheckBox.d.ts +17 -0
- package/types/ui/editor/basic/ADatetimePicker.d.ts +15 -0
- package/types/ui/editor/basic/AGB2260.d.ts +10 -0
- package/types/ui/editor/basic/AInputBox.d.ts +8 -0
- package/types/ui/editor/basic/AIntBox.d.ts +9 -0
- package/types/ui/editor/basic/AKvSet.d.ts +19 -0
- package/types/ui/editor/basic/ARadio.d.ts +14 -0
- package/types/ui/editor/basic/ARangePicker.d.ts +30 -0
- package/types/ui/editor/basic/ARate.d.ts +13 -0
- package/types/ui/editor/basic/ARemoteSelector.d.ts +15 -0
- package/types/ui/editor/basic/ASelector.d.ts +14 -0
- package/types/ui/editor/basic/ASetSelector.d.ts +10 -0
- package/types/ui/editor/basic/ASpecInputBox.d.ts +9 -0
- package/types/ui/editor/basic/ATreeSelect.d.ts +18 -0
- package/types/ui/editor/basic/AUpload.d.ts +33 -0
- package/types/ui/editor/basic/NPS.d.ts +13 -0
- package/types/ui/editor/complex/AArray.d.ts +11 -0
- package/types/ui/editor/complex/AArrayGrid.d.ts +13 -0
- package/types/ui/editor/complex/ACnAddress.d.ts +10 -0
- package/types/ui/editor/complex/ADialogForm.d.ts +11 -0
- package/types/ui/editor/complex/AExperience.d.ts +16 -0
- package/types/ui/editor/complex/AForm.d.ts +46 -0
- package/types/ui/editor/complex/AIntDiff.d.ts +14 -0
- package/types/ui/editor/complex/AMatrix.d.ts +48 -0
- package/types/ui/editor/complex/ATable.d.ts +9 -0
- package/types/ui/editor/complex/JsonEditor.d.ts +9 -0
- package/types/ui/readable/A.d.ts +5 -0
- package/types/ui/readable/ArrayViewer.d.ts +5 -0
- package/types/ui/readable/DecorationViewer.d.ts +3 -0
- package/types/ui/readable/DivViewer.d.ts +5 -0
- package/types/ui/widget/Collapsible.d.ts +46 -0
- package/types/ui/widget/Segment.d.ts +18 -0
- package/types/ui/widget/SegmentEditSwitch.d.ts +20 -0
- package/types/ui/widget/SelectBox.d.ts +13 -0
- package/types/ui/widget/UnderlineInputBox.d.ts +6 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MViewerProp } from './MViewer';
|
|
3
|
+
|
|
4
|
+
// 上下文定义
|
|
5
|
+
export interface MContextDef {
|
|
6
|
+
/** 根配置 */
|
|
7
|
+
rootProps: MViewerProp;
|
|
8
|
+
|
|
9
|
+
/** 表单是否是强制校验状态 */
|
|
10
|
+
forceValid: boolean;
|
|
11
|
+
|
|
12
|
+
/** 将表单设置为强制校验*/
|
|
13
|
+
setForceValid: (b:boolean)=>void;
|
|
14
|
+
}
|
|
15
|
+
export const MContext = React.createContext<MContextDef|undefined>(undefined);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { MProp } from './Schema';
|
|
3
|
+
import { MUtil } from './MUtil';
|
|
4
|
+
import { assembly } from './Assembly';
|
|
5
|
+
import { MContext } from './MContext';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 一个字段的视图
|
|
9
|
+
*/
|
|
10
|
+
export class MFieldViewer extends React.Component<MProp, any> {
|
|
11
|
+
render() {
|
|
12
|
+
const viewer = assembly.getViewerOf(this.props.schema, this.props.morph);
|
|
13
|
+
if(!viewer){
|
|
14
|
+
return MUtil.error(`字段的视图尚未实现`, this.props.schema);
|
|
15
|
+
}
|
|
16
|
+
const props = {
|
|
17
|
+
afterChange: ()=>{}, // 给afterChange一个默认值
|
|
18
|
+
name:this.props.path,
|
|
19
|
+
...this.props
|
|
20
|
+
};
|
|
21
|
+
const ele = React.createElement(viewer, props, null);
|
|
22
|
+
return <MContext.Consumer>
|
|
23
|
+
{ctx => {
|
|
24
|
+
if (ctx.rootProps.wrapper && this.props.schema.type !== 'object' && this.props.schema.type !== 'array') {
|
|
25
|
+
return ctx.rootProps.wrapper(ele, this.props.schema)
|
|
26
|
+
} else {
|
|
27
|
+
return ele
|
|
28
|
+
}
|
|
29
|
+
}}
|
|
30
|
+
</MContext.Consumer>
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import moment from "moment";
|
|
3
|
+
import { M3UISpec, MEnumField, MFieldSchema, MFieldSchemaAnonymity, MValidationFail } from './Schema';
|
|
4
|
+
import { JSONSchema6 } from 'json-schema';
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { SchemaFunc } from './SchemaFunc';
|
|
7
|
+
|
|
8
|
+
export type HideMap = { [fieldName: string]: boolean };
|
|
9
|
+
|
|
10
|
+
let next = Date.now() - 1617265950471;
|
|
11
|
+
|
|
12
|
+
// /** 用来测量字符串长度 */
|
|
13
|
+
// let _messurer: HTMLCanvasElement|undefined;
|
|
14
|
+
|
|
15
|
+
export let MUtil = {
|
|
16
|
+
/**
|
|
17
|
+
* 每次调用,返回不一样的数字
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
unique(){
|
|
21
|
+
return next ++;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 枚举字段可以写成空格分隔的字符串,也能写成MEnumField[],这个函数将两种形式转换成统一的MEnumField[]
|
|
26
|
+
* @deprecated 改成用MUtils.option
|
|
27
|
+
*/
|
|
28
|
+
standardFields: (fs: string | MEnumField[] | undefined): MEnumField[] => {
|
|
29
|
+
if (typeof fs === 'string') {
|
|
30
|
+
return fs.split(" ").map(
|
|
31
|
+
aEnum => {
|
|
32
|
+
const kv = aEnum.split("=");
|
|
33
|
+
return { label: kv[0], value: kv[1] ?? kv[0] };
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
} else if (fs) {
|
|
37
|
+
return fs;
|
|
38
|
+
} else {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取enumFields或者setFields,返回标准的MEnumField[]
|
|
45
|
+
* @param f 如果f是nil,返回[]
|
|
46
|
+
*/
|
|
47
|
+
option: (f:MFieldSchemaAnonymity):MEnumField[] => {
|
|
48
|
+
let result: MEnumField[];
|
|
49
|
+
if(_.isArray(f.option)){
|
|
50
|
+
result = f.option;
|
|
51
|
+
} else {
|
|
52
|
+
let ori;
|
|
53
|
+
if(f.type == "set"){
|
|
54
|
+
ori = f.setFields;
|
|
55
|
+
} else if(f.type == "enum"){
|
|
56
|
+
ori = f.enumFields
|
|
57
|
+
} else {
|
|
58
|
+
ori = f.option ?? f.setFields ?? f.enumFields; // 只是为了保险
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof ori === 'string') {
|
|
62
|
+
f.option = result = ori.split(" ").map(
|
|
63
|
+
aEnum => {
|
|
64
|
+
const kv = aEnum.split("=");
|
|
65
|
+
return { label: kv[0], value: kv[1] ?? kv[0] };
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
f.option = result = ori; // TODO children 可能有递归的,还是得靠预处理解决
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result ?? [];
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/** 如果出错了,返回这么个元素,可能用在BaseEditor子类或者其他的东西上 */
|
|
77
|
+
error: function (msg: string, schema?: MFieldSchema | MFieldSchemaAnonymity): JSX.Element {
|
|
78
|
+
return <div key={Date.now()} style={{ color: "white", border: "1px solid darkgrey", background: "red", textAlign: "center", borderRadius: "5px" }}>
|
|
79
|
+
{msg}
|
|
80
|
+
{schema ? <pre style={{ whiteSpace: "pre-wrap", textAlign: "left" }}>{JSON.stringify(schema)}</pre> : undefined}
|
|
81
|
+
</div>;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
sign: function (v: number | null | undefined): number | null {
|
|
85
|
+
if (_.isNil(v)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return Math.sign(v);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 当前设备是不是个手机(或者类似手机的设备,比如ipad上运行iphone app中的webview)
|
|
93
|
+
*/
|
|
94
|
+
phoneLike: function () {
|
|
95
|
+
// eslint-disable-next-line no-restricted-globals
|
|
96
|
+
return screen.height / screen.width > 1.5;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// /**
|
|
100
|
+
// * 计算文字的宽度
|
|
101
|
+
// * @param text 文字
|
|
102
|
+
// * @param font 字体
|
|
103
|
+
// * @returns 宽度,单位是px
|
|
104
|
+
// */
|
|
105
|
+
// getTextWidth(text:string, font?:string) {
|
|
106
|
+
// var canvas = _messurer ?? (_messurer = document.createElement("canvas"));
|
|
107
|
+
// var context = canvas.getContext("2d");
|
|
108
|
+
// if(!context) {
|
|
109
|
+
// console.error('神奇的事情发生了:canvas.getContext("2d")居然能返回null');
|
|
110
|
+
// return text.length * 10; // 总比出不来好
|
|
111
|
+
// }
|
|
112
|
+
// if(font){
|
|
113
|
+
// context.font = font;
|
|
114
|
+
// }
|
|
115
|
+
// var metrics = context.measureText(text);
|
|
116
|
+
// return metrics.width;
|
|
117
|
+
// }
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 计算文字的宽度
|
|
121
|
+
* @param text
|
|
122
|
+
*/
|
|
123
|
+
antdTextWidth(text?: string) {
|
|
124
|
+
if (!text) {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
return text.length * 16;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 从数组中删掉一个元素
|
|
132
|
+
* @param a 这个数组会被改变的
|
|
133
|
+
* @param element 要删的元素
|
|
134
|
+
* @returns 修改后的数组(如果它不是nil)
|
|
135
|
+
*/
|
|
136
|
+
arrayRemove: function (a: any[] | null | undefined, element: any): typeof a {
|
|
137
|
+
if (a) {
|
|
138
|
+
const idx = a.indexOf(element);
|
|
139
|
+
if (idx >= 0) {
|
|
140
|
+
a.splice(idx, 1);
|
|
141
|
+
}
|
|
142
|
+
return a;
|
|
143
|
+
} else {
|
|
144
|
+
return a;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
fieldOfLayout: function (fields: MFieldSchema[], spec: M3UISpec): MFieldSchema[] {
|
|
149
|
+
const objectFieldMap = _.chain(fields).keyBy('name').value();
|
|
150
|
+
let r: { [key: string]: MFieldSchema } = {};
|
|
151
|
+
for (let s of spec.segments ?? []) {
|
|
152
|
+
for (let f of s.fields ?? []) {
|
|
153
|
+
if (_.isString(f)) {
|
|
154
|
+
const def = objectFieldMap[f];
|
|
155
|
+
if (def) {
|
|
156
|
+
r[f] = def;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return _.values(r);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 跟lodash的get一样,除了:
|
|
166
|
+
* 1. path是nil时返回undefined
|
|
167
|
+
* 2. path是""时返回原始对象
|
|
168
|
+
* @param a
|
|
169
|
+
* @param path
|
|
170
|
+
*/
|
|
171
|
+
get: function (a?: any | null, path?: string | null) {
|
|
172
|
+
if (_.isNil(a) || _.isNil(path)) {
|
|
173
|
+
return undefined;
|
|
174
|
+
} else if (path === "") {
|
|
175
|
+
return a;
|
|
176
|
+
}
|
|
177
|
+
return _.get(a, path);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* a : database
|
|
182
|
+
* v : newValue
|
|
183
|
+
* 跟lodash的set一样,除了path是""时等效于_.assign(a, v)
|
|
184
|
+
*/
|
|
185
|
+
set: function (a: any | null, path: string | null, v: any) {
|
|
186
|
+
if (path === "") {
|
|
187
|
+
// 如果 database 是数组,需要特殊处理
|
|
188
|
+
if (_.isArray(a)) a.length = v.length
|
|
189
|
+
_.assign(a, v);
|
|
190
|
+
} else {
|
|
191
|
+
_.set(a, path, v);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
debug: function (d: any, ...optionalParams: any[]) {
|
|
196
|
+
console.log(d, optionalParams);
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 填入默认值
|
|
201
|
+
* @param schema 你懂得
|
|
202
|
+
* @param database 数据库,会被修改的
|
|
203
|
+
* @param path 路径
|
|
204
|
+
*/
|
|
205
|
+
applyDefaultValue: function (schema: MFieldSchema, database: any, path: string) { // FIXME 多测试下,type是object或其他简单类型的情况
|
|
206
|
+
if (schema.type === "object") {
|
|
207
|
+
for (let f of schema.objectFields ?? []) {
|
|
208
|
+
this.applyDefaultValue(f, database, (path ? path + "." : "") + f.name);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
if (!_.isNil(schema.defaultValue) && _.isNil(_.get(database, path))) {
|
|
212
|
+
_.set(database, path, schema.defaultValue);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
filterHide: function (schema: MFieldSchema, database: any[] | object) { // TODO 要改成递归的
|
|
218
|
+
const hm = MUtil.hideMap(database, schema.objectFields ?? [], schema.uispec);
|
|
219
|
+
let finalData: any = _.isArray(database) ? [] : {};
|
|
220
|
+
for (let k in database) {
|
|
221
|
+
if (!hm[k]) {
|
|
222
|
+
finalData[k] = database[k];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return finalData;
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 计算哪些field、segment需要隐藏
|
|
230
|
+
* 支持ShowIfFunc里的函数
|
|
231
|
+
* TODO 如果segment隐藏,里面的字段全都要隐藏
|
|
232
|
+
* @returns 示例:
|
|
233
|
+
* let {name,role,gender,alias,birthday,company,position,tel,email,citizenship,politicsCode,companyArch,address,work,edu,assistant,student,mib} = data;
|
|
234
|
+
* return {politicsCode: !(citizenship=='中国大陆'),"segment:学员信息": !(_.intersection(role, ['校友']).length > 0))}
|
|
235
|
+
* @param objectFields
|
|
236
|
+
* @param uispec 用来计算segment是不是要展示,不填就不计算
|
|
237
|
+
*/
|
|
238
|
+
hideMap: function (database: any, objectFields: MFieldSchema[], uispec?: M3UISpec): HideMap {
|
|
239
|
+
// 构造字段依赖计算的脚本,类似这样:
|
|
240
|
+
// let {name,role,gender,alias,tel,email,citizenship,student} = {"name":"aaa","alias":"bbb","tel":"1"};citizenship=='中国大陆'
|
|
241
|
+
|
|
242
|
+
let l1fields = _.uniq( // 名字要唯一
|
|
243
|
+
objectFields
|
|
244
|
+
.filter(e => !!e.name) // 过滤掉没有名字的字段
|
|
245
|
+
.map(e => _.first(e.name.split("."))) // 对于objectFields里类似student.no/student.state的字段,这里只要拼一个student就可以了
|
|
246
|
+
);
|
|
247
|
+
let showIfScript = `let {${l1fields.join(",")}} = data || {};\nlet hide = {`;
|
|
248
|
+
for (let f of objectFields) {
|
|
249
|
+
if (f.showIf) {
|
|
250
|
+
showIfScript += `'${f.name}': !(${f.showIf}),`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (let s of uispec?.segments ?? []) {
|
|
254
|
+
if (s.showIf) {
|
|
255
|
+
showIfScript += `"segment:${s.label}": !(${s.showIf}),`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
showIfScript += "}\n";
|
|
259
|
+
|
|
260
|
+
// 如果一个segment里的所有字段都隐藏,segment就要隐藏
|
|
261
|
+
for (let s of uispec?.segments ?? []) {
|
|
262
|
+
const cond = s.fields.map(f => "(hide['" + f + "'])").join(" && ");
|
|
263
|
+
showIfScript += `if(${cond}) { hide["segment:${s.label}"] = true }\n`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
showIfScript += "return hide;\n";
|
|
267
|
+
|
|
268
|
+
// eslint-disable-next-line no-new-func
|
|
269
|
+
try {
|
|
270
|
+
const fn = Object.keys(SchemaFunc)
|
|
271
|
+
const fv = Object.values(SchemaFunc)
|
|
272
|
+
let hideMap = new Function("_", "data", ...fn, showIfScript)(_, database, ...fv);
|
|
273
|
+
return hideMap;
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.error("Calc hideMap failed: " + e.message, "function(_,data){" + showIfScript + "}", database);
|
|
276
|
+
return {};
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 计算 showIf 的值
|
|
282
|
+
* @returns Boolean
|
|
283
|
+
* @param database
|
|
284
|
+
* @param objectFields
|
|
285
|
+
*/
|
|
286
|
+
isShow: function (database: any, objectFields: MFieldSchema[], showIfExpr: string) {
|
|
287
|
+
if (!showIfExpr) return true
|
|
288
|
+
let l1fields = _.uniq(
|
|
289
|
+
objectFields
|
|
290
|
+
.filter(e => !!e.name)
|
|
291
|
+
.map(e => _.first(e.name.split(".")))
|
|
292
|
+
);
|
|
293
|
+
let showIfScript = `let {${l1fields.join(",")}} = data || {};\n return ${showIfExpr}`;
|
|
294
|
+
try {
|
|
295
|
+
const fn = Object.keys(SchemaFunc)
|
|
296
|
+
const fv = Object.values(SchemaFunc)
|
|
297
|
+
let res = new Function("_", "data", ...fn, showIfScript)(_, database, ...fv);
|
|
298
|
+
return res;
|
|
299
|
+
} catch (e) {
|
|
300
|
+
console.error("Calc isShow failed: " + e.message, "function(_,data){" + showIfScript + "}", database);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
scoreOf: function(f:MFieldSchema, database:any) {
|
|
306
|
+
const v = _.get(database, f.name);
|
|
307
|
+
if(f.type == "enum"){
|
|
308
|
+
return _.toNumber(MUtil.standardFields(f.enumFields)?.find(e=>e.value == v)?.score);
|
|
309
|
+
} else if(f.type == "set"){
|
|
310
|
+
let score = 0;
|
|
311
|
+
const opts = MUtil.standardFields(f.setFields);
|
|
312
|
+
for(let s of opts){
|
|
313
|
+
if(_.find(opts, {value:v})) {
|
|
314
|
+
score += _.toNumber(s.score);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return score;
|
|
318
|
+
} else {
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 查找fs依赖(通过showIf)的所有字段
|
|
325
|
+
* @param fs
|
|
326
|
+
* @param all
|
|
327
|
+
* @returns fs依赖的所有字段,包含fs。这个数组中下标小的元素,依赖下标大的元素
|
|
328
|
+
*/
|
|
329
|
+
dependency: function (fs:MFieldSchema[], all:MFieldSchema[]) {
|
|
330
|
+
// 先建个索引
|
|
331
|
+
const allFieldsIdx = _.keyBy(all, "name");
|
|
332
|
+
|
|
333
|
+
// 构造未被依赖的集合
|
|
334
|
+
let ndep = _.keyBy(all, "name"); // 全体
|
|
335
|
+
ndep = _.omit(ndep, fs.map(f=>f.name)); // 去掉fs
|
|
336
|
+
|
|
337
|
+
// 构造被依赖的集合
|
|
338
|
+
let dep = new Map<string, MFieldSchema>();
|
|
339
|
+
fs.forEach(f => dep[f.name] = f);
|
|
340
|
+
|
|
341
|
+
// 循环从ndep里把被依赖的字段放到dep中,直到dep不再增加
|
|
342
|
+
// 算法有点粗暴,将就用吧
|
|
343
|
+
for(let i = 0; i < all.length; i ++) { // 轮数最多all.length,防止卡死
|
|
344
|
+
let newDepNames = []
|
|
345
|
+
for(let dn in dep) {
|
|
346
|
+
const i = _.intersection( dep[dn].showIf?.split(/[^a-zA-Z0-9_$]/), Object.keys(ndep) );
|
|
347
|
+
newDepNames = newDepNames.concat(i);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let prevSize = Object.keys(dep).length;
|
|
351
|
+
for(let n of newDepNames){
|
|
352
|
+
dep[n] = allFieldsIdx[n];
|
|
353
|
+
delete ndep[n];
|
|
354
|
+
}
|
|
355
|
+
let afterSize = Object.keys(dep).length;
|
|
356
|
+
if(prevSize == afterSize) {
|
|
357
|
+
break; // 如果找不到更多依赖,就可以结束了
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return Object.values(dep);
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 用moment格式化
|
|
365
|
+
* @param s 原始数据,参见moment
|
|
366
|
+
* @param srcFormat 原始数据的格式,默认x(时间戳)
|
|
367
|
+
* @param dstFormat 转换成的数据格式,默认YYYY年MM月DD日
|
|
368
|
+
* @param fallback 如果原始数据是nil,返回fallback
|
|
369
|
+
*/
|
|
370
|
+
momentFormat: function (s: moment.MomentInput, srcFormat: string = "x", dstFormat: string = "YYYY年MM月DD日", fallback: string = "不详") {
|
|
371
|
+
if (_.isNil(s)) {
|
|
372
|
+
return fallback;
|
|
373
|
+
}
|
|
374
|
+
return moment(s, srcFormat).format(dstFormat)
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 将标准的json schema转换成m3的schema
|
|
379
|
+
* @param e json schema
|
|
380
|
+
* @param base 如果填了一个对象,转换出的属性会放在这个对象上
|
|
381
|
+
* @param template 如果有子结构,创建子结构的模板
|
|
382
|
+
*/
|
|
383
|
+
jsonSchema2MFieldSchema: function (e: JSONSchema6, base: MFieldSchema = { name: "", type: "string" }, template: Partial<MFieldSchema> = {}): MFieldSchema {
|
|
384
|
+
switch (e.type) {
|
|
385
|
+
case 'string':
|
|
386
|
+
if (e.enum) {
|
|
387
|
+
base.type = "enum";
|
|
388
|
+
base.enumFields = base.enumFields ?? e.enum.map(v => ({ value: (v ?? "").toString() }))
|
|
389
|
+
} else {
|
|
390
|
+
base.type = "string"
|
|
391
|
+
base.max = e.maxLength
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
case 'number':
|
|
395
|
+
case 'integer':
|
|
396
|
+
base.type = "int";
|
|
397
|
+
break;
|
|
398
|
+
case 'object':
|
|
399
|
+
base.type = "object"
|
|
400
|
+
base.objectFields = [];
|
|
401
|
+
base.uispec = {
|
|
402
|
+
type: "flowForm",
|
|
403
|
+
layout: MUtil.phoneLike() ? "vertical" : "horizontal",
|
|
404
|
+
comma: ":",
|
|
405
|
+
};
|
|
406
|
+
for (let key in e.properties) {
|
|
407
|
+
let jsmField = e.properties[key];
|
|
408
|
+
let m3Field: MFieldSchema = _.assign({}, template, { name: key, label: key, type: "object" });
|
|
409
|
+
if (!_.isBoolean(jsmField)) {
|
|
410
|
+
m3Field.label = jsmField.title ?? key;
|
|
411
|
+
this.jsonSchema2MFieldSchema(jsmField, m3Field, template);
|
|
412
|
+
base.objectFields.push(m3Field);
|
|
413
|
+
} else {
|
|
414
|
+
m3Field.type = "不支持的json schema:object.properties的value是boolean";
|
|
415
|
+
base.objectFields.push()
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
break;
|
|
419
|
+
case 'array':
|
|
420
|
+
base.type = "array"
|
|
421
|
+
base.arrayMember = { label: "", type: "array" };
|
|
422
|
+
if (_.isArray(e.items)) {
|
|
423
|
+
base.arrayMember.type = "不支持的json schema:array.items是数组";
|
|
424
|
+
} else if (_.isBoolean(e.items)) {
|
|
425
|
+
base.arrayMember.type = "不支持的json schema:array.items是boolean";
|
|
426
|
+
} else if (e.items) {
|
|
427
|
+
this.jsonSchema2MFieldSchema(e.items, base, template);
|
|
428
|
+
} else {
|
|
429
|
+
base.arrayMember.type = "不支持的json schema:array.items是undefined";
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
case 'null':
|
|
433
|
+
case 'any':
|
|
434
|
+
case 'boolean':
|
|
435
|
+
default:
|
|
436
|
+
base.type = "不支持的json schema:array.items是" + e.type;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
return base;
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 修改一个对象里的key
|
|
444
|
+
* @param object 要修改的对象,
|
|
445
|
+
* @param renameTo
|
|
446
|
+
* @param removeNotExistKey 是否在结果里面去除renameTo里不存在的key
|
|
447
|
+
* @returns 返回一个改过key名字的新对象
|
|
448
|
+
*/
|
|
449
|
+
renameKey: function (object: any, renameTo: { [oldKey: string]: string }, removeNotExistKey: boolean = false): any {
|
|
450
|
+
let result: any = {};
|
|
451
|
+
for (let oldKey in object) {
|
|
452
|
+
let newKey = renameTo[oldKey];
|
|
453
|
+
if (!newKey) {
|
|
454
|
+
if (removeNotExistKey) {
|
|
455
|
+
continue;
|
|
456
|
+
} else {
|
|
457
|
+
newKey = oldKey;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
result[newKey] = object[oldKey];
|
|
461
|
+
}
|
|
462
|
+
return result;
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
/** 啥也不干的空回调 */
|
|
466
|
+
doNothing: function () { },
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* 去掉arr中的undefined和null,拼出来一个json path
|
|
470
|
+
*/
|
|
471
|
+
jsonPath: function (...arr) {
|
|
472
|
+
return _.compact(arr).join(".");
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* 获取一个json path的父path。
|
|
477
|
+
* @param path 如果path是空的返回也是空的
|
|
478
|
+
*/
|
|
479
|
+
parentPath: function (path: string): string {
|
|
480
|
+
const split = path.replace(/]/g, '').split(/[.\\[]/);
|
|
481
|
+
split.splice(-1, 1);
|
|
482
|
+
return split.join(".");
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 校验一个schema
|
|
487
|
+
* @param s 要校验的schema
|
|
488
|
+
* @returns 空数组表示校验通过,否则返回校验错误信息
|
|
489
|
+
*/
|
|
490
|
+
validateSchema: function (s: MFieldSchema, parentPath?: string): MValidationFail[] {
|
|
491
|
+
let result: MValidationFail[] = [];
|
|
492
|
+
const error = (msg: string) => result.push({ message: msg, path: MUtil.jsonPath(parentPath, s.name) });
|
|
493
|
+
const checkDup = (a: any[], msgIfFail: string) => {
|
|
494
|
+
if (_.uniq(a).length !== a.length) {
|
|
495
|
+
error(msgIfFail);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const checkVL = (fs: MEnumField[]) => {
|
|
499
|
+
checkDup(fs.map(f => f.value), s.name + "值有重复");
|
|
500
|
+
checkDup(fs.map(f => f.label).filter(l => l), s.name + "文案有重复");
|
|
501
|
+
}
|
|
502
|
+
const stringOnlyEnum = (candidate: string | MEnumField[] | undefined) => {
|
|
503
|
+
const ns = MUtil.standardFields(candidate).find(f => !_.isString(f.value))
|
|
504
|
+
if (ns) {
|
|
505
|
+
error("暂不支持的选项数据类型:" + JSON.stringify(ns) + ": " + (typeof ns))
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (s.type === "decoration") {
|
|
510
|
+
return [];
|
|
511
|
+
} else if (s.type === "enum") {
|
|
512
|
+
checkVL(MUtil.standardFields(s.enumFields))
|
|
513
|
+
} else if (s.type === "set") {
|
|
514
|
+
checkVL(MUtil.standardFields(s.setFields))
|
|
515
|
+
} else if (s.type === "matrix") {
|
|
516
|
+
stringOnlyEnum(s.matrix.x);
|
|
517
|
+
stringOnlyEnum(s.matrix.y);
|
|
518
|
+
} else if (s.type === "object") {
|
|
519
|
+
if (!s.objectFields) {
|
|
520
|
+
error("object类型未定义成员");
|
|
521
|
+
} else {
|
|
522
|
+
checkDup(s.objectFields.filter(f => f.type !== 'decoration').map(f => f.name), "字段名有重复");
|
|
523
|
+
for (let f of s.objectFields) {
|
|
524
|
+
result = _.concat(result, MUtil.validateSchema(f, MUtil.jsonPath(parentPath, s.name)));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (s.uispec?.type === "segmentForm") {
|
|
528
|
+
if (!s.uispec.segments) {
|
|
529
|
+
error("分段未定义");
|
|
530
|
+
} else {
|
|
531
|
+
checkDup(s.uispec.segments.map(f => f.label), "分段文案有重复");
|
|
532
|
+
const fieldsInSegments = _.flatten(s.uispec.segments.map(f => f.fields));
|
|
533
|
+
checkDup(fieldsInSegments, "分段字段有重复");
|
|
534
|
+
const fields = s.objectFields.map(f => f.name);
|
|
535
|
+
checkDup(fields, "字段名有重复");
|
|
536
|
+
const notInSegment = _.difference(fields, fieldsInSegments);
|
|
537
|
+
if (notInSegment.length > 0) {
|
|
538
|
+
error("字段" + notInSegment.join(",") + "未体现在分段中");
|
|
539
|
+
}
|
|
540
|
+
const fieldNotExist = _.difference(fieldsInSegments, fields);
|
|
541
|
+
if (fieldNotExist.length > 0) {
|
|
542
|
+
error("分段字段名无效:" + notInSegment.join(","));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return result;
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* 参考https://stackoverflow.com/questions/3362471/how-can-i-call-a-javascript-constructor-using-call-or-apply
|
|
553
|
+
* 示例: var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);
|
|
554
|
+
* @param constructor
|
|
555
|
+
* @param argArray
|
|
556
|
+
*/
|
|
557
|
+
applyToConstructor: function (constructor, argArray) {
|
|
558
|
+
var args = [null, ...argArray];
|
|
559
|
+
var factoryFunction = constructor.bind.apply(constructor, args);
|
|
560
|
+
return new factoryFunction();
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 执行表达式或者回调函数
|
|
565
|
+
* @param exprOrFunction 表达式或者回调函数
|
|
566
|
+
* @param paramName 参数名字
|
|
567
|
+
* @param paramValue 参数值
|
|
568
|
+
* @param fallback 如果exprOrFunction无效,返回fallback
|
|
569
|
+
* @param _this this指针
|
|
570
|
+
*/
|
|
571
|
+
evalExprOrFunction: function (exprOrFunction, paramName: string[], paramValue: any[], fallback: any = undefined, _this: any = undefined) {
|
|
572
|
+
if (_.isFunction(exprOrFunction)) {
|
|
573
|
+
return exprOrFunction.apply(_this, paramValue);
|
|
574
|
+
} else if (_.isString(exprOrFunction)) {
|
|
575
|
+
//return new Function("_", "value", "parent", "return (" + labelExpr + ");")(_, value, parent);
|
|
576
|
+
const func = MUtil.applyToConstructor(Function, [...paramName, "return (" + exprOrFunction + ")"]);
|
|
577
|
+
return func.apply(_this, paramValue);
|
|
578
|
+
} else {
|
|
579
|
+
return fallback;
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* 读取url参数,转换成map
|
|
585
|
+
* @param hashPrefix nil表示不读取hash里的参数,字符串表示读取hash里的参数,并加上这个前缀返回。默认是返回,但前缀是空字符串
|
|
586
|
+
*/
|
|
587
|
+
getQuery: function (hashPrefix: string = ""): any {
|
|
588
|
+
const arrs = [...window.location.search.split(/[?&]/), ...window.location.hash.split(/[#?&]/)];
|
|
589
|
+
const result = {};
|
|
590
|
+
arrs.forEach((item) => {
|
|
591
|
+
const kv = item.split(/=/);
|
|
592
|
+
if (kv.length <= 2 && kv[0]) {
|
|
593
|
+
result[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
return result;
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* 动态创建一个CSS样式
|
|
601
|
+
* @param css css内容,会被写进style标签里
|
|
602
|
+
* @param id <style>的id,可以没有。如果已经存在,会被覆盖掉。
|
|
603
|
+
*/
|
|
604
|
+
setCss: function (css: string, id?: string) {
|
|
605
|
+
const s = document.getElementById(id) as HTMLStyleElement ?? document.createElement('style');
|
|
606
|
+
s.id = id;
|
|
607
|
+
s.type = 'text/css';
|
|
608
|
+
s.innerHTML = css;
|
|
609
|
+
document.getElementsByTagName('head')[0].appendChild(s);
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* escape数值
|
|
614
|
+
* @param str - 字符串
|
|
615
|
+
* @returns 会带上引号,类似"abc",str=nil时会返回""
|
|
616
|
+
*/
|
|
617
|
+
escapeCsv: function (str?: string) {
|
|
618
|
+
if (!_.isString(str)) {
|
|
619
|
+
str = _.toString(str);
|
|
620
|
+
}
|
|
621
|
+
return '"' + str.replace(/["]/g, '""') + '"';
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* 判断两个值是否相同
|
|
626
|
+
* @param v1 一个值
|
|
627
|
+
* @param v2 另一个值
|
|
628
|
+
* @param tolerate true用==判断,false用===判断
|
|
629
|
+
*/
|
|
630
|
+
isEquals: function(v1, v2, tolerate: boolean) {
|
|
631
|
+
if(tolerate) {
|
|
632
|
+
return v1 == v2;
|
|
633
|
+
} else {
|
|
634
|
+
return v1 === v2;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** 压缩数组,如果只有一个元素时,它是数据本身,多于一个元素时才是数组 */
|
|
640
|
+
export type CompactArrayType<T> = T | T[];
|
|
641
|
+
|
|
642
|
+
export let CompactArray = {
|
|
643
|
+
indexOf: function <T>(ca?: CompactArrayType<T> | null, d?: T | null): -1 | number {
|
|
644
|
+
if (_.isNil(d) || _.isNil(ca)) {
|
|
645
|
+
return -1;
|
|
646
|
+
}
|
|
647
|
+
if (_.isArray(ca)) {
|
|
648
|
+
return ca.indexOf(d);
|
|
649
|
+
} else {
|
|
650
|
+
return ca === d ? 0 : -1
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|