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.
Files changed (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/m3.css +310 -0
  4. package/dist/m3.js +1 -0
  5. package/es/m3.css +310 -0
  6. package/es/m3.js +20919 -0
  7. package/lib/m3.css +310 -0
  8. package/lib/m3.js +20959 -0
  9. package/package.json +132 -0
  10. package/src/.DS_Store +0 -0
  11. package/src/framework/Ajax.ts +96 -0
  12. package/src/framework/Assembly.tsx +165 -0
  13. package/src/framework/Init.tsx +196 -0
  14. package/src/framework/M3.tsx +94 -0
  15. package/src/framework/MContext.ts +15 -0
  16. package/src/framework/MFieldViewer.tsx +32 -0
  17. package/src/framework/MUtil.tsx +653 -0
  18. package/src/framework/MViewer.less +128 -0
  19. package/src/framework/MViewer.tsx +180 -0
  20. package/src/framework/MViewerDebug.tsx +95 -0
  21. package/src/framework/Persistant.ts +90 -0
  22. package/src/framework/Schema.ts +386 -0
  23. package/src/framework/SchemaFunc.ts +30 -0
  24. package/src/framework/Validator.ts +160 -0
  25. package/src/framework/editorMap.ts +109 -0
  26. package/src/index.ts +33 -0
  27. package/src/types/MArrayType.ts +73 -0
  28. package/src/types/MCascadeType.ts +35 -0
  29. package/src/types/MCnAddressType.ts +54 -0
  30. package/src/types/MDateRangeType.ts +52 -0
  31. package/src/types/MDateTimeType.ts +53 -0
  32. package/src/types/MDecorationType.ts +6 -0
  33. package/src/types/MEnumType.ts +65 -0
  34. package/src/types/MExperienceType.ts +81 -0
  35. package/src/types/MFloatType.ts +10 -0
  36. package/src/types/MGB2260Type.ts +56 -0
  37. package/src/types/MIntDiffType.ts +6 -0
  38. package/src/types/MIntType.ts +44 -0
  39. package/src/types/MKvSetType.ts +50 -0
  40. package/src/types/MMatrixType.ts +52 -0
  41. package/src/types/MObjectType.ts +89 -0
  42. package/src/types/MSetType.ts +220 -0
  43. package/src/types/MStringType.ts +27 -0
  44. package/src/types/MTelType.ts +14 -0
  45. package/src/types/MType.ts +77 -0
  46. package/src/types/MVLPairType.ts +35 -0
  47. package/src/types/gb2260.json +1 -0
  48. package/src/ui/BaseViewer.tsx +110 -0
  49. package/src/ui/editor/.DS_Store +0 -0
  50. package/src/ui/editor/basic/.DS_Store +0 -0
  51. package/src/ui/editor/basic/ACascadePicker.tsx +114 -0
  52. package/src/ui/editor/basic/ACheckBox.tsx +104 -0
  53. package/src/ui/editor/basic/ADatetimePicker.tsx +76 -0
  54. package/src/ui/editor/basic/AGB2260.tsx +52 -0
  55. package/src/ui/editor/basic/AInputBox.tsx +59 -0
  56. package/src/ui/editor/basic/AIntBox.tsx +39 -0
  57. package/src/ui/editor/basic/AKvSet.less +9 -0
  58. package/src/ui/editor/basic/AKvSet.tsx +90 -0
  59. package/src/ui/editor/basic/ARadio.tsx +86 -0
  60. package/src/ui/editor/basic/ARangePicker.tsx +129 -0
  61. package/src/ui/editor/basic/ARate.less +8 -0
  62. package/src/ui/editor/basic/ARate.tsx +37 -0
  63. package/src/ui/editor/basic/ARemoteSelector.tsx +116 -0
  64. package/src/ui/editor/basic/ASelector.tsx +88 -0
  65. package/src/ui/editor/basic/ASetSelector.tsx +65 -0
  66. package/src/ui/editor/basic/ASpecInputBox.tsx +20 -0
  67. package/src/ui/editor/basic/ATreeSelect.tsx +41 -0
  68. package/src/ui/editor/basic/AUpload.tsx +119 -0
  69. package/src/ui/editor/basic/NPS.less +21 -0
  70. package/src/ui/editor/basic/NPS.tsx +47 -0
  71. package/src/ui/editor/complex/AArray.less +10 -0
  72. package/src/ui/editor/complex/AArray.tsx +104 -0
  73. package/src/ui/editor/complex/AArrayGrid.tsx +115 -0
  74. package/src/ui/editor/complex/ACnAddress.less +15 -0
  75. package/src/ui/editor/complex/ACnAddress.tsx +61 -0
  76. package/src/ui/editor/complex/ADialogForm.tsx +45 -0
  77. package/src/ui/editor/complex/AExperience.tsx +85 -0
  78. package/src/ui/editor/complex/AForm.less +35 -0
  79. package/src/ui/editor/complex/AForm.tsx +340 -0
  80. package/src/ui/editor/complex/AIntDiff.tsx +77 -0
  81. package/src/ui/editor/complex/AMatrix.less +18 -0
  82. package/src/ui/editor/complex/AMatrix.tsx +242 -0
  83. package/src/ui/editor/complex/ATable.less +4 -0
  84. package/src/ui/editor/complex/ATable.tsx +33 -0
  85. package/src/ui/editor/complex/JsonEditor.tsx +37 -0
  86. package/src/ui/readable/A.tsx +33 -0
  87. package/src/ui/readable/ArrayViewer.tsx +46 -0
  88. package/src/ui/readable/DecorationViewer.tsx +76 -0
  89. package/src/ui/readable/DivViewer.tsx +11 -0
  90. package/src/ui/widget/Collapsible.tsx +156 -0
  91. package/src/ui/widget/Segment.less +39 -0
  92. package/src/ui/widget/Segment.tsx +40 -0
  93. package/src/ui/widget/SegmentEditSwitch.tsx +46 -0
  94. package/src/ui/widget/SelectBox.tsx +43 -0
  95. package/src/ui/widget/UnderlineInputBox.less +47 -0
  96. package/src/ui/widget/UnderlineInputBox.tsx +10 -0
  97. package/types/framework/Ajax.d.ts +5 -0
  98. package/types/framework/Assembly.d.ts +59 -0
  99. package/types/framework/Init.d.ts +4 -0
  100. package/types/framework/M3.d.ts +6 -0
  101. package/types/framework/MContext.d.ts +11 -0
  102. package/types/framework/MFieldViewer.d.ts +8 -0
  103. package/types/framework/MUtil.d.ts +180 -0
  104. package/types/framework/MViewer.d.ts +75 -0
  105. package/types/framework/MViewerDebug.d.ts +11 -0
  106. package/types/framework/Persistant.d.ts +17 -0
  107. package/types/framework/Schema.d.ts +306 -0
  108. package/types/framework/SchemaFunc.d.ts +14 -0
  109. package/types/framework/Validator.d.ts +53 -0
  110. package/types/framework/editorMap.d.ts +107 -0
  111. package/types/index.d.ts +21 -0
  112. package/types/types/MArrayType.d.ts +2 -0
  113. package/types/types/MCascadeType.d.ts +11 -0
  114. package/types/types/MCnAddressType.d.ts +2 -0
  115. package/types/types/MDateRangeType.d.ts +7 -0
  116. package/types/types/MDateTimeType.d.ts +11 -0
  117. package/types/types/MDecorationType.d.ts +7 -0
  118. package/types/types/MEnumType.d.ts +2 -0
  119. package/types/types/MExperienceType.d.ts +5 -0
  120. package/types/types/MFloatType.d.ts +2 -0
  121. package/types/types/MGB2260Type.d.ts +9 -0
  122. package/types/types/MIntDiffType.d.ts +2 -0
  123. package/types/types/MIntType.d.ts +2 -0
  124. package/types/types/MKvSetType.d.ts +11 -0
  125. package/types/types/MMatrixType.d.ts +5 -0
  126. package/types/types/MObjectType.d.ts +11 -0
  127. package/types/types/MSetType.d.ts +7 -0
  128. package/types/types/MStringType.d.ts +2 -0
  129. package/types/types/MTelType.d.ts +4 -0
  130. package/types/types/MType.d.ts +46 -0
  131. package/types/types/MVLPairType.d.ts +5 -0
  132. package/types/ui/BaseViewer.d.ts +45 -0
  133. package/types/ui/editor/basic/ACascadePicker.d.ts +11 -0
  134. package/types/ui/editor/basic/ACheckBox.d.ts +17 -0
  135. package/types/ui/editor/basic/ADatetimePicker.d.ts +15 -0
  136. package/types/ui/editor/basic/AGB2260.d.ts +10 -0
  137. package/types/ui/editor/basic/AInputBox.d.ts +8 -0
  138. package/types/ui/editor/basic/AIntBox.d.ts +9 -0
  139. package/types/ui/editor/basic/AKvSet.d.ts +19 -0
  140. package/types/ui/editor/basic/ARadio.d.ts +14 -0
  141. package/types/ui/editor/basic/ARangePicker.d.ts +30 -0
  142. package/types/ui/editor/basic/ARate.d.ts +13 -0
  143. package/types/ui/editor/basic/ARemoteSelector.d.ts +15 -0
  144. package/types/ui/editor/basic/ASelector.d.ts +14 -0
  145. package/types/ui/editor/basic/ASetSelector.d.ts +10 -0
  146. package/types/ui/editor/basic/ASpecInputBox.d.ts +9 -0
  147. package/types/ui/editor/basic/ATreeSelect.d.ts +18 -0
  148. package/types/ui/editor/basic/AUpload.d.ts +33 -0
  149. package/types/ui/editor/basic/NPS.d.ts +13 -0
  150. package/types/ui/editor/complex/AArray.d.ts +11 -0
  151. package/types/ui/editor/complex/AArrayGrid.d.ts +13 -0
  152. package/types/ui/editor/complex/ACnAddress.d.ts +10 -0
  153. package/types/ui/editor/complex/ADialogForm.d.ts +11 -0
  154. package/types/ui/editor/complex/AExperience.d.ts +16 -0
  155. package/types/ui/editor/complex/AForm.d.ts +46 -0
  156. package/types/ui/editor/complex/AIntDiff.d.ts +14 -0
  157. package/types/ui/editor/complex/AMatrix.d.ts +48 -0
  158. package/types/ui/editor/complex/ATable.d.ts +9 -0
  159. package/types/ui/editor/complex/JsonEditor.d.ts +9 -0
  160. package/types/ui/readable/A.d.ts +5 -0
  161. package/types/ui/readable/ArrayViewer.d.ts +5 -0
  162. package/types/ui/readable/DecorationViewer.d.ts +3 -0
  163. package/types/ui/readable/DivViewer.d.ts +5 -0
  164. package/types/ui/widget/Collapsible.d.ts +46 -0
  165. package/types/ui/widget/Segment.d.ts +18 -0
  166. package/types/ui/widget/SegmentEditSwitch.d.ts +20 -0
  167. package/types/ui/widget/SelectBox.d.ts +13 -0
  168. 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
+ }