@cloudbase/lowcode-builder 1.7.1 → 1.8.1

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,117 @@
1
+ function checkFunc(listener) {
2
+ if (!listener instanceof Function) {
3
+ throw new Error(' The listener argument must be of type Function. ');
4
+ }
5
+ }
6
+
7
+ export default class EventEmitter {
8
+ constructor() {
9
+ this.listeners = {};
10
+ }
11
+
12
+ on(eventName, listener) {
13
+ checkFunc(listener);
14
+ let listeners = this.listeners[eventName];
15
+ if (!listeners) {
16
+ this.listeners[eventName] = [listener];
17
+ } else {
18
+ listeners.push(listener);
19
+ }
20
+ }
21
+
22
+ off(eventName, listener) {
23
+ let listeners = this.listeners[eventName];
24
+ if (listeners && listeners.length) {
25
+ const index = listeners.indexOf(listener);
26
+ index > -1 && listeners.splice(index, 1);
27
+ }
28
+ }
29
+
30
+ emit(eventName, ...args) {
31
+ let listeners = this.listeners[eventName] || [];
32
+ listeners.forEach((fn) => {
33
+ try {
34
+ fn.call(this, ...args);
35
+ } catch (err) {
36
+ console.error(err);
37
+ }
38
+ });
39
+ }
40
+
41
+ clear() {
42
+ this.listeners = {};
43
+ }
44
+ }
45
+
46
+ export class Event {
47
+ type: string;
48
+ detail: any;
49
+ /**
50
+ * 类别应当为 typeof UserWidget
51
+ * 当前类别为 typeof $page.widgets.xxxx
52
+ * 添加上报确定用量
53
+ */
54
+ currentTarget: any /* typeof UserWidget */;
55
+ target?: any /* typeof UserWidget */;
56
+ /**
57
+ * 内部实现
58
+ * 外部不应该进行访问
59
+ */
60
+ _isCapturePhase: boolean;
61
+ origin: Event;
62
+ constructor({
63
+ type = '',
64
+ detail = undefined,
65
+ currentTarget = undefined,
66
+ target = undefined,
67
+ _isCapturePhase = false,
68
+ origin,
69
+ }: {
70
+ type: string;
71
+ currentTarget: any;
72
+ target?: any;
73
+ detail?: any;
74
+ _isCapturePhase?: boolean;
75
+ origin?: Event;
76
+ }) {
77
+ function proxyWrapper(target, key) {
78
+ try {
79
+ return new Proxy(target, {
80
+ get(target, p) {
81
+ if (p !== 'id') {
82
+ // reportEvent(`event.${key}.${String(p)}`);
83
+ }
84
+ return target[p];
85
+ },
86
+ });
87
+ } catch (e) {
88
+ return target;
89
+ }
90
+ }
91
+ this.type = type;
92
+ this.detail = detail;
93
+ this.currentTarget = proxyWrapper(currentTarget, 'currentTarget');
94
+ this.target = proxyWrapper(target, 'target');
95
+ this.origin = proxyWrapper(origin, 'origin');
96
+
97
+ this._isCapturePhase = _isCapturePhase;
98
+ return new Proxy(this, {
99
+ get(target, prop) {
100
+ switch (prop) {
101
+ case 'name': {
102
+ console.warn('[deprecated] event.name 将在未来版本放弃支持,请使用 event.type 替代');
103
+ return target.type;
104
+ }
105
+ case 'origin':
106
+ case 'target':
107
+ case 'currentTarget': {
108
+ return target[prop];
109
+ break;
110
+ }
111
+ }
112
+
113
+ return target[prop];
114
+ },
115
+ });
116
+ }
117
+ }
@@ -0,0 +1,102 @@
1
+ import { createEventHandlers, getMpEventHandlerName } from './util';
2
+ import { Event } from './event-emitter';
3
+ type IGenerateContext = any;
4
+
5
+ export interface IEventFlowContext extends IGenerateContext {
6
+ $page?: any;
7
+ $app?: any;
8
+ target?: any; // widget
9
+ }
10
+
11
+ interface IMPEventFlowGenerateOptions {
12
+ looseError?: boolean;
13
+ isComposite?: boolean;
14
+ }
15
+
16
+ export class EventFlow {
17
+ id: string;
18
+ description: string;
19
+ private _eventHandlerMap: Record<string, Function> = {};
20
+ private _context: IEventFlowContext;
21
+
22
+ constructor({
23
+ schema,
24
+ context,
25
+ options,
26
+ }: {
27
+ schema: {
28
+ id?: string;
29
+ description?: string;
30
+ /**
31
+ * 预处理后 event handler listeners
32
+ */
33
+ eventHandlers?: Record<string, any>;
34
+ };
35
+ context: IEventFlowContext;
36
+ options?: IMPEventFlowGenerateOptions;
37
+ }) {
38
+ this._context = context;
39
+ const { id, description, eventHandlers = {} } = schema || {};
40
+ this.id = id || '';
41
+ this.description = description || '';
42
+
43
+ this._eventHandlerMap = Object.entries(createEventHandlers(eventHandlers, {
44
+ looseError: true,
45
+ isComposite: options?.isComposite || false,
46
+ syncCall: true
47
+ })).reduce(
48
+ (map, [key, fn]) => {
49
+ map[key] = fn;
50
+ return map;
51
+ },
52
+ {
53
+ _getInstance: () => this._context.$page || this._context.$app,
54
+ },
55
+ );
56
+
57
+ return this;
58
+ }
59
+
60
+ trigger(additionalScope, options: Partial<IEventFlowContext> = {}) {
61
+ const mergedContext = {
62
+ ...this._context,
63
+ ...options,
64
+ };
65
+ const { target } = mergedContext;
66
+ const eventName = `${this.id}.start`;
67
+ return this._eventHandlerMap[getMpEventHandlerName(this.id, eventName)](
68
+ new Event({
69
+ type: eventName,
70
+ detail: additionalScope,
71
+ target,
72
+ currentTarget: target,
73
+ }),
74
+ );
75
+ // const emit = (trigger, eventData, originalEvent?) =>
76
+ // generateEmit($w, target)(
77
+ // trigger,
78
+ // this._listenerInstances,
79
+ // eventData,
80
+ // forContext,
81
+ // originalEvent,
82
+ // scopeContext,
83
+ // dataContext,
84
+ // $w.page.id,
85
+ // true,
86
+ // );
87
+ // return emit(`${this.id}.start`, additionalScope);
88
+ }
89
+ }
90
+
91
+ export function generateEventFlows(
92
+ flows: { id: string }[] = [],
93
+ context: IEventFlowContext,
94
+ options?: IMPEventFlowGenerateOptions,
95
+ ) {
96
+ const result = {};
97
+
98
+ for (let flow of flows) {
99
+ result[flow.id] = new EventFlow({ schema: flow, context, options });
100
+ }
101
+ return result;
102
+ }
@@ -0,0 +1,239 @@
1
+ import { observable, IReactionDisposer, autorun, toJS } from 'mobx';
2
+ import { createEventHandlers, getMpEventHandlerName, mergeDynamic2StaticData } from './util';
3
+ import { Event } from './event-emitter';
4
+
5
+ interface IMPDataSourceQuery {
6
+ id: string;
7
+ label?: string;
8
+ description?: string;
9
+ trigger: 'auto' | 'manual';
10
+ type: 'model' | 'apis' | 'sql';
11
+ dataSourceName: string;
12
+ methodName: string;
13
+ data: object;
14
+ dataBinds: Record<string, Function>;
15
+ /**
16
+ * 预处理后 event handler listeners
17
+ */
18
+ eventHandlers?: Record<string, any>;
19
+ }
20
+ interface IQueryContext {
21
+ $w: any,
22
+ $app?: any,
23
+ $page?: any
24
+ };
25
+ type IDataBind = any;
26
+ interface IGenerateOptions {
27
+ looseError: boolean
28
+ };
29
+
30
+ export class Query {
31
+ private _schema: IMPDataSourceQuery;
32
+ private _context: IQueryContext;
33
+ private _disposes: IReactionDisposer[] = [];
34
+ private _dataBinds: Record<string, IDataBind> = {};
35
+ private _triggered = false;
36
+ private _action: any;
37
+ private _paramsRef: { current: any } = observable({ current: null });
38
+ private _currentRequestKey: any = null;
39
+ private _observableValue: { data: any; error: Error | null } = observable({ data: null, error: null });
40
+ private _eventHandlerMap: Record<string, Function> = {};
41
+ private _timer: any;
42
+
43
+ constructor({
44
+ schema,
45
+ context,
46
+ options = {},
47
+ }: {
48
+ schema: IMPDataSourceQuery;
49
+ context: IQueryContext;
50
+ options?: IGenerateOptions;
51
+ }) {
52
+ const { looseError = false } = options;
53
+ const { $w } = context;
54
+ this._schema = schema;
55
+ this._context = context;
56
+
57
+ if (this._schema?.trigger === 'auto') {
58
+ this._disposes.push(
59
+ autorun(
60
+ (r) => {
61
+ try {
62
+ const data = this._resolveParams();
63
+ if (this._triggered) {
64
+ this._debounceTrigger(data);
65
+ }
66
+ } catch (e) {
67
+ console.error(e);
68
+ }
69
+ },
70
+ { delay: 50 },
71
+ ),
72
+ );
73
+ }
74
+
75
+ const baseParam: {
76
+ dataSourceName: string;
77
+ sqlTemplateId?: string;
78
+ methodName?: string;
79
+ } = {
80
+ dataSourceName: this._schema.dataSourceName,
81
+ };
82
+
83
+ if (this._schema?.type === 'sql') {
84
+ baseParam.sqlTemplateId = this._schema.methodName;
85
+ } else {
86
+ baseParam.methodName = this._schema.methodName;
87
+ }
88
+
89
+ this._paramsRef.current = this._schema.data;
90
+ this._dataBinds = this._schema.dataBinds;
91
+
92
+ this._action = async (data) => {
93
+ return $w.cloud.callDataSource({
94
+ ...baseParam,
95
+ params: data,
96
+ });
97
+ };
98
+
99
+ this._eventHandlerMap = Object.entries(
100
+ createEventHandlers(this._schema.eventHandlers || {}, {
101
+ looseError: looseError,
102
+ isComposite: false,
103
+ }),
104
+ ).reduce(
105
+ (map, [key, fn]: [string, any]) => {
106
+ // map[key] = fn.bind(this);
107
+ map[key] = fn;
108
+ return map;
109
+ },
110
+ {
111
+ _getInstance: () => this._context.$page || this._context.$app,
112
+ },
113
+ );
114
+
115
+ // this._emit = async (trigger, eventData, originalEvent?) =>
116
+ // generateEmit($w)(
117
+ // trigger,
118
+ // generateListnerInstances(
119
+ // {
120
+ // $app,
121
+ // $page,
122
+ // actionsMap: (this._context as any).actionsMap,
123
+ // },
124
+ // this._schema.listeners || [],
125
+ // ),
126
+ // eventData,
127
+ // {},
128
+ // originalEvent,
129
+ // {},
130
+ // {},
131
+ // $w.page.id,
132
+ // true,
133
+ // );
134
+
135
+ return this;
136
+ }
137
+
138
+ get id(): string {
139
+ return this._schema?.id || '';
140
+ }
141
+
142
+ get label(): string {
143
+ return this._schema?.label || '';
144
+ }
145
+
146
+ get description(): string {
147
+ return this._schema?.description || '';
148
+ }
149
+
150
+ get data() {
151
+ return this._observableValue.data;
152
+ }
153
+
154
+ get error() {
155
+ return this._observableValue.error;
156
+ }
157
+
158
+ async trigger(additionalScope?, options = {}) {
159
+ this._triggered = true;
160
+ return this._innerTrigger(this._resolveParams(), additionalScope, options);
161
+ }
162
+
163
+ reset() {
164
+ this._observableValue.data = null;
165
+ this._observableValue.error = null;
166
+ }
167
+
168
+ async _innerTrigger(data, additionalScope?, options = {}) {
169
+ this._currentRequestKey = Date.now();
170
+ const key = this._currentRequestKey;
171
+ try {
172
+ const res = await this._action(data);
173
+ if (key === this._currentRequestKey) {
174
+ this._observableValue.data = res;
175
+ this._observableValue.error = null;
176
+ this._emit(`success`, res);
177
+ }
178
+ return res;
179
+ } catch (e) {
180
+ if (key === this._currentRequestKey) {
181
+ this._observableValue.data = null;
182
+ this._observableValue.error = e as any;
183
+ this._emit(`fail`, e);
184
+ }
185
+ throw e;
186
+ }
187
+ }
188
+
189
+ private _debounceTrigger(...args) {
190
+ if (this._timer) {
191
+ clearTimeout(this._timer);
192
+ }
193
+ this._timer = setTimeout(() => {
194
+ this._innerTrigger(...args);
195
+ }, 300);
196
+ }
197
+
198
+ destroy() {
199
+ this._disposes.forEach((dispose) => dispose());
200
+ }
201
+
202
+ private _resolveParams() {
203
+ /**
204
+ * 这里万一其中某个字段计算失败
205
+ * 好像会阻塞其他字段的计算
206
+ * 从而导致 autorun 没有添加依赖
207
+ */
208
+ return mergeDynamic2StaticData(toJS(this._paramsRef.current), this._dataBinds, {
209
+ codeContext: {
210
+ /**
211
+ * $page 或 $comp 实例
212
+ */
213
+ instance: this._context.$page,
214
+ },
215
+ $w: this._context.$w,
216
+ // may be additional scope
217
+ })?.params;
218
+ }
219
+
220
+ private async _emit(eventName: string, data?: any) {
221
+ return this._eventHandlerMap[getMpEventHandlerName(this.id, eventName)]?.(
222
+ new Event({
223
+ type: eventName,
224
+ detail: data,
225
+ target: undefined,
226
+ currentTarget: undefined,
227
+ }),
228
+ );
229
+ }
230
+ }
231
+
232
+ export function generateDatasetQuery(schema, context: IQueryContext, options: IGenerateOptions) {
233
+ const result = {};
234
+
235
+ for (const key in schema) {
236
+ result[key] = new Query({ schema: schema[key], context, options });
237
+ }
238
+ return result;
239
+ }
@@ -4,6 +4,9 @@ import { generateForContextOfWidget, generateWidgetAPIContext, getWidget } from
4
4
  import { observable, untracked } from 'mobx';
