@ctzy-web-client/data-model 1.0.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.
@@ -0,0 +1,327 @@
1
+ import get from 'lodash/get';
2
+ import DataModelAdapter from './DataModelAdapter';
3
+ import DataColumn from './DataColumn';
4
+ import { inject } from '@ctzy-web-client/ioc-annotations';
5
+ import { EventDispatcher } from '@ctzy-web-client/support';
6
+ import { InstantiationService } from '@ctzy-web-client/ioc';
7
+ import { clone } from 'lodash';
8
+ import { cloneDeep } from 'lodash';
9
+ import { BehaviorSubject } from 'rxjs';
10
+
11
+ export default class DataModel extends EventDispatcher {
12
+ /**
13
+ * @type {String} 表格名称
14
+ */
15
+ name = '';
16
+
17
+ /**
18
+ * @type {String} 表格标题
19
+ */
20
+ title = '';
21
+
22
+ /**
23
+ * @type {String} 主键字段名
24
+ */
25
+ primaryKey = 'id';
26
+
27
+ /**
28
+ * @type {DataModelAdapter} 数据适配器
29
+ */
30
+ @inject(DataModelAdapter)
31
+ adapter = null;
32
+
33
+ @inject(InstantiationService)
34
+ instantiationService = null;
35
+
36
+ defaultParams = {};
37
+
38
+ /**
39
+ * @type {Function} 映射目标
40
+ */
41
+ model = null;
42
+
43
+ /**
44
+ * @type {Boolean} 是否准备就绪
45
+ */
46
+ ready = false;
47
+
48
+ _options = null;
49
+
50
+ extendField = '';
51
+
52
+ extendFieldInfos = [];
53
+
54
+ /**
55
+ * @type {DataColumn[]} 字段列
56
+ */
57
+ _columns = [];
58
+
59
+ _displayColumns = [];
60
+
61
+ _columnSubscriptionMap = new Map();
62
+
63
+ _displayColumnsSubject = new BehaviorSubject([]);
64
+
65
+ /**
66
+ * @param {Object} options 配置
67
+ * @param {String} options.name 表格名称
68
+ * @param {String} options.title 表格标题
69
+ * @param {String} options.primaryKey 主键字段名
70
+ */
71
+ constructor(options) {
72
+ super();
73
+ this.setOptions(options);
74
+ }
75
+
76
+ setOptions(options) {
77
+ this._options = options;
78
+ this.name = options.name;
79
+ this.title = options.title;
80
+ this.primaryKey = options.primaryKey || this.primaryKey;
81
+ this.extendField = options.extendField;
82
+ this.model = options.model;
83
+ }
84
+
85
+ /**
86
+ * @protected
87
+ */
88
+ init() {
89
+ if (typeof this._options?.adapter === 'function') {
90
+ this.adapter = this.instantiationService.createInstance(
91
+ this._options.adapter
92
+ );
93
+ }
94
+ }
95
+
96
+ _format(data, propName = 'name') {
97
+ const columns = this.getColumns();
98
+
99
+ const _data = {};
100
+ for (let column of columns.filter((column) => !column.isExtend)) {
101
+ const value = data?.[column[propName]];
102
+ _data[column.attrName] =
103
+ value === '' || value == null ? column.default : value;
104
+ }
105
+
106
+ if (this.extendField) {
107
+ let extendObject = {};
108
+ for (let column of columns.filter((column) => column.isExtend)) {
109
+ const value = get(data, column.fullAttrName);
110
+
111
+ extendObject[column.attrName] =
112
+ value === '' || value == null ? column.default : value;
113
+ }
114
+
115
+ _data[this.extendField] = extendObject;
116
+ }
117
+
118
+ const extendProperties = (source, target, predicate) => {
119
+ let keys = Object.keys(source);
120
+
121
+ keys = keys.filter(
122
+ (key) =>
123
+ !columns.filter(predicate).find((column) => column.attrName === key)
124
+ );
125
+
126
+ for (const key of keys) {
127
+ target[key] = source[key];
128
+ }
129
+ };
130
+
131
+ extendProperties(data, _data, (column) => !column.isExtend);
132
+
133
+ if (this.extendField && data[this.extendField]) {
134
+ extendProperties(
135
+ data[this.extendField],
136
+ _data[this.extendField],
137
+ (column) => column.isExtend
138
+ );
139
+ }
140
+
141
+ return _data;
142
+ }
143
+
144
+ getDisplayColumnsSubject() {
145
+ return this._displayColumnsSubject;
146
+ }
147
+
148
+ setParam(key, value) {
149
+ this.defaultParams[key] = value;
150
+ }
151
+
152
+ getParam(key) {
153
+ return this.defaultParams[key];
154
+ }
155
+
156
+ hasParam(key) {
157
+ return Reflect.has(this.defaultParams, key);
158
+ }
159
+
160
+ removeParam(key) {
161
+ Reflect.deleteProperty(this.defaultParams, key);
162
+ }
163
+
164
+ addParams(params = {}) {
165
+ this.defaultParams = { ...this.defaultParams, ...params };
166
+ }
167
+
168
+ setParams(params = {}) {
169
+ this.defaultParams = params;
170
+ }
171
+
172
+ clearParam() {
173
+ this.defaultParams = {};
174
+ }
175
+
176
+ formatData(data) {
177
+ return this._format(data);
178
+ }
179
+
180
+ formatExtendFieldInfo(extendFieldInfo) {
181
+ return {
182
+ title: extendFieldInfo.title,
183
+ name: extendFieldInfo.name,
184
+ default: extendFieldInfo.defaultValue,
185
+ attrName: extendFieldInfo.name,
186
+ isExtend: !extendFieldInfo.type,
187
+ required: !!extendFieldInfo.isRequired,
188
+ visible: !!extendFieldInfo.isVisible,
189
+ component: extendFieldInfo.component,
190
+ componentProps: { ...extendFieldInfo.extendInfo },
191
+ };
192
+ }
193
+
194
+ mergeTableColumn(formatedExtendFieldInfo, modelFieldInfo) {
195
+ return {
196
+ ...modelFieldInfo,
197
+ ...formatedExtendFieldInfo,
198
+ fullAttrName: `${this.extendField}.${formatedExtendFieldInfo.name}`,
199
+ componentProps: {
200
+ ...modelFieldInfo?.componentProps,
201
+ ...formatedExtendFieldInfo.extendInfo,
202
+ },
203
+ };
204
+ }
205
+
206
+ mergeTableColumns(extendFieldInfos, modelFieldInfos = []) {
207
+ return extendFieldInfos.map((extendFieldInfo) => {
208
+ const modelFieldInfo = modelFieldInfos.find(
209
+ (field) => field.name === extendFieldInfo.name
210
+ );
211
+
212
+ const formatedExtendFieldInfo =
213
+ this.formatExtendFieldInfo(extendFieldInfo);
214
+
215
+ return this.mergeTableColumn(formatedExtendFieldInfo, modelFieldInfo);
216
+ });
217
+ }
218
+
219
+ /**
220
+ *
221
+ * @returns {DataColumn[]}
222
+ */
223
+ getColumns() {
224
+ return this._columns.slice();
225
+ }
226
+
227
+ setColumns(columns) {
228
+ this._columnSubscriptionMap.clear();
229
+
230
+ this._columns = [];
231
+ for (let column of columns) {
232
+ this.addColumn(column);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * @prootected
238
+ */
239
+ sortColumns(columns) {
240
+ throw new Error('暂未实现。');
241
+ }
242
+
243
+ /**
244
+ *
245
+ * @param {DataColumn} column
246
+ */
247
+ addColumn(column) {
248
+ if (!this.getColumn(column.attrName)) {
249
+ this._columns = this.sortColumns(this.getColumns().concat(column));
250
+
251
+ const subscription = column.getVisibleSubject().subscribe({
252
+ next: (visible) => {
253
+ if (visible) {
254
+ this.addDisplayColumn(column);
255
+ return;
256
+ }
257
+
258
+ this.removeDisplayColumn(column);
259
+ },
260
+ error: () => {
261
+ this.removeDisplayColumn(column);
262
+ },
263
+ complete: () => {
264
+ this.removeDisplayColumn(column);
265
+ },
266
+ });
267
+
268
+ this._columnSubscriptionMap.set(column.attrName, subscription);
269
+ } else {
270
+ throw new Error('无法添加两个字段名称一样的字段:' + column.attrName);
271
+ }
272
+ }
273
+
274
+ getColumn(columnName) {
275
+ return (
276
+ this._columns.find((column) => column.attrName === columnName) ?? null
277
+ );
278
+ }
279
+
280
+ getDisplayColumns() {
281
+ return this._displayColumns.slice();
282
+ }
283
+
284
+ setDisplayColumns(displayColumns) {
285
+ this._displayColumns = displayColumns;
286
+ this.getDisplayColumnsSubject().next(this.getDisplayColumns());
287
+ }
288
+
289
+ hasDisplayColumn(column) {
290
+ return !!this._displayColumns.find(
291
+ (item) => item.attrName === column.attrName
292
+ );
293
+ }
294
+
295
+ addDisplayColumn(column) {
296
+ if (!this.getColumn(column.attrName) || this.hasDisplayColumn(column)) {
297
+ return;
298
+ }
299
+
300
+ this.setDisplayColumns(this._displayColumns.concat(column));
301
+ }
302
+
303
+ removeDisplayColumn(column) {
304
+ if (!this.hasDisplayColumn(column)) {
305
+ return;
306
+ }
307
+
308
+ this.setDisplayColumns(
309
+ this._displayColumns.filter((item) => item.attrName !== column.attrName)
310
+ );
311
+ }
312
+
313
+ /**
314
+ * 克隆
315
+ * @returns
316
+ */
317
+ clone() {
318
+ const cloned = clone(this);
319
+
320
+ cloned.adapter = this.adapter;
321
+ cloned.instantiationService = this.instantiationService;
322
+ cloned.defaultParams = cloneDeep(this.defaultParams);
323
+ cloned._columns = cloneDeep(this._columns);
324
+
325
+ return cloned;
326
+ }
327
+ }
@@ -0,0 +1,241 @@
1
+ import { value, inject } from '@ctzy-web-client/ioc-annotations';
2
+ import { HttpRequest } from '@ctzy-web-client/support';
3
+
4
+ /**
5
+ * 数据模型适配器
6
+ * 主要是定义数据请求的规范
7
+ * 接口地址模板占位符有: ${AppName}:应用名称 ${DataModelName}:数据模型名称 ${RecID}:当前记录主键字段值
8
+ */
9
+ export default class DataModelAdapter {
10
+ /**
11
+ * @type {HttpRequest} 请求
12
+ */
13
+ @inject(HttpRequest)
14
+ httpRequest = null;
15
+
16
+ /**
17
+ * @type {String} 应用名称
18
+ */
19
+ @value('AppName')
20
+ appName = '';
21
+
22
+ /**
23
+ * @param {String} 加载数据列表 默认:POST
24
+ */
25
+ loadDataListItfMethod = 'POST';
26
+
27
+ /**
28
+ * @param {String} 数据列表删除接口的请求方法 默认:POST
29
+ */
30
+ deleteItfMethod = 'POST';
31
+
32
+ /**
33
+ * @param {String} 根据记录主键值获取详情数据接口 默认:POST
34
+ */
35
+ loadDataByRecIdMethod = 'POST';
36
+
37
+ /**
38
+ * @type {string} 保存数据请求方式 默认:POST
39
+ */
40
+ saveItfMethod = 'POST';
41
+
42
+ /**
43
+ * @type {string} 修改数据请求方式 默认:POST
44
+ */
45
+ updateItfMethod = 'POST';
46
+
47
+ /**
48
+ * 加载扩展字段请求方法 默认:POST
49
+ * @memberof DataModelAdapter
50
+ */
51
+ extendFieldItfMethod = 'POST';
52
+
53
+ /**
54
+ * @param {String} 数据接口地址
55
+ */
56
+ loadDataListItfUrl = '/${DataModelName}/page';
57
+
58
+ /**
59
+ * @type {string} 保存接口
60
+ */
61
+ saveItfUrl = '${DataModelName}/save';
62
+
63
+ /**
64
+ * @type {string} 更新接口
65
+ */
66
+ updateItfUrl = '${DataModelName}/update';
67
+
68
+ /**
69
+ * @param {String} 根据记录主键值获取详情数据接口
70
+ */
71
+ loadDataByRecIdItfUrl = '/${DataModelName}/find';
72
+
73
+ /**
74
+ * @param {string} 删除数据
75
+ */
76
+ deleteItfUrl = '/${DataModelName}/delete';
77
+
78
+ /**
79
+ * 加载扩展字段接口
80
+ * @memberof DataModelAdapter
81
+ */
82
+ extendFieldItfUrl = '/sceneField/default/page';
83
+
84
+ /**
85
+ * @param {String} 提交的方法类型 默认:POST
86
+ */
87
+ submitMethod = 'POST';
88
+
89
+ /**
90
+ * @param {String} 翻页页码参数名称 默认:pageNo
91
+ */
92
+ pagerNumParamName = 'pageNo';
93
+
94
+ /**
95
+ * @param {String} 每页记录条数参数名称 默认:pageSize
96
+ */
97
+ recCountParamName = 'pageSize';
98
+
99
+ /**
100
+ * @param {String} 请求返回数据节点的名称 默认:data
101
+ */
102
+ dataNodeName = 'data';
103
+
104
+ /**
105
+ * @param {String} 请求返回总记录条数的节点名称 默认:totalRecCount
106
+ */
107
+ totalRecCountNodeName = 'totalRecCount';
108
+
109
+ /**
110
+ * 格式化url
111
+ * @protected
112
+ * @param {string} url
113
+ * @param {object} context
114
+ * @returns {string}
115
+ */
116
+ formatUrl(url, context) {
117
+ return url.replace(/(\$\{[\S]+?\})/g, (name) => {
118
+ if (name == '${AppName}') {
119
+ return context.appName;
120
+ } else if (name == '${DataModelName}') {
121
+ return context.dataModelName;
122
+ }
123
+ return name;
124
+ });
125
+ }
126
+
127
+ /**
128
+ * @protected
129
+ * @param {string} url
130
+ * @param {string} method
131
+ * @param {object} params
132
+ */
133
+ async sendRequest(url, method, params) {
134
+ return this.httpRequest[method.toLowerCase()](url, params);
135
+ }
136
+
137
+ /**
138
+ * @private
139
+ * @param {object} context
140
+ * @param {string} url
141
+ * @param {string} method
142
+ * @param {object} params
143
+ */
144
+ async _sendRequest(context, url, method, params) {
145
+ const _context = { appName: this.appName, dataModelName: context.name };
146
+
147
+ url = this.formatUrl(url, _context);
148
+
149
+ return this.sendRequest(url, method, params);
150
+ }
151
+
152
+ getExtendsFieldHandle(context, params) {
153
+ return this._sendRequest(
154
+ context,
155
+ this.extendFieldItfUrl,
156
+ this.extendFieldItfMethod,
157
+ params
158
+ );
159
+ }
160
+
161
+ /**
162
+ * 数据加载数据列表
163
+ * @param {Object} context 发起请求的数据模型上下文
164
+ * @param {Object} params 请求参数
165
+ */
166
+ async loadDataListHandle(context, params) {
167
+ return this._sendRequest(
168
+ context,
169
+ this.loadDataListItfUrl,
170
+ this.loadDataListItfMethod,
171
+ params
172
+ );
173
+ }
174
+
175
+ /**
176
+ * 根据主键字段id获取数据详情
177
+ * @param {Object} context 发起请求的数据模型上下文
178
+ * @param {Object} params 请求参数
179
+ */
180
+ async deleteHandle(context, params) {
181
+ return this._sendRequest(
182
+ context,
183
+ this.deleteItfUrl,
184
+ this.deleteItfMethod,
185
+ params
186
+ );
187
+ }
188
+
189
+ /**
190
+ * 根据主键字段id获取数据详情
191
+ * @param {Object} context 发起请求的数据模型上下文
192
+ * @param {Object} params 请求参数
193
+ */
194
+ async loadDataByRecIdHandle(context, params) {
195
+ return this._sendRequest(
196
+ context,
197
+ this.loadDataByRecIdItfUrl,
198
+ this.loadDataByRecIdMethod,
199
+ params
200
+ );
201
+ }
202
+
203
+ /**
204
+ * 保存数据
205
+ * @param {object} context
206
+ * @param {object} params
207
+ */
208
+ async saveHandle(context, params) {
209
+ return this._sendRequest(
210
+ context,
211
+ this.saveItfUrl,
212
+ this.saveItfMethod,
213
+ params
214
+ );
215
+ }
216
+
217
+ /**
218
+ * 更新数据
219
+ * @param {object} context
220
+ * @param {object} params
221
+ */
222
+ async updateHandle(context, params) {
223
+ return this._sendRequest(
224
+ context,
225
+ this.updateItfUrl,
226
+ this.updateItfMethod,
227
+ params
228
+ );
229
+ }
230
+
231
+ /**
232
+ * 提交
233
+ * @param {Object} context 发起请求的数据模型上下文
234
+ * @param {Object} params 保存参数
235
+ */
236
+ async submitHandle(context, params) {
237
+ return params?.[context.primaryKey]
238
+ ? this.updateHandle(context, params)
239
+ : this.saveHandle(context, params);
240
+ }
241
+ }