5
5
  import { getAccessToken, loginScope } from '../datasources/index';
6
6
  import { app } from '../app/weapps-api';
7
+ export { generateDatasetQuery } from './query';
8
+ export { generateEventFlows } from './flow';
9
+ import { Event } from './event-emitter';
7
10
 
8
11
  /**
9
12
  * Convert abcWordSnd -> abc-word-snd
@@ -55,9 +58,10 @@ export function createEventHandlers(
55
58
  options = {
56
59
  looseError: false,
57
60
  isComposite: false,
61
+ syncCall: false,
58
62
  },
59
63
  ) {
60
- const { looseError = false, isComposite = false } = options;
64
+ const { looseError = false, isComposite = false, syncCall = false } = options;
61
65
  const evtHandlers = {};
62
66
  for (const name in evtListeners) {
63
67
  const listeners = evtListeners[name];
@@ -71,7 +75,6 @@ export function createEventHandlers(
71
75
  const [prefix = '', trigger] = name.split('$');
72
76
  // The page event handler
73
77
  const forContext = (!!currentTarget && generateForContextOfWidget(currentTarget)) || {};
74
- const { lists = [], forItems = {} } = forContext;
75
78
  const dataContext = untracked(() => generateDataContext(currentTarget));
76
79
  const $w = untracked(() => generateWidgetAPIContext(owner?.__internal__?.$w, currentTarget, forContext));
77
80
 
@@ -84,7 +87,13 @@ export function createEventHandlers(
84
87
  }
85
88
  const { id, __internal__ } = currentPageContext || {};
86
89
  if (pageId && id) {
87
- if (pageId !== id || (__internal__ && !__internal__.active)) {
90
+ /**
91
+ * TODO:
92
+ * IOS 设备跳转扫码成功回到当前页调度存在时序问题
93
+ * 成功响应时当前页非 active 状态
94
+ * 取消判断待兼容
95
+ */
96
+ if (pageId !== id /* || (__internal__ && !__internal__.active)*/) {
88
97
  console.error(`Action error: [${name}] 页面生命周期结束,链式调用中断`);
89
98
  return false;
90
99
  }
@@ -92,7 +101,7 @@ export function createEventHandlers(
92
101
  return true;
93
102
  }
94
103
 
95
- listeners.forEach(async (l) => {
104
+ async function processListener(l) {
96
105
  /**
97
106
  * 调用前置校验
98
107
  * 是否仍可调用方法
@@ -115,10 +124,16 @@ export function createEventHandlers(
115
124
  const isIfAction = l.sourceKey === 'platform:utils.If';
116
125
  const isShowModalAction = l.sourceKey === 'platform:showModal';
117
126
  try {
118
- for (const k in boundData) {
119
- set(data, k, boundData[k].call(owner, owner, lists, forItems, event, dataContext, $w));
120
- }
121
- let res = await l.handler.call(owner, { event, data });
127
+ const resolvedData = mergeDynamic2StaticData(data, boundData, {
128
+ $w,
129
+ forContext,
130
+ codeContext: {
131
+ instance: owner,
132
+ event,
133
+ },
134
+ dataContext,
135
+ });
136
+ let res = await l.handler.call(owner, { event, data: resolvedData });
122
137
  nextEventHandles[0].handlerName =
123
138
  prefix && l.key ? `${prefix}$${l.key}${!isIfAction || res ? '_success' : '_fail'}` : '';
124
139
  nextEventHandles[0].event.detail = isIfAction ? event.detail : res;
@@ -164,17 +179,41 @@ export function createEventHandlers(
164
179
  }
165
180
  throw error;
166
181
  } else {
167
- await Promise.all(
168
- nextEventHandles.map(async (nextHandler) => {
169
- if (self[nextHandler.handlerName]) {
182
+ const res = await Promise.all(
183
+ nextEventHandles
184
+ .filter((nextHandler) => self[nextHandler.handlerName])
185
+ .map(async (nextHandler) => {
170
186
  return self[nextHandler.handlerName](nextHandler.event);
171
- }
172
- return null;
173
- }),
187
+ }),
174
188
  );
189
+ return res.length ? res[res.length - 1] : nextEventHandles[nextEventHandles.length - 1]?.event?.detail;
175
190
  }
176
191
  }
177
- });
192
+ }
193
+
194
+ const promise = Promise.all(
195
+ listeners.map((l) => {
196
+ return processListener(l).then(
197
+ (res) => ({ data: res, error: null }),
198
+ (error) => ({ data: null, error }),
199
+ );
200
+ }),
201
+ );
202
+
203
+ if (syncCall) {
204
+ return promise.then((listenerRes) => {
205
+ const res = [];
206
+ for (const item of listenerRes) {
207
+ if (item.error) {
208
+ throw item.error;
209
+ } else {
210
+ res.push(item.data);
211
+ }
212
+ }
213
+ return res.length ? res[0] : event.detail;
214
+ });
215
+ }
216
+
178
217
  };
179
218
  }
180
219
 
@@ -417,6 +456,7 @@ export function formatErrorMsg(e) {
417
456
  * 检查页面权限
418
457
  **/
419
458
  export async function checkAuth(app, appId, $page) {
459
+ return true;
420
460
  const loginPage = findLoginPage(app);
421
461
  if (loginPage?.id === $page.id) {
422
462
  return true;
@@ -560,3 +600,45 @@ const isEmptyObj = (obj) => {
560
600
  }
561
601
  return true;
562
602
  };
603
+
604
+ export function mergeDynamic2StaticData(
605
+ staticData,
606
+ dataBinds,
607
+ context /*: {
608
+ $w: any;
609
+ forContext?: {
610
+ lists: any[]
611
+ forItems: Record<string,any>
612
+ };
613
+ codeContext: {
614
+ instance: any
615
+ event?: Event;
616
+ };
617
+ dataContext?: Record<string,any>;
618
+ }*/,
619
+ ) {
620
+ const { forContext = {}, codeContext = {}, dataContext = {}, $w } = context;
621
+ const { lists = [], forItems = {} } = forContext;
622
+
623
+ const resolvedData = {
624
+ ...staticData,
625
+ };
626
+
627
+ for (const key in dataBinds) {
628
+ set(
629
+ resolvedData,
630
+ key,
631
+ dataBinds[key].call(
632
+ codeContext.instance,
633
+ codeContext.instance,
634
+ lists,
635
+ forItems,
636
+ codeContext.event,
637
+ dataContext,
638
+ $w,
639
+ ),
640
+ );
641
+ }
642
+
643
+ return resolvedData;
644
+ }