clickgo 3.0.0-dev

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 (145) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +75 -0
  3. package/dist/app/demo/config.json +106 -0
  4. package/dist/app/demo/form/control/block/block.css +1 -0
  5. package/dist/app/demo/form/control/block/block.scss +17 -0
  6. package/dist/app/demo/form/control/block/block.xml +7 -0
  7. package/dist/app/demo/form/control/button/button.css +1 -0
  8. package/dist/app/demo/form/control/button/button.js +27 -0
  9. package/dist/app/demo/form/control/button/button.scss +18 -0
  10. package/dist/app/demo/form/control/button/button.xml +126 -0
  11. package/dist/app/demo/form/control/check/check.js +12 -0
  12. package/dist/app/demo/form/control/check/check.xml +13 -0
  13. package/dist/app/demo/form/control/dialog/dialog.js +8 -0
  14. package/dist/app/demo/form/control/dialog/dialog.xml +26 -0
  15. package/dist/app/demo/form/control/file/file.js +23 -0
  16. package/dist/app/demo/form/control/file/file.xml +25 -0
  17. package/dist/app/demo/form/control/form/form.css +1 -0
  18. package/dist/app/demo/form/control/form/form.js +38 -0
  19. package/dist/app/demo/form/control/form/form.scss +9 -0
  20. package/dist/app/demo/form/control/form/form.xml +28 -0
  21. package/dist/app/demo/form/control/greatview/greatview.css +1 -0
  22. package/dist/app/demo/form/control/greatview/greatview.js +92 -0
  23. package/dist/app/demo/form/control/greatview/greatview.scss +22 -0
  24. package/dist/app/demo/form/control/greatview/greatview.xml +89 -0
  25. package/dist/app/demo/form/control/img/img.xml +16 -0
  26. package/dist/app/demo/form/control/label/label.xml +11 -0
  27. package/dist/app/demo/form/control/list/list.css +1 -0
  28. package/dist/app/demo/form/control/list/list.js +194 -0
  29. package/dist/app/demo/form/control/list/list.scss +7 -0
  30. package/dist/app/demo/form/control/list/list.xml +91 -0
  31. package/dist/app/demo/form/control/loading/loading.xml +8 -0
  32. package/dist/app/demo/form/control/marquee/marquee.js +30 -0
  33. package/dist/app/demo/form/control/marquee/marquee.xml +36 -0
  34. package/dist/app/demo/form/control/menu/menu.js +8 -0
  35. package/dist/app/demo/form/control/menu/menu.xml +122 -0
  36. package/dist/app/demo/form/control/monaco/monaco.js +113 -0
  37. package/dist/app/demo/form/control/monaco/monaco.xml +27 -0
  38. package/dist/app/demo/form/control/overflow/overflow.css +1 -0
  39. package/dist/app/demo/form/control/overflow/overflow.js +70 -0
  40. package/dist/app/demo/form/control/overflow/overflow.scss +18 -0
  41. package/dist/app/demo/form/control/overflow/overflow.xml +98 -0
  42. package/dist/app/demo/form/control/property/property.js +129 -0
  43. package/dist/app/demo/form/control/property/property.xml +6 -0
  44. package/dist/app/demo/form/control/radio/radio.js +7 -0
  45. package/dist/app/demo/form/control/radio/radio.xml +12 -0
  46. package/dist/app/demo/form/control/scroll/scroll.js +14 -0
  47. package/dist/app/demo/form/control/scroll/scroll.xml +35 -0
  48. package/dist/app/demo/form/control/select/select.js +91 -0
  49. package/dist/app/demo/form/control/select/select.xml +74 -0
  50. package/dist/app/demo/form/control/tab/tab.js +75 -0
  51. package/dist/app/demo/form/control/tab/tab.xml +22 -0
  52. package/dist/app/demo/form/control/text/text.js +53 -0
  53. package/dist/app/demo/form/control/text/text.xml +37 -0
  54. package/dist/app/demo/form/control/view/view.css +1 -0
  55. package/dist/app/demo/form/control/view/view.js +73 -0
  56. package/dist/app/demo/form/control/view/view.scss +18 -0
  57. package/dist/app/demo/form/control/view/view.xml +94 -0
  58. package/dist/app/demo/form/event/form/form.css +1 -0
  59. package/dist/app/demo/form/event/form/form.js +129 -0
  60. package/dist/app/demo/form/event/form/form.scss +24 -0
  61. package/dist/app/demo/form/event/form/form.xml +16 -0
  62. package/dist/app/demo/form/event/screen/screen.js +51 -0
  63. package/dist/app/demo/form/event/screen/screen.xml +9 -0
  64. package/dist/app/demo/form/event/task/task.js +78 -0
  65. package/dist/app/demo/form/event/task/task.xml +20 -0
  66. package/dist/app/demo/form/main.css +1 -0
  67. package/dist/app/demo/form/main.js +25 -0
  68. package/dist/app/demo/form/main.scss +9 -0
  69. package/dist/app/demo/form/main.xml +49 -0
  70. package/dist/app/demo/form/method/core/core.js +25 -0
  71. package/dist/app/demo/form/method/core/core.xml +7 -0
  72. package/dist/app/demo/form/method/dom/dom.css +1 -0
  73. package/dist/app/demo/form/method/dom/dom.js +163 -0
  74. package/dist/app/demo/form/method/dom/dom.scss +10 -0
  75. package/dist/app/demo/form/method/dom/dom.xml +55 -0
  76. package/dist/app/demo/form/method/form/form.css +1 -0
  77. package/dist/app/demo/form/method/form/form.js +217 -0
  78. package/dist/app/demo/form/method/form/form.scss +3 -0
  79. package/dist/app/demo/form/method/form/form.xml +56 -0
  80. package/dist/app/demo/form/method/form/test.xml +5 -0
  81. package/dist/app/demo/form/method/fs/fs.js +88 -0
  82. package/dist/app/demo/form/method/fs/fs.xml +8 -0
  83. package/dist/app/demo/form/method/fs/text.js +15 -0
  84. package/dist/app/demo/form/method/fs/text.xml +3 -0
  85. package/dist/app/demo/form/method/task/locale1.json +3 -0
  86. package/dist/app/demo/form/method/task/locale2.json +3 -0
  87. package/dist/app/demo/form/method/task/task.js +153 -0
  88. package/dist/app/demo/form/method/task/task.xml +57 -0
  89. package/dist/app/demo/form/method/theme/theme.js +74 -0
  90. package/dist/app/demo/form/method/theme/theme.xml +9 -0
  91. package/dist/app/demo/form/method/tool/tool.js +64 -0
  92. package/dist/app/demo/form/method/tool/tool.xml +26 -0
  93. package/dist/app/demo/form/method/zip/zip.js +99 -0
  94. package/dist/app/demo/form/method/zip/zip.xml +12 -0
  95. package/dist/app/demo/global.css +3 -0
  96. package/dist/app/demo/res/icon.svg +1 -0
  97. package/dist/app/demo/res/img.jpg +0 -0
  98. package/dist/app/demo/res/r-1.svg +1 -0
  99. package/dist/app/demo/res/r-2.svg +1 -0
  100. package/dist/app/demo/res/sql.svg +1 -0
  101. package/dist/app/demo/res/txt.svg +1 -0
  102. package/dist/app/demo/res/zip.svg +1 -0
  103. package/dist/app/task/config.json +29 -0
  104. package/dist/app/task/form/bar/bar.js +299 -0
  105. package/dist/app/task/form/bar/bar.xml +47 -0
  106. package/dist/app/task/form/desktop/desktop.xml +1 -0
  107. package/dist/app/task/locale/en.json +11 -0
  108. package/dist/app/task/locale/ja.json +11 -0
  109. package/dist/app/task/locale/sc.json +11 -0
  110. package/dist/app/task/locale/tc.json +11 -0
  111. package/dist/clickgo.js +41 -0
  112. package/dist/clickgo.ts +51 -0
  113. package/dist/control/common.cgc +0 -0
  114. package/dist/control/form.cgc +0 -0
  115. package/dist/control/monaco.cgc +0 -0
  116. package/dist/control/property.cgc +0 -0
  117. package/dist/control/task.cgc +0 -0
  118. package/dist/global.css +1 -0
  119. package/dist/icon.png +0 -0
  120. package/dist/index.js +88 -0
  121. package/dist/index.ts +92 -0
  122. package/dist/lib/control.js +365 -0
  123. package/dist/lib/control.ts +428 -0
  124. package/dist/lib/core.js +668 -0
  125. package/dist/lib/core.ts +732 -0
  126. package/dist/lib/dom.js +1471 -0
  127. package/dist/lib/dom.ts +1785 -0
  128. package/dist/lib/form.js +2101 -0
  129. package/dist/lib/form.ts +2496 -0
  130. package/dist/lib/fs.js +849 -0
  131. package/dist/lib/fs.ts +995 -0
  132. package/dist/lib/native.js +138 -0
  133. package/dist/lib/native.ts +219 -0
  134. package/dist/lib/task.js +686 -0
  135. package/dist/lib/task.ts +842 -0
  136. package/dist/lib/theme.js +159 -0
  137. package/dist/lib/theme.ts +196 -0
  138. package/dist/lib/tool.js +501 -0
  139. package/dist/lib/tool.ts +620 -0
  140. package/dist/lib/zip.js +352 -0
  141. package/dist/lib/zip.ts +434 -0
  142. package/dist/theme/familiar.cgt +0 -0
  143. package/package.json +27 -0
  144. package/types/dev.d.ts +30 -0
  145. package/types/index.d.ts +673 -0
@@ -0,0 +1,2496 @@
1
+ /**
2
+ * Copyright 2022 Han Guoshuai <zohegs@gmail.com>
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import * as types from '../../types';
17
+ import * as clickgo from '../clickgo';
18
+ import * as core from './core';
19
+ import * as task from './task';
20
+ import * as tool from './tool';
21
+ import * as dom from './dom';
22
+ import * as control from './control';
23
+ import * as native from './native';
24
+
25
+ /** --- form 相关信息 --- */
26
+ const info: {
27
+ /** --- 最后一个窗体 id --- */
28
+ lastId: number;
29
+ /** --- 最后一个窗体层级,1000(一千)开始 --- */
30
+ lastZIndex: number;
31
+ /** --- 最后一个置顶窗体层级,10000000(一千万)开始 --- */
32
+ topLastZIndex: number;
33
+ /** --- 用到的语言包 --- */
34
+ locale: Record<string, {
35
+ 'ok': string;
36
+ 'yes': string;
37
+ 'no': string;
38
+ 'cancel': string;
39
+ }>;
40
+ } = {
41
+ 'lastId': 0,
42
+ 'lastZIndex': 999,
43
+ 'topLastZIndex': 9999999,
44
+ 'locale': {
45
+ 'sc': {
46
+ 'ok': '好',
47
+ 'yes': '是',
48
+ 'no': '否',
49
+ 'cancel': '取消'
50
+ },
51
+ 'tc': {
52
+ 'ok': '好',
53
+ 'yes': '是',
54
+ 'no': '否',
55
+ 'cancel': '取消'
56
+ },
57
+ 'en': {
58
+ 'ok': 'OK',
59
+ 'yes': 'Yes',
60
+ 'no': 'No',
61
+ 'cancel': 'Cancel'
62
+ },
63
+ 'ja': {
64
+ 'ok': '好',
65
+ 'yes': 'はい',
66
+ 'no': 'いいえ',
67
+ 'cancel': 'キャンセル'
68
+ }
69
+ }
70
+ };
71
+
72
+ /** --- pop 相关信息 --- */
73
+ const popInfo: {
74
+ /** --- 当前显示的 pop 列表 --- */
75
+ 'list': HTMLElement[];
76
+ /** --- 当前显示的 pop 的母标签 --- */
77
+ 'elList': HTMLElement[];
78
+ /** --- pop 最后一个层级 --- */
79
+ 'lastZIndex': number;
80
+ } = {
81
+ 'list': [],
82
+ 'elList': [],
83
+ 'lastZIndex': 0
84
+ };
85
+
86
+ export let simpleSystemTaskRoot: types.IVue;
87
+ const elements: {
88
+ 'wrap': HTMLDivElement;
89
+ 'list': HTMLDivElement;
90
+ 'popList': HTMLDivElement;
91
+ 'circular': HTMLDivElement;
92
+ 'rectangle': HTMLDivElement;
93
+ 'gesture': HTMLDivElement;
94
+ 'drag': HTMLDivElement;
95
+ 'dragIcon'?: HTMLElement;
96
+ 'system': HTMLElement;
97
+ 'simpleSystemtask': HTMLDivElement;
98
+ 'init': () => void;
99
+ } = {
100
+ 'wrap': document.createElement('div'),
101
+ 'list': document.createElement('div'),
102
+ 'popList': document.createElement('div'),
103
+ 'circular': document.createElement('div'),
104
+ 'rectangle': document.createElement('div'),
105
+ 'gesture': document.createElement('div'),
106
+ 'drag': document.createElement('div'),
107
+ 'dragIcon': undefined,
108
+ 'system': document.createElement('div'),
109
+ 'simpleSystemtask': document.createElement('div'),
110
+ 'init': function() {
111
+ /** --- clickgo 所有的 div wrap --- */
112
+ this.wrap.id = 'cg-wrap';
113
+ document.getElementsByTagName('body')[0].appendChild(this.wrap);
114
+ if (clickgo.getNative()) {
115
+ this.wrap.addEventListener('mouseenter', function() {
116
+ native.send('cg-mouse-ignore', 'false');
117
+ });
118
+ this.wrap.addEventListener('mouseleave', function() {
119
+ native.send('cg-mouse-ignore', 'true');
120
+ });
121
+ }
122
+
123
+ /** --- form list 的 div --- */
124
+ this.list.id = 'cg-form-list';
125
+ this.wrap.appendChild(this.list);
126
+ this.list.addEventListener('touchmove', function(e): void {
127
+ // --- 防止拖动时整个网页跟着动 ---
128
+ if (e.cancelable) {
129
+ e.preventDefault();
130
+ }
131
+ // --- 为啥要在这加,因为有些设备性能不行,在 touchstart 之时添加的 touchmove 不能立马响应,导致网页还是跟着动,所以增加此函数 ---
132
+ }, {
133
+ 'passive': false
134
+ });
135
+ this.list.addEventListener('wheel', function(e): void {
136
+ // --- 防止不小心前进后退,或上下缓动滚动(Mac) ---
137
+ e.preventDefault();
138
+ }, {
139
+ 'passive': false
140
+ });
141
+ this.list.addEventListener('contextmenu', function(e): void {
142
+ e.preventDefault();
143
+ });
144
+
145
+ /** --- pop list 的 div --- */
146
+ this.popList.id = 'cg-pop-list';
147
+ this.popList.addEventListener('contextmenu', function(e): void {
148
+ e.preventDefault();
149
+ });
150
+ this.wrap.appendChild(this.popList);
151
+ this.popList.addEventListener('touchmove', function(e): void {
152
+ // --- 防止拖动时整个网页跟着动 ---
153
+ e.preventDefault();
154
+ }, {
155
+ 'passive': false
156
+ });
157
+
158
+ // --- 从鼠标指针处从小到大缩放然后淡化的圆圈动画特效对象 ---=
159
+ this.circular.id = 'cg-circular';
160
+ this.wrap.appendChild(this.circular);
161
+
162
+ // --- 从鼠标指针处开始从小到大缩放并铺满屏幕(或任意大小矩形)的对象 ---
163
+ this.rectangle.setAttribute('data-pos', '');
164
+ this.rectangle.id = 'cg-rectangle';
165
+ this.wrap.appendChild(this.rectangle);
166
+
167
+ // --- 手势有效无效的圆圈 ---
168
+ this.gesture.id = 'cg-gesture';
169
+ this.wrap.appendChild(this.gesture);
170
+
171
+ // --- drag drop 的拖动占位符 ---
172
+ this.drag.id = 'cg-drag';
173
+ this.drag.innerHTML = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 8L40 40" stroke="#FFF" stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter"/><path d="M8 40L40 8" stroke="#FFF" stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter"/></svg>';
174
+ this.dragIcon = this.drag.childNodes[0] as HTMLElement;
175
+ this.wrap.appendChild(this.drag);
176
+
177
+ // --- 添加 cg-system 的 dom ---
178
+ this.system.id = 'cg-system';
179
+ this.system.addEventListener('contextmenu', function(e): void {
180
+ e.preventDefault();
181
+ });
182
+ this.wrap.appendChild(this.system);
183
+ this.system.addEventListener('touchmove', function(e): void {
184
+ // --- 防止拖动时整个网页跟着动 ---
185
+ e.preventDefault();
186
+ }, {
187
+ 'passive': false
188
+ });
189
+
190
+ // --- 添加 cg-simplesystemtask 的 dom ---
191
+ this.simpleSystemtask.id = 'cg-simplesystemtask';
192
+ this.simpleSystemtask.addEventListener('contextmenu', function(e): void {
193
+ e.preventDefault();
194
+ });
195
+ this.wrap.appendChild(this.simpleSystemtask);
196
+ this.simpleSystemtask.addEventListener('touchmove', function(e): void {
197
+ // --- 防止拖动时整个网页跟着动 ---
198
+ e.preventDefault();
199
+ }, {
200
+ 'passive': false
201
+ });
202
+ const simpleSystemtaskApp = Vue.createApp({
203
+ 'template': '<div v-for="(item, formId) of forms" class="cg-simplesystemtask-item" @click="click(parseInt(formId))"><div v-if="item.icon" class="cg-simplesystemtask-icon" :style="{\'background-image\': \'url(\' + item.icon + \')\'}"></div><div>{{item.title}}</div></div>',
204
+ 'data': function() {
205
+ return {
206
+ 'forms': {}
207
+ };
208
+ },
209
+ 'watch': {
210
+ 'forms': {
211
+ handler: function(this: types.IVue) {
212
+ const length = Object.keys(this.forms).length;
213
+ if (length > 0) {
214
+ if (elements.simpleSystemtask.style.bottom !== '0px') {
215
+ elements.simpleSystemtask.style.bottom = '0px';
216
+ core.trigger('screenResize');
217
+ }
218
+ }
219
+ else {
220
+ if (elements.simpleSystemtask.style.bottom === '0px') {
221
+ elements.simpleSystemtask.style.bottom = '-46px';
222
+ core.trigger('screenResize');
223
+ }
224
+ }
225
+ },
226
+ 'deep': true
227
+ }
228
+ },
229
+ 'methods': {
230
+ click: function(this: types.IVue, formId: number): void {
231
+ changeFocus(formId);
232
+ }
233
+ },
234
+ 'mounted': function(this: types.IVue): void {
235
+ simpleSystemTaskRoot = this;
236
+ }
237
+ });
238
+ simpleSystemtaskApp.mount('#cg-simplesystemtask');
239
+ }
240
+ };
241
+ elements.init();
242
+
243
+ /**
244
+ * --- 修改窗体的最大化、最小化状态,外部或不可调整 state 时才调用 ---
245
+ * @param state 最大化、最小化或关闭
246
+ * @param formId 窗体 id
247
+ */
248
+ function changeState(state: 'min' | 'max' | 'close', formId?: number): boolean {
249
+ if (!formId) {
250
+ return false;
251
+ }
252
+ const tid: number = getTaskId(formId);
253
+ const t = task.list[tid];
254
+ if (!t) {
255
+ return false;
256
+ }
257
+ switch (state) {
258
+ case 'max': {
259
+ t.forms[formId].vroot.$refs.form.maxMethod();
260
+ break;
261
+ }
262
+ case 'min': {
263
+ t.forms[formId].vroot.$refs.form.minMethod();
264
+ break;
265
+ }
266
+ default: {
267
+ remove(formId);
268
+ }
269
+ }
270
+ return true;
271
+ }
272
+
273
+ /**
274
+ * --- 最小化某个窗体 ---
275
+ * @param formId 窗体 id,App 模式下可省略
276
+ */
277
+ export function min(formId?: number): boolean {
278
+ return changeState('min', formId);
279
+ }
280
+
281
+ /**
282
+ * --- 最大化某个窗体 ---
283
+ * @param formId formId 窗体 id,App 模式下可省略
284
+ */
285
+ export function max(formId?: number): boolean {
286
+ return changeState('max', formId);
287
+ }
288
+
289
+ /**
290
+ * --- 关闭一个窗体 ---
291
+ * @param formId formId 窗体 id,App 模式下可省略
292
+ */
293
+ export function close(formId?: number): boolean {
294
+ return changeState('close', formId);
295
+ }
296
+
297
+ /**
298
+ * --- 绑定窗体拖动大小事件,在 mousedown、touchstart 中绑定 ---
299
+ * @param e 事件源
300
+ * @param border 调整大小的方位
301
+ */
302
+ export function bindResize(e: MouseEvent | TouchEvent, border: types.TBorder): void {
303
+ const formWrap = dom.findParentByClass(e.target as HTMLElement, 'cg-form-wrap');
304
+ if (!formWrap) {
305
+ return;
306
+ }
307
+ const formId = formWrap.dataset.formId;
308
+ if (!formId) {
309
+ return;
310
+ }
311
+ const fid = parseInt(formId);
312
+ const tid = getTaskId(fid);
313
+ const t = task.list[tid];
314
+ if (!t) {
315
+ return;
316
+ }
317
+ t.forms[fid].vroot.$refs.form.resizeMethod(e, border);
318
+ }
319
+
320
+ /**
321
+ * --- 绑定窗体拖动事件,在 mousedown、touchstart 中绑定 ---
322
+ * @param e 事件源
323
+ */
324
+ export function bindDrag(e: MouseEvent | TouchEvent): void {
325
+ const formWrap = dom.findParentByClass(e.target as HTMLElement, 'cg-form-wrap');
326
+ if (!formWrap) {
327
+ return;
328
+ }
329
+ const formId = formWrap.dataset.formId;
330
+ if (!formId) {
331
+ return;
332
+ }
333
+ const fid = parseInt(formId);
334
+ const tid = getTaskId(fid);
335
+ const t = task.list[tid];
336
+ if (!t) {
337
+ return;
338
+ }
339
+ t.forms[fid].vroot.$refs.form.moveMethod(e, true);
340
+ }
341
+
342
+ /**
343
+ * --- 重置所有已经最大化的窗体大小和位置 ---
344
+ */
345
+ export function refreshMaxPosition(): void {
346
+ const area = core.getAvailArea();
347
+ for (let i = 0; i < elements.list.children.length; ++i) {
348
+ const el = elements.list.children.item(i) as HTMLElement;
349
+ const ef = el.children.item(0) as HTMLElement;
350
+ if (ef.dataset.cgMax === undefined) {
351
+ continue;
352
+ }
353
+ const taskId = parseInt(el.getAttribute('data-task-id')!);
354
+ const formId = parseInt(el.getAttribute('data-form-id')!);
355
+ if (!task.list[taskId]) {
356
+ continue;
357
+ }
358
+ const vroot = task.list[taskId].forms[formId].vroot;
359
+ vroot.$refs.form.setPropData('left', area.left);
360
+ vroot.$refs.form.setPropData('top', area.top);
361
+ vroot.$refs.form.setPropData('width', area.width);
362
+ vroot.$refs.form.setPropData('height', area.height);
363
+ }
364
+ }
365
+
366
+ /**
367
+ * --- 根据窗体 id 获取 task id ---
368
+ * @param formId 窗体 id
369
+ */
370
+ export function getTaskId(formId: number): number {
371
+ const formElement = elements.list.querySelector(`[data-form-id='${formId}']`);
372
+ if (!formElement) {
373
+ return 0;
374
+ }
375
+ // --- 获取 task id ---
376
+ const taskIdAttr = formElement.getAttribute('data-task-id');
377
+ if (!taskIdAttr) {
378
+ return 0;
379
+ }
380
+ return parseInt(taskIdAttr);
381
+ }
382
+
383
+ /**
384
+ * --- 获取窗体信息 ---
385
+ * @param formId 窗体 id
386
+ */
387
+ export function get(formId: number): types.IFormInfo | null {
388
+ const taskId: number = getTaskId(formId);
389
+ if (taskId === 0) {
390
+ return null;
391
+ }
392
+ const item = task.list[taskId].forms[formId];
393
+ return {
394
+ 'taskId': taskId,
395
+ 'title': item.vroot.$refs.form.title,
396
+ 'icon': item.vroot.$refs.form.iconData,
397
+ 'stateMax': item.vroot.$refs.form.stateMaxData,
398
+ 'stateMin': item.vroot.$refs.form.stateMinData,
399
+ 'show': item.vroot.$refs.form.showData,
400
+ 'focus': item.vroot.cgFocus
401
+ };
402
+ }
403
+
404
+ /**
405
+ * --- 给一个窗体发送一个对象,不会知道成功与失败状态 ---
406
+ * @param formId 要接收对象的 form id
407
+ * @param obj 要发送的对象
408
+ */
409
+ export function send(formId: number, obj: Record<string, any>): void {
410
+ const taskId: number = getTaskId(formId);
411
+ if (taskId === 0) {
412
+ return;
413
+ }
414
+ const item = task.list[taskId].forms[formId];
415
+ if (!item.vroot.cgReceive) {
416
+ return;
417
+ }
418
+ item.vroot.cgReceive(obj);
419
+ }
420
+
421
+ /**
422
+ * --- 获取 form list 的简略情况 ---
423
+ * @param taskId 任务 ID
424
+ */
425
+ export function getList(taskId: number): Record<string, types.IFormInfo> {
426
+ if (!task.list[taskId]) {
427
+ return {};
428
+ }
429
+ const list: Record<string, types.IFormInfo> = {};
430
+ for (const fid in task.list[taskId].forms) {
431
+ const item = task.list[taskId].forms[fid];
432
+ list[fid] = {
433
+ 'taskId': taskId,
434
+ 'title': item.vroot.$refs.form.title,
435
+ 'icon': item.vroot.$refs.form.iconData,
436
+ 'stateMax': item.vroot.$refs.form.stateMaxData,
437
+ 'stateMin': item.vroot.$refs.form.stateMinData,
438
+ 'show': item.vroot.$refs.form.showData,
439
+ 'focus': item.vroot.cgFocus
440
+ };
441
+ }
442
+ return list;
443
+ }
444
+
445
+ /**
446
+ * --- 改变 form 的焦点 class ---
447
+ * @param formId 变更后的 form id
448
+ */
449
+ export function changeFocus(formId: number = 0): void {
450
+ if (typeof formId !== 'number') {
451
+ notify({
452
+ 'title': 'Warning',
453
+ 'content': 'The "formId" of "changeFocus" must be a number type.',
454
+ 'type': 'warning'
455
+ });
456
+ return;
457
+ }
458
+ const focusElement = document.querySelector('#cg-form-list > [data-cg-focus]');
459
+ if (focusElement) {
460
+ const dataFormId = focusElement.getAttribute('data-form-id');
461
+ if (dataFormId) {
462
+ const dataFormIdNumber = parseInt(dataFormId);
463
+ if (dataFormIdNumber === formId) {
464
+ return;
465
+ }
466
+ else {
467
+ const taskId = parseInt(focusElement.getAttribute('data-task-id') ?? '0');
468
+ const t = task.list[taskId];
469
+ t.forms[dataFormIdNumber].vapp._container.removeAttribute('data-cg-focus');
470
+ t.forms[dataFormIdNumber].vroot._cgFocus = false;
471
+ // --- 触发 formBlurred 事件 ---
472
+ core.trigger('formBlurred', taskId, dataFormIdNumber);
473
+ }
474
+ }
475
+ else {
476
+ return;
477
+ }
478
+ }
479
+ if (formId === 0) {
480
+ return;
481
+ }
482
+ const el = document.querySelector(`#cg-form-list > [data-form-id='${formId}']`);
483
+ if (!el) {
484
+ return;
485
+ }
486
+ // --- 如果是最小化状态的话,需要还原 ---
487
+ if ((el.children.item(0) as HTMLElement).dataset.cgMin !== undefined) {
488
+ min(formId);
489
+ }
490
+ // --- 获取所属的 taskId ---
491
+ const taskId: number = parseInt(el.getAttribute('data-task-id') ?? '0');
492
+ const t = task.list[taskId];
493
+ // --- 如果不是自定义的 zindex,则设置 zIndex 为最大 ---
494
+ if (!t.forms[formId].vroot.cgCustomZIndex) {
495
+ if (t.forms[formId].vroot.cgTopMost) {
496
+ t.forms[formId].vroot.$refs.form.setPropData('zIndex', ++info.topLastZIndex);
497
+ }
498
+ else {
499
+ t.forms[formId].vroot.$refs.form.setPropData('zIndex', ++info.lastZIndex);
500
+ }
501
+ }
502
+ // --- 检测 maskFor ---
503
+ const maskFor = t.forms[formId].vroot.$refs.form.maskFor;
504
+ if ((typeof maskFor === 'number') && (task.list[taskId].forms[maskFor])) {
505
+ // --- 有 maskFor 窗体 ---
506
+ // --- 如果是最小化状态的话,需要还原 ---
507
+ if (get(maskFor)!.stateMin) {
508
+ min(maskFor);
509
+ }
510
+ // --- 如果不是自定义的 zindex,则设置 zIndex 为最大 ---
511
+ if (!task.list[taskId].forms[maskFor].vroot.cgCustomZIndex) {
512
+ if (task.list[taskId].forms[maskFor].vroot.cgTopMost) {
513
+ task.list[taskId].forms[maskFor].vroot.$refs.form.setPropData('zIndex', ++info.topLastZIndex);
514
+ }
515
+ else {
516
+ task.list[taskId].forms[maskFor].vroot.$refs.form.setPropData('zIndex', ++info.lastZIndex);
517
+ }
518
+ }
519
+ // --- 开启 focus ---
520
+ task.list[taskId].forms[maskFor].vapp._container.dataset.cgFocus = '';
521
+ task.list[taskId].forms[maskFor].vroot._cgFocus = true;
522
+ // --- 触发 formFocused 事件 ---
523
+ core.trigger('formFocused', taskId, maskFor);
524
+ // --- 闪烁 ---
525
+ clickgo.form.flash(maskFor, taskId);
526
+ }
527
+ else {
528
+ // --- 正常开启 focus ---
529
+ t.forms[formId].vapp._container.dataset.cgFocus = '';
530
+ t.forms[formId].vroot._cgFocus = true;
531
+ // --- 触发 formFocused 事件 ---
532
+ core.trigger('formFocused', taskId, formId);
533
+ }
534
+ }
535
+
536
+ /**
537
+ * --- 获取当前 z-index 值最大的 form id(除了 top 模式的窗体和最小化的窗体) ---
538
+ * @param out 排除选项
539
+ */
540
+ export function getMaxZIndexID(out: {
541
+ 'taskIds'?: number[];
542
+ 'formIds'?: number[];
543
+ } = {}): number | null {
544
+ let zIndex: number = 0;
545
+ let formId: number | null = null;
546
+ for (let i = 0; i < elements.list.children.length; ++i) {
547
+ const formWrap = elements.list.children.item(i) as HTMLDivElement;
548
+ const formInner = formWrap.children.item(0) as HTMLDivElement;
549
+ // --- 排除 top most 窗体 ---
550
+ const z = parseInt(formInner.style.zIndex);
551
+ if (z > 9999999) {
552
+ continue;
553
+ }
554
+ // --- 排除最小化窗体 ---
555
+ if (formInner.dataset.cgMin !== undefined) {
556
+ continue;
557
+ }
558
+ // --- 排除用户定义的 task id 窗体 ---
559
+ const tid = parseInt(formWrap.getAttribute('data-task-id')!);
560
+ if (out.taskIds?.includes(tid)) {
561
+ continue;
562
+ }
563
+ // --- 排除用户定义的 form id 窗体 ---
564
+ const fid = parseInt(formWrap.getAttribute('data-form-id')!);
565
+ if (out.formIds?.includes(fid)) {
566
+ continue;
567
+ }
568
+ if (z > zIndex) {
569
+ zIndex = z;
570
+ formId = fid;
571
+ }
572
+ }
573
+ return formId;
574
+ }
575
+
576
+ /**
577
+ * --- 根据 border 方向 获取理论窗体大小 ---
578
+ * @param border 显示的位置代号
579
+ */
580
+ export function getRectByBorder(border: types.TBorder): { 'width': number; 'height': number; 'left': number; 'top': number; } {
581
+ const area = core.getAvailArea();
582
+ let width!: number, height!: number, left!: number, top!: number;
583
+ if (typeof border === 'string') {
584
+ switch (border) {
585
+ case 'lt': {
586
+ width = area.width / 2;
587
+ height = area.height / 2;
588
+ left = area.left;
589
+ top = area.top;
590
+ break;
591
+ }
592
+ case 't': {
593
+ width = area.width;
594
+ height = area.height;
595
+ left = area.left;
596
+ top = area.top;
597
+ break;
598
+ }
599
+ case 'tr': {
600
+ width = area.width / 2;
601
+ height = area.height / 2;
602
+ left = area.left + area.width / 2;
603
+ top = area.top;
604
+ break;
605
+ }
606
+ case 'r': {
607
+ width = area.width / 2;
608
+ height = area.height;
609
+ left = area.left + area.width / 2;
610
+ top = area.top;
611
+ break;
612
+ }
613
+ case 'rb': {
614
+ width = area.width / 2;
615
+ height = area.height / 2;
616
+ left = area.left + area.width / 2;
617
+ top = area.top + area.height / 2;
618
+ break;
619
+ }
620
+ case 'b': {
621
+ width = area.width;
622
+ height = area.height / 2;
623
+ left = area.left;
624
+ top = area.top + area.height / 2;
625
+ break;
626
+ }
627
+ case 'bl': {
628
+ width = area.width / 2;
629
+ height = area.height / 2;
630
+ left = area.left;
631
+ top = area.top + area.height / 2;
632
+ break;
633
+ }
634
+ case 'l': {
635
+ width = area.width / 2;
636
+ height = area.height;
637
+ left = area.left;
638
+ top = area.top;
639
+ break;
640
+ }
641
+ default: {
642
+ break;
643
+ }
644
+ }
645
+ }
646
+ else {
647
+ width = border.width ?? area.width;
648
+ height = border.height ?? area.height;
649
+ left = border.left ?? area.left;
650
+ top = border.top ?? area.top;
651
+ }
652
+ return {
653
+ 'width': width,
654
+ 'height': height,
655
+ 'left': left,
656
+ 'top': top
657
+ };
658
+ }
659
+
660
+ /**
661
+ * --- 显示从小到大的圆圈动画特效对象 ---
662
+ * @param x X 坐标
663
+ * @param y Y 坐标
664
+ */
665
+ export function showCircular(x: number, y: number): void {
666
+ elements.circular.style.transition = 'none';
667
+ requestAnimationFrame(function() {
668
+ elements.circular.style.width = '6px';
669
+ elements.circular.style.height = '6px';
670
+ elements.circular.style.left = (x - 3).toString() + 'px';
671
+ elements.circular.style.top = (y - 3).toString() + 'px';
672
+ elements.circular.style.opacity = '1';
673
+ requestAnimationFrame(function() {
674
+ elements.circular.style.transition = 'all .3s ease-out';
675
+ requestAnimationFrame(function() {
676
+ elements.circular.style.width = '60px';
677
+ elements.circular.style.height = '60px';
678
+ elements.circular.style.left = (x - 30).toString() + 'px';
679
+ elements.circular.style.top = (y - 30).toString() + 'px';
680
+ elements.circular.style.opacity = '0';
681
+ });
682
+ });
683
+ });
684
+ }
685
+
686
+ /**
687
+ * --- 移动矩形到新位置 ---
688
+ * @param border 显示的位置代号
689
+ */
690
+ export function moveRectangle(border: types.TBorder): void {
691
+ const dataReady = elements.rectangle.getAttribute('data-ready') ?? '0';
692
+ if (dataReady === '0') {
693
+ return;
694
+ }
695
+ const dataBorder = elements.rectangle.getAttribute('data-border') ?? '';
696
+ const setDataBorder = typeof border === 'string' ? border : `o-${border.left}-${border.top ?? 'n'}-${border.width}-${border.height ?? 'n'}`;
697
+ if (dataBorder === setDataBorder) {
698
+ return;
699
+ }
700
+ elements.rectangle.setAttribute('data-dir', setDataBorder);
701
+ const pos = getRectByBorder(border);
702
+ const width = pos.width - 20;
703
+ const height = pos.height - 20;
704
+ const left = pos.left + 10;
705
+ const top = pos.top + 10;
706
+ if (width !== undefined && height !== undefined && left !== undefined && top !== undefined) {
707
+ elements.rectangle.style.width = width.toString() + 'px';
708
+ elements.rectangle.style.height = height.toString() + 'px';
709
+ elements.rectangle.style.left = left.toString() + 'px';
710
+ elements.rectangle.style.top = top.toString() + 'px';
711
+ }
712
+ }
713
+
714
+ /**
715
+ * --- 显示从小到大的矩形动画特效对象 ---
716
+ * @param x 起始位置
717
+ * @param y 起始位置
718
+ * @param border 最大时位置代号
719
+ */
720
+ export function showRectangle(x: number, y: number, border: types.TBorder): void {
721
+ elements.rectangle.style.transition = 'none';
722
+ requestAnimationFrame(function() {
723
+ elements.rectangle.style.width = '5px';
724
+ elements.rectangle.style.height = '5px';
725
+ elements.rectangle.style.left = (x - 10).toString() + 'px';
726
+ elements.rectangle.style.top = (y - 10).toString() + 'px';
727
+ elements.rectangle.style.opacity = '1';
728
+ elements.rectangle.setAttribute('data-ready', '0');
729
+ elements.rectangle.setAttribute('data-dir', '');
730
+ requestAnimationFrame(function() {
731
+ elements.rectangle.style.transition = 'all .2s ease-out';
732
+ requestAnimationFrame(function() {
733
+ elements.rectangle.setAttribute('data-ready', '1');
734
+ moveRectangle(border);
735
+ });
736
+ });
737
+ });
738
+ }
739
+
740
+ /**
741
+ * --- 结束时请隐藏矩形 ---
742
+ */
743
+ export function hideRectangle(): void {
744
+ elements.rectangle.style.opacity = '0';
745
+ }
746
+
747
+ // --- DRAG ---
748
+
749
+ /**
750
+ * --- 显示 drag 虚拟框 ---
751
+ */
752
+ export function showDrag(): void {
753
+ elements.drag.style.opacity = '1';
754
+ }
755
+
756
+ /**
757
+ * --- 移动 drag 到新位置 ---
758
+ * @param opt top:顶部位置,left:左侧位置,width:宽度位置,height:高度位置
759
+ */
760
+ export function moveDrag(opt: types.IMoveDragOptions): void {
761
+ if (opt.top) {
762
+ elements.drag.style.top = opt.top.toString() + 'px';
763
+ }
764
+ if (opt.left) {
765
+ elements.drag.style.left = opt.left.toString() + 'px';
766
+ }
767
+ if (opt.width) {
768
+ elements.drag.style.width = opt.width.toString() + 'px';
769
+ }
770
+ if (opt.height) {
771
+ elements.drag.style.height = opt.height.toString() + 'px';
772
+ }
773
+ if (opt.icon) {
774
+ if (elements.dragIcon) {
775
+ elements.dragIcon.style.display = 'block';
776
+ }
777
+ }
778
+ else {
779
+ if (elements.dragIcon) {
780
+ elements.dragIcon.style.display = 'none';
781
+ }
782
+ }
783
+ }
784
+
785
+ /**
786
+ * --- 隐藏拖拽框框 ---
787
+ */
788
+ export function hideDrag(): void {
789
+ elements.drag.style.opacity = '0';
790
+ }
791
+
792
+ let notifyTop: number = 10;
793
+ let notifyId: number = 0;
794
+ /**
795
+ * --- 弹出右上角信息框 ---
796
+ * @param opt timeout 默认 5 秒
797
+ */
798
+ export function notify(opt: types.INotifyOptions): number {
799
+ // --- 申请 nid ---
800
+ const nid = ++notifyId;
801
+ // --- 设置 timeout ---
802
+ let timeout = 5000;
803
+ if (opt.timeout !== undefined) {
804
+ if (opt.timeout <= 0 || opt.timeout > 30000) {
805
+ timeout = 30000;
806
+ }
807
+ else {
808
+ timeout = opt.timeout;
809
+ }
810
+ }
811
+ // --- 设置 type ---
812
+ if (opt.progress && !opt.type) {
813
+ opt.type = 'progress';
814
+ }
815
+ // --- 创建 notify element ---
816
+ const el = document.createElement('div');
817
+ const y = notifyTop;
818
+ el.classList.add('cg-system-notify');
819
+ el.setAttribute('data-notifyid', nid.toString());
820
+ el.style.transform = `translateY(${y}px) translateX(280px)`;
821
+ el.style.opacity = '1';
822
+ el.innerHTML = `<div class="cg-system-icon cg-${tool.escapeHTML(opt.type ?? 'primary')}"></div>
823
+ <div style="flex: 1;">
824
+ <div class="cg-system-notify-title">${tool.escapeHTML(opt.title)}</div>
825
+ <div class="cg-system-notify-content">${tool.escapeHTML(opt.content).replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n/g, '<br>')}</div>
826
+ ${opt.progress ? '<div class="cg-system-notify-progress"></div>' : ''}
827
+ </div>`;
828
+ if (opt.icon) {
829
+ (el.childNodes.item(0) as HTMLElement).style.background = 'url(' + opt.icon + ')';
830
+ (el.childNodes.item(0) as HTMLElement).style.backgroundSize = '16px';
831
+ }
832
+ elements.system.appendChild(el);
833
+ notifyTop += el.offsetHeight + 10;
834
+ requestAnimationFrame(function() {
835
+ el.style.transform = `translateY(${y}px) translateX(-10px)`;
836
+ const timer = window.setTimeout(function() {
837
+ hideNotify(nid);
838
+ }, timeout);
839
+ el.setAttribute('data-timer', timer.toString());
840
+ });
841
+ return nid;
842
+ }
843
+
844
+ /**
845
+ * --- 修改 notify 的进度条进度 ---
846
+ * @param notifyId notify id
847
+ * @param per 进度,0 - 100 或 0% - 100% (0 - 1)
848
+ */
849
+ export function notifyProgress(notifyId: number, per: number): void {
850
+ const el: HTMLElement = elements.system.querySelector(`[data-notifyid="${notifyId}"]`)!;
851
+ if (!el) {
852
+ return;
853
+ }
854
+ const progress: HTMLElement = el.querySelector('.cg-system-notify-progress')!;
855
+ if (!progress) {
856
+ return;
857
+ }
858
+ if (per > 100) {
859
+ per = 100;
860
+ }
861
+ if (per === 1) {
862
+ const o = parseFloat(progress.style.width);
863
+ if (o > 1) {
864
+ per = 100;
865
+ }
866
+ }
867
+ if (per === 100) {
868
+ progress.style.transitionDelay = '.1s';
869
+ }
870
+ progress.style.width = (per < 1 ? per * 100 : per).toString() + '%';
871
+ }
872
+
873
+ /**
874
+ * --- 隐藏 notify ---
875
+ * @param notifyId 要隐藏的 notify id
876
+ */
877
+ export function hideNotify(notifyId: number): void {
878
+ const el: HTMLElement = elements.system.querySelector(`[data-notifyid="${notifyId}"]`)!;
879
+ if (!el) {
880
+ return;
881
+ }
882
+ clearTimeout(parseInt(el.getAttribute('data-timer')!));
883
+ const notifyHeight = el.offsetHeight;
884
+ el.style.opacity = '0';
885
+ setTimeout(function() {
886
+ notifyTop -= notifyHeight + 10;
887
+ const notifyElementList = document.getElementsByClassName('cg-system-notify') as HTMLCollectionOf<HTMLDivElement>;
888
+ let needSub = false;
889
+ for (const notifyElement of notifyElementList) {
890
+ if (notifyElement === el) {
891
+ // --- el 之后的 notify 都要往上移动 ---
892
+ needSub = true;
893
+ continue;
894
+ }
895
+ if (needSub) {
896
+ notifyElement.style.transform = notifyElement.style.transform.replace(/translateY\(([0-9]+)px\)/,
897
+ function(t: string, t1: string): string {
898
+ return `translateY(${parseInt(t1) - notifyHeight - 10}px)`;
899
+ }
900
+ );
901
+ }
902
+ }
903
+ el.remove();
904
+ }, 100);
905
+ }
906
+
907
+ /**
908
+ * --- 将标签追加到 pop 层 ---
909
+ * @param el 要追加的标签
910
+ */
911
+ export function appendToPop(el: HTMLElement): void {
912
+ elements.popList.appendChild(el);
913
+ }
914
+
915
+ /**
916
+ * --- 将标签从 pop 层移除 ---
917
+ * @param el 要移除的标签
918
+ */
919
+ export function removeFromPop(el: HTMLElement): void {
920
+ elements.popList.removeChild(el);
921
+ }
922
+
923
+ /** --- 最后一次 touchstart 的时间戳 */
924
+ let lastShowPopTime: number = 0;
925
+ /**
926
+ * --- 获取 pop 显示出来的坐标并报系统全局记录 ---
927
+ * @param el 响应的元素
928
+ * @param pop 要显示 pop 元素
929
+ * @param direction 要显示方向(以 $el 为准的 h 水平和 v 垂直)或坐标
930
+ * @param opt width / height 显示的 pop 定义自定义宽/高度,可省略;null,true 代表为空也会显示,默认为 false
931
+ */
932
+ export function showPop(el: HTMLElement, pop: HTMLElement | undefined, direction: 'h' | 'v' | MouseEvent | TouchEvent | { x: number; y: number; }, opt: { 'size'?: { width?: number; height?: number; }; 'null'?: boolean; } = {}): void {
933
+ // --- opt.null 为 true 代表可为空,为空也会被显示,默认为 false ---
934
+ if (opt.null === undefined) {
935
+ opt.null = false;
936
+ }
937
+ if (opt.size === undefined) {
938
+ opt.size = {};
939
+ }
940
+ // --- 也可能不执行本次显示 ---
941
+ if (!pop && !opt.null) {
942
+ return;
943
+ }
944
+ // --- 如果短时间内已经有了 pop 被展示,则可能是冒泡序列,本次则不展示 ---
945
+ const now = Date.now();
946
+ if (now - lastShowPopTime < 5) {
947
+ lastShowPopTime = now;
948
+ return;
949
+ }
950
+ lastShowPopTime = now;
951
+ // --- 检测是不是已经显示了 ---
952
+ if (el.dataset.cgPopOpen !== undefined) {
953
+ return;
954
+ }
955
+ /** --- 要不要隐藏别的 pop --- */
956
+ const parentPop = dom.findParentByData(el, 'cg-pop');
957
+ if (parentPop) {
958
+ for (let i = 0; i < popInfo.list.length; ++i) {
959
+ if (popInfo.list[i] !== parentPop) {
960
+ continue;
961
+ }
962
+ if (!popInfo.elList[i + 1]) {
963
+ continue;
964
+ }
965
+ hidePop(popInfo.elList[i + 1]);
966
+ }
967
+ }
968
+ else {
969
+ // --- 本层不是 pop,因此要隐藏所有 pop ---
970
+ hidePop();
971
+ }
972
+ // --- 检测如果 pop 是 undefined 还显示吗 ---
973
+ if (!pop) {
974
+ popInfo.elList.push(el);
975
+ el.dataset.cgPopOpen = '';
976
+ el.dataset.cgLevel = (popInfo.elList.length - 1).toString();
977
+ return;
978
+ }
979
+ // --- 最终 pop 的大小 ---
980
+ const width = opt.size.width ?? (pop ? pop.offsetWidth : 0);
981
+ const height = opt.size.height ?? (pop ? pop.offsetHeight : 0);
982
+ // --- 最终显示位置 ---
983
+ let left: number, top: number;
984
+ if (typeof direction === 'string') {
985
+ const bcr = el.getBoundingClientRect();
986
+ if (direction === 'v') {
987
+ // --- 垂直弹出 ---
988
+ left = bcr.left;
989
+ top = bcr.top + bcr.height;
990
+ }
991
+ else {
992
+ // --- 水平弹出 ---
993
+ left = bcr.left + bcr.width - 2;
994
+ top = bcr.top - 2;
995
+ }
996
+ // --- 检查水平是否出框 ---
997
+ if (width + left > document.body.clientWidth) {
998
+ if (direction === 'v') {
999
+ // --- 垂直弹出 ---
1000
+ left = bcr.left + bcr.width - width;
1001
+ }
1002
+ else {
1003
+ // --- 水平弹出,右边位置不够弹到左边 ---
1004
+ left = bcr.left - width + 2;
1005
+ }
1006
+ }
1007
+ // --- 检测垂直是否出框 ---
1008
+ if (height + top > document.body.clientHeight) {
1009
+ if (direction === 'v') {
1010
+ top = bcr.top - height;
1011
+ }
1012
+ else {
1013
+ top = bcr.top + bcr.height - height + 2;
1014
+ }
1015
+ }
1016
+ }
1017
+ else {
1018
+ let x: number;
1019
+ let y: number;
1020
+ if (direction instanceof MouseEvent) {
1021
+ x = direction.clientX;
1022
+ y = direction.clientY;
1023
+ }
1024
+ else if (direction instanceof TouchEvent) {
1025
+ x = direction.touches[0].clientX;
1026
+ y = direction.touches[0].clientY;
1027
+ }
1028
+ else {
1029
+ x = direction.x;
1030
+ y = direction.y;
1031
+ }
1032
+ left = x + 5;
1033
+ top = y + 7;
1034
+ // --- 水平 ---
1035
+ if (width + left > document.body.clientWidth) {
1036
+ left = x - width - 5;
1037
+ }
1038
+ // --- 垂直 ---
1039
+ if (height + top > document.body.clientHeight) {
1040
+ top = y - height - 5;
1041
+ }
1042
+ }
1043
+ if (left < 0) {
1044
+ left = 0;
1045
+ }
1046
+ if (top < 0) {
1047
+ top = 0;
1048
+ }
1049
+ pop.style.left = left.toString() + 'px';
1050
+ pop.style.top = top.toString() + 'px';
1051
+ pop.style.zIndex = (++popInfo.lastZIndex).toString();
1052
+ if (opt.size.width) {
1053
+ pop.style.width = opt.size.width.toString() + 'px';
1054
+ }
1055
+ if (opt.size.height) {
1056
+ pop.style.height = opt.size.height.toString() + 'px';
1057
+ }
1058
+ popInfo.list.push(pop);
1059
+ popInfo.elList.push(el);
1060
+ pop.dataset.cgOpen = '';
1061
+ pop.dataset.cgLevel = (popInfo.list.length - 1).toString();
1062
+ el.dataset.cgPopOpen = '';
1063
+ el.dataset.cgLevel = (popInfo.elList.length - 1).toString();
1064
+ }
1065
+
1066
+ /**
1067
+ * --- 隐藏正在显示中的所有 pop,或指定 pop/el ---
1068
+ */
1069
+ export function hidePop(pop?: HTMLElement): void {
1070
+ if (!pop) {
1071
+ if (popInfo.elList.length === 0) {
1072
+ return;
1073
+ }
1074
+ hidePop(popInfo.elList[0]);
1075
+ return;
1076
+ }
1077
+ let isPop: boolean = false;
1078
+ if (pop.dataset.cgPopOpen !== undefined) {
1079
+ // --- el ---
1080
+ }
1081
+ else if (pop.dataset.cgOpen !== undefined) {
1082
+ // --- pop ---
1083
+ isPop = true;
1084
+ }
1085
+ else {
1086
+ return;
1087
+ }
1088
+ const level = pop.dataset.cgLevel ? parseInt(pop.dataset.cgLevel) : -1;
1089
+ if (level === -1) {
1090
+ return;
1091
+ }
1092
+ if (popInfo.elList[level + 1]) {
1093
+ hidePop(popInfo.elList[level + 1]);
1094
+ }
1095
+ if (isPop) {
1096
+ pop.removeAttribute('data-cg-open');
1097
+ pop.removeAttribute('data-cg-level');
1098
+ popInfo.elList[level].removeAttribute('data-cg-pop-open');
1099
+ popInfo.elList[level].removeAttribute('data-cg-level');
1100
+ }
1101
+ else {
1102
+ if (popInfo.list[level]) {
1103
+ popInfo.list[level].removeAttribute('data-cg-open');
1104
+ popInfo.list[level].removeAttribute('data-cg-level');
1105
+ }
1106
+ pop.removeAttribute('data-cg-pop-open');
1107
+ pop.removeAttribute('data-cg-level');
1108
+ }
1109
+ popInfo.list.splice(level);
1110
+ popInfo.elList.splice(level);
1111
+ }
1112
+
1113
+ /**
1114
+ * --- 点下 (mousedown / touchstart) 屏幕任意一位置时根据点击处处理隐藏 pop 和焦点丢失事件,鼠标和 touch 只会响应一个 ---
1115
+ * @param e 事件对象
1116
+ */
1117
+ export function doFocusAndPopEvent(e: MouseEvent | TouchEvent): void {
1118
+ if (dom.hasTouchButMouse(e)) {
1119
+ return;
1120
+ }
1121
+ const target = e.target;
1122
+ if (!target) {
1123
+ return;
1124
+ }
1125
+ const element: HTMLElement | null = target as HTMLElement;
1126
+ if (element.dataset.cgPopOpen !== undefined) {
1127
+ // --- 此对象为已打开 pop 的组件,不做处理,组件自行处理 ---
1128
+ return;
1129
+ }
1130
+ const paths: HTMLElement[] = (e as any).path ?? (e.composedPath ? e.composedPath() : []);
1131
+ // --- 检测是不是弹出层 ---
1132
+ for (const item of paths) {
1133
+ if (!item.tagName) {
1134
+ continue;
1135
+ }
1136
+ if (item.tagName.toLowerCase() === 'body') {
1137
+ break;
1138
+ }
1139
+ if (item.id === 'cg-pop-list') {
1140
+ // --- 弹出层点击,不触发丢失焦点,也不触发隐藏 pop,是否隐藏请自行处理 ---
1141
+ return;
1142
+ }
1143
+ if (item.dataset.cgPopOpen !== undefined) {
1144
+ return;
1145
+ }
1146
+ }
1147
+ // --- 检测是不是窗体内部点击 ---
1148
+ for (const item of paths) {
1149
+ if (!item.tagName) {
1150
+ continue;
1151
+ }
1152
+ if (item.tagName.toLowerCase() === 'body') {
1153
+ break;
1154
+ }
1155
+ if (item.classList.contains('cg-form-wrap')) {
1156
+ // --- 窗体内部点击,转换焦点到当前窗体,但触发隐藏 pop ---
1157
+ const formId = parseInt(item.getAttribute('data-form-id') ?? '0');
1158
+ changeFocus(formId);
1159
+ hidePop();
1160
+ return;
1161
+ }
1162
+ }
1163
+ // --- 普罗大众的状态,要隐藏 menu,并且丢失窗体焦点 ---
1164
+ hidePop();
1165
+ changeFocus();
1166
+ }
1167
+ window.addEventListener('touchstart', doFocusAndPopEvent, {
1168
+ 'passive': true
1169
+ });
1170
+ window.addEventListener('mousedown', doFocusAndPopEvent);
1171
+
1172
+ /**
1173
+ * --- 移除一个 form(关闭窗口) ---
1174
+ * @param formId 要移除的 form id
1175
+ */
1176
+ export function remove(formId: number): boolean {
1177
+ const taskId: number = getTaskId(formId);
1178
+ let title = '';
1179
+ let icon = '';
1180
+ if (task.list[taskId].forms[formId]) {
1181
+ title = task.list[taskId].forms[formId].vroot.$refs.form.title;
1182
+ icon = task.list[taskId].forms[formId].vroot.$refs.form.iconData;
1183
+ if (task.list[taskId].forms[formId].vroot.$refs.form.maskFrom !== undefined) {
1184
+ const fid = task.list[taskId].forms[formId].vroot.$refs.form.maskFrom;
1185
+ task.list[taskId].forms[fid].vroot.$refs.form.maskFor = undefined;
1186
+ }
1187
+ task.list[taskId].forms[formId].vroot.$refs.form.$data.showData = false;
1188
+ setTimeout(function() {
1189
+ // --- 获取最大的 z index 窗体,并让他获取焦点 ---
1190
+ const fid = getMaxZIndexID({
1191
+ 'formIds': [formId]
1192
+ });
1193
+ if (fid) {
1194
+ changeFocus(fid);
1195
+ }
1196
+ else {
1197
+ changeFocus();
1198
+ }
1199
+ // --- 延长 100 秒是为了响应 100 毫秒的动画 ---
1200
+ if (!task.list[taskId]) {
1201
+ // --- 可能这时候 task 已经被结束了 ---
1202
+ return true;
1203
+ }
1204
+ task.list[taskId].forms[formId].vapp.unmount();
1205
+ task.list[taskId].forms[formId].vapp._container.remove();
1206
+ delete task.list[taskId].forms[formId];
1207
+ // --- 移除 form 的 style ---
1208
+ dom.removeStyle(taskId, 'form', formId);
1209
+ // --- 触发 formRemoved 事件 ---
1210
+ core.trigger('formRemoved', taskId, formId, title, icon);
1211
+ // --- 检测是否已经没有窗体了,如果没有了的话就要结束任务了 ---
1212
+ if (Object.keys(task.list[taskId].forms).length === 0) {
1213
+ task.end(taskId);
1214
+ }
1215
+ }, 100);
1216
+ return true;
1217
+ }
1218
+ else {
1219
+ return false;
1220
+ }
1221
+ }
1222
+
1223
+ /**
1224
+ * --- 根据任务 id 和 form id 获取 IForm 对象 ---
1225
+ * @param taskId 任务 id
1226
+ * @param formId 窗体 id
1227
+ */
1228
+ function getForm(taskId?: number, formId?: number): types.IForm | null {
1229
+ if (!taskId) {
1230
+ return null;
1231
+ }
1232
+ /** --- 当前的 task 对象 --- */
1233
+ const t = task.list[taskId];
1234
+ if (!t) {
1235
+ return null;
1236
+ }
1237
+ if (!formId) {
1238
+ return null;
1239
+ }
1240
+ const form = t.forms[formId];
1241
+ if (!form) {
1242
+ return null;
1243
+ }
1244
+ return form;
1245
+ }
1246
+
1247
+ /**
1248
+ * --- 直接创建一个窗体(需要验证传入 code、layout 等是否能成功创建) ---
1249
+ * @param opt 创建窗体的配置对象
1250
+ */
1251
+ export async function create(opt: string | types.IFormCreateOptions): Promise<number | types.IForm> {
1252
+ if (typeof opt === 'string') {
1253
+ return 0;
1254
+ }
1255
+ if (!opt.taskId) {
1256
+ return -1;
1257
+ }
1258
+ if (opt.path && (!opt.path.endsWith('/') || !opt.path?.startsWith('/'))) {
1259
+ return -2;
1260
+ }
1261
+ const taskId = opt.taskId;
1262
+ /** --- 当前的 task 对象 --- */
1263
+ const t = task.list[taskId];
1264
+ if (!t) {
1265
+ return -3;
1266
+ }
1267
+ let form: types.IForm | null = null;
1268
+ if (opt.formId) {
1269
+ if (!t.forms[opt.formId]) {
1270
+ return -4;
1271
+ }
1272
+ form = t.forms[opt.formId];
1273
+ }
1274
+ /** --- 是否创建置顶的窗体 --- */
1275
+ let topMost = opt.topMost ?? false;
1276
+ if (form?.vroot.cgTopMost) {
1277
+ topMost = true;
1278
+ }
1279
+ // --- 是否要给原窗体增加遮罩 ---
1280
+ if (opt.mask && form) {
1281
+ form.vroot.$refs.form.maskFor = 0;
1282
+ }
1283
+ /** --- 当前父 form 的路径(以 / 结尾)或 /(没有基路径的话) --- */
1284
+ const base: string = form ? form.vroot.cgPath : '/';
1285
+ /** --- 要新建的 form 的文件路径 --- */
1286
+ let filePath = '', newBase = '';
1287
+ if (opt.file) {
1288
+ filePath = clickgo.tool.urlResolve(base, opt.file);
1289
+ newBase = filePath.slice(0, filePath.lastIndexOf('/') + 1);
1290
+ }
1291
+ else {
1292
+ newBase = opt.path ?? base;
1293
+ }
1294
+
1295
+ /** --- 当前的 APP 对象 --- */
1296
+ const app: types.IApp = t.app;
1297
+ // --- 申请 formId ---
1298
+ const formId = ++info.lastId;
1299
+ // --- 注入的参数,屏蔽浏览器全局对象,注入新的对象 ---
1300
+ const invoke: Record<string, any> = {};
1301
+ if (clickgo.getSafe()) {
1302
+ invoke.window = undefined;
1303
+ invoke.loader = undefined;
1304
+ const ks = Object.getOwnPropertyNames(window);
1305
+ for (const k of ks) {
1306
+ if (k.includes('Event')) {
1307
+ continue;
1308
+ }
1309
+ if (k.includes('-')) {
1310
+ continue;
1311
+ }
1312
+ if (/^[0-9]+$/.test(k)) {
1313
+ continue;
1314
+ }
1315
+ if ([
1316
+ 'require',
1317
+ '__awaiter', 'eval', 'Math', 'Array', 'Blob', 'Infinity', 'parseInt', 'parseFloat', 'Promise', 'Date', 'JSON', 'fetch'].includes(k)) {
1318
+ continue;
1319
+ }
1320
+ invoke[k] = undefined;
1321
+ }
1322
+ // --- console ---
1323
+ invoke.console = {
1324
+ log: function(message?: any, ...optionalParams: any[]) {
1325
+ console.log(message, ...optionalParams);
1326
+ }
1327
+ };
1328
+ // --- loader ---
1329
+ invoke.loader = {
1330
+ require: function(paths: string | string[], files: Record<string, Blob | string>, opt?: {
1331
+ 'executed'?: Record<string, any>;
1332
+ 'map'?: Record<string, string>;
1333
+ 'dir'?: string;
1334
+ 'style'?: string;
1335
+ 'invoke'?: Record<string, any>;
1336
+ 'preprocess'?: (code: string, path: string) => string;
1337
+ }): any[] {
1338
+ return loader.require(paths, files, opt);
1339
+ }
1340
+ };
1341
+ // --- Object ---
1342
+ invoke.Object = {
1343
+ defineProperty: function(): void {
1344
+ return;
1345
+ },
1346
+ keys: function(o: any): string[] {
1347
+ return Object.keys(o);
1348
+ },
1349
+ assign: function(o: any, o2: any): any {
1350
+ if (o.controlName !== undefined) {
1351
+ return o;
1352
+ }
1353
+ return Object.assign(o, o2);
1354
+ }
1355
+ };
1356
+ invoke.navigator = {};
1357
+ if (navigator.clipboard) {
1358
+ invoke.navigator.clipboard = navigator.clipboard;
1359
+ }
1360
+ // --- ClickGo 相关 ---
1361
+ invoke.invokeClickgo = {
1362
+ getVersion: function(): string {
1363
+ return clickgo.getVersion();
1364
+ },
1365
+ getNative(): boolean {
1366
+ return clickgo.getNative();
1367
+ },
1368
+ getSafe(): boolean {
1369
+ return clickgo.getSafe();
1370
+ },
1371
+ getCdn(): string {
1372
+ return clickgo.getCdn();
1373
+ },
1374
+ 'control': {
1375
+ read: function(blob: Blob): Promise<false | types.TControl> {
1376
+ return clickgo.control.read(blob);
1377
+ }
1378
+ },
1379
+ 'core': {
1380
+ 'config': clickgo.core.config,
1381
+ initModules: function(names: string | string[]): Promise<number> {
1382
+ return clickgo.core.initModules(names);
1383
+ },
1384
+ getModule: function(name: string): null | any {
1385
+ return clickgo.core.getModule(name);
1386
+ },
1387
+ setSystemEventListener: function(
1388
+ name: types.TGlobalEvent,
1389
+ func: (...any: any) => void | Promise<void>,
1390
+ fid?: number
1391
+ ): void {
1392
+ clickgo.core.setSystemEventListener(name, func, fid ?? formId, taskId);
1393
+ },
1394
+ removeSystemEventListener: function(
1395
+ name: types.TGlobalEvent,
1396
+ fid?: number
1397
+ ): void {
1398
+ clickgo.core.removeSystemEventListener(name, fid ?? formId, taskId);
1399
+ },
1400
+ trigger: function(name: types.TGlobalEvent, param1: boolean | Error | string = '', param2: string = ''): void {
1401
+ if (!['formTitleChanged', 'formIconChanged', 'formStateMinChanged', 'formStateMaxChanged', 'formShowChanged'].includes(name)) {
1402
+ return;
1403
+ }
1404
+ clickgo.core.trigger(name, taskId, formId, param1, param2);
1405
+ },
1406
+ readApp: function(blob: Blob): Promise<false | types.IApp> {
1407
+ return clickgo.core.readApp(blob);
1408
+ },
1409
+ getAvailArea: function(): types.IAvailArea {
1410
+ return clickgo.core.getAvailArea();
1411
+ }
1412
+ },
1413
+ 'dom': {
1414
+ setGlobalCursor: function(type?: string): void {
1415
+ clickgo.dom.setGlobalCursor(type);
1416
+ },
1417
+ hasTouchButMouse: function(e: MouseEvent | TouchEvent | PointerEvent): boolean {
1418
+ return clickgo.dom.hasTouchButMouse(e);
1419
+ },
1420
+ getStyleCount: function(taskId: number, type: 'theme' | 'control' | 'form'): number {
1421
+ return clickgo.dom.getStyleCount(taskId, type);
1422
+ },
1423
+ getSize: function(el: HTMLElement): types.IDomSize {
1424
+ return clickgo.dom.getSize(el);
1425
+ },
1426
+ watchSize: function(
1427
+ el: HTMLElement,
1428
+ cb: (size: types.IDomSize) => Promise<void> | void,
1429
+ immediate: boolean = false
1430
+ ): types.IDomSize {
1431
+ return clickgo.dom.watchSize(el, cb, immediate, taskId);
1432
+ },
1433
+ unwatchSize: function(el: HTMLElement): void {
1434
+ clickgo.dom.unwatchSize(el, taskId);
1435
+ },
1436
+ clearWatchSize(): void {
1437
+ clickgo.dom.clearWatchSize(taskId);
1438
+ },
1439
+ watch: function(el: HTMLElement, cb: () => void, mode: 'child' | 'childsub' | 'style' | 'default' = 'default', immediate: boolean = false): void {
1440
+ clickgo.dom.watch(el, cb, mode, immediate, taskId);
1441
+ },
1442
+ unwatch: function(el: HTMLElement): void {
1443
+ clickgo.dom.unwatch(el, taskId);
1444
+ },
1445
+ clearWatch: function(): void {
1446
+ clickgo.dom.clearWatch(taskId);
1447
+ },
1448
+ watchStyle: function(
1449
+ el: HTMLElement,
1450
+ name: string | string[],
1451
+ cb: (name: string, value: string) => void,
1452
+ immediate: boolean = false
1453
+ ): void {
1454
+ clickgo.dom.watchStyle(el, name, cb, immediate);
1455
+ },
1456
+ isWatchStyle: function(el: HTMLElement): boolean {
1457
+ return clickgo.dom.isWatchStyle(el);
1458
+ },
1459
+ bindDown: function(oe: MouseEvent | TouchEvent, opt: types.IBindDownOptions) {
1460
+ clickgo.dom.bindDown(oe, opt);
1461
+ },
1462
+ bindGesture: function(e: MouseEvent | TouchEvent | WheelEvent | { 'x'?: number; 'y'?: number; }, opt: types.IBindGestureOptions): void {
1463
+ clickgo.dom.bindGesture(e, opt);
1464
+ },
1465
+ bindLong: function(
1466
+ e: MouseEvent | TouchEvent,
1467
+ long: (e: MouseEvent | TouchEvent) => void | Promise<void>
1468
+ ): void {
1469
+ clickgo.dom.bindLong(e, long);
1470
+ },
1471
+ bindDrag: function(e: MouseEvent | TouchEvent, opt: { 'el': HTMLElement; 'data'?: any; }): void {
1472
+ clickgo.dom.bindDrag(e, opt);
1473
+ },
1474
+ 'is': clickgo.dom.is,
1475
+ bindMove: function(e: MouseEvent | TouchEvent, opt: types.IBindMoveOptions): types.IBindMoveResult {
1476
+ return clickgo.dom.bindMove(e, opt);
1477
+ },
1478
+ bindResize: function(e: MouseEvent | TouchEvent, opt: types.IBindResizeOptions): void {
1479
+ clickgo.dom.bindResize(e, opt);
1480
+ },
1481
+ findParentByData: function(el: HTMLElement, name: string): HTMLElement | null {
1482
+ return clickgo.dom.findParentByData(el, name);
1483
+ },
1484
+ findParentByClass: function(el: HTMLElement, name: string): HTMLElement | null {
1485
+ return clickgo.dom.findParentByClass(el, name);
1486
+ },
1487
+ siblings: function(el: HTMLElement): HTMLElement[] {
1488
+ return clickgo.dom.siblings(el);
1489
+ },
1490
+ siblingsData: function(el: HTMLElement, name: string): HTMLElement[] {
1491
+ return clickgo.dom.siblingsData(el, name);
1492
+ },
1493
+ fullscreen: function(): boolean {
1494
+ return clickgo.dom.fullscreen();
1495
+ }
1496
+ },
1497
+ 'form': {
1498
+ min: function(fid?: number): boolean {
1499
+ return clickgo.form.min(fid ?? formId);
1500
+ },
1501
+ max: function max(fid?: number): boolean {
1502
+ return clickgo.form.max(fid ?? formId);
1503
+ },
1504
+ close: function(fid?: number): boolean {
1505
+ return clickgo.form.close(fid ?? formId);
1506
+ },
1507
+ bindResize: function(e: MouseEvent | TouchEvent, border: types.TBorder): void {
1508
+ clickgo.form.bindResize(e, border);
1509
+ },
1510
+ bindDrag: function(e: MouseEvent | TouchEvent): void {
1511
+ clickgo.form.bindDrag(e);
1512
+ },
1513
+ getTaskId: function(fid: number): number {
1514
+ return clickgo.form.getTaskId(fid);
1515
+ },
1516
+ get: function(fid: number): types.IFormInfo | null {
1517
+ return clickgo.form.get(fid);
1518
+ },
1519
+ send: function(fid: number, obj: Record<string, any>): void {
1520
+ obj.taskId = taskId;
1521
+ obj.formId = formId;
1522
+ clickgo.form.send(fid, obj);
1523
+ },
1524
+ getList: function(tid: number): Record<string, types.IFormInfo> {
1525
+ return clickgo.form.getList(tid);
1526
+ },
1527
+ changeFocus: function(fid: number = 0): void {
1528
+ clickgo.form.changeFocus(fid);
1529
+ },
1530
+ getMaxZIndexID: function(out?: {
1531
+ 'taskIds'?: number[];
1532
+ 'formIds'?: number[];
1533
+ }): number | null {
1534
+ return clickgo.form.getMaxZIndexID(out);
1535
+ },
1536
+ getRectByBorder: function(border: types.TBorder): { 'width': number; 'height': number; 'left': number; 'top': number; } {
1537
+ return clickgo.form.getRectByBorder(border);
1538
+ },
1539
+ showCircular: function(x: number, y: number): void {
1540
+ clickgo.form.showCircular(x, y);
1541
+ },
1542
+ moveRectangle: function(border: types.TBorder): void {
1543
+ clickgo.form.moveRectangle(border);
1544
+ },
1545
+ showRectangle: function(x: number, y: number, border: types.TBorder): void {
1546
+ clickgo.form.showRectangle(x, y, border);
1547
+ },
1548
+ hideRectangle: function(): void {
1549
+ clickgo.form.hideRectangle();
1550
+ },
1551
+ showDrag: function(): void {
1552
+ clickgo.form.showDrag();
1553
+ },
1554
+ moveDrag: function(opt: types.IMoveDragOptions): void {
1555
+ clickgo.form.moveDrag(opt);
1556
+ },
1557
+ hideDrag: function(): void {
1558
+ clickgo.form.hideDrag();
1559
+ },
1560
+ notify: function(opt: types.INotifyOptions): number {
1561
+ return clickgo.form.notify(opt);
1562
+ },
1563
+ notifyProgress: function(notifyId: number, per: number): void {
1564
+ clickgo.form.notifyProgress(notifyId, per);
1565
+ },
1566
+ hideNotify: function(notifyId: number): void {
1567
+ clickgo.form.hideNotify(notifyId);
1568
+ },
1569
+ showPop: function(el: HTMLElement, pop: HTMLElement | undefined, direction: 'h' | 'v' | MouseEvent | TouchEvent | { x: number; y: number; }, opt: { 'size'?: { width?: number; height?: number; }; 'null'?: boolean; } = {}): void {
1570
+ clickgo.form.showPop(el, pop, direction, opt);
1571
+ },
1572
+ hidePop: function(pop?: HTMLElement): void {
1573
+ clickgo.form.hidePop(pop);
1574
+ },
1575
+ create: function(opt: string | types.IFormCreateOptions): Promise<number | types.IForm> {
1576
+ if (typeof opt === 'string') {
1577
+ opt = {
1578
+ 'file': opt
1579
+ };
1580
+ }
1581
+ opt.taskId = taskId;
1582
+ opt.formId = formId;
1583
+ return clickgo.form.create(opt);
1584
+ },
1585
+ dialog: function(opt: string | types.IFormDialogOptions): Promise<string> {
1586
+ if (typeof opt === 'string') {
1587
+ opt = {
1588
+ 'content': opt
1589
+ };
1590
+ }
1591
+ opt.formId = formId;
1592
+ return clickgo.form.dialog(opt);
1593
+ },
1594
+ confirm: function(opt: string | types.IFormConfirmOptions): Promise<boolean | number> {
1595
+ if (typeof opt === 'string') {
1596
+ opt = {
1597
+ 'content': opt
1598
+ };
1599
+ }
1600
+ opt.formId = formId;
1601
+ return clickgo.form.confirm(opt);
1602
+ },
1603
+ setTopMost: function(top: boolean, opt: types.IFormSetTopMostOptions = {}): void {
1604
+ opt.taskId = taskId;
1605
+ opt.formId = formId;
1606
+ clickgo.form.setTopMost(top, opt);
1607
+ },
1608
+ flash: function(fid?: number): void {
1609
+ clickgo.form.flash(fid ?? formId, taskId);
1610
+ },
1611
+ show: function(fid?: number): void {
1612
+ clickgo.form.show(fid ?? formId, taskId);
1613
+ },
1614
+ hide: function(fid?: number): void {
1615
+ clickgo.form.hide(fid ?? formId, taskId);
1616
+ }
1617
+ },
1618
+ 'fs': {
1619
+ getContent: function(
1620
+ path: string,
1621
+ options: any = {}
1622
+ ): Promise<Blob | string | null> {
1623
+ if (!options.files) {
1624
+ options.files = t.files;
1625
+ }
1626
+ if (!options.current) {
1627
+ options.current = t.path;
1628
+ }
1629
+ return clickgo.fs.getContent(path, options);
1630
+ },
1631
+ putContent: function(path: string, data: string | Buffer, options: any = {}) {
1632
+ if (!options.current) {
1633
+ options.current = t.path;
1634
+ }
1635
+ return clickgo.fs.putContent(path, data, options);
1636
+ },
1637
+ readLink: function(path: string, options: any = {}): Promise<string | null> {
1638
+ if (!options.current) {
1639
+ options.current = t.path;
1640
+ }
1641
+ return clickgo.fs.readLink(path, options);
1642
+ },
1643
+ symlink: function(fPath: string, linkPath: string, options: any = {}): Promise<boolean> {
1644
+ if (!options.current) {
1645
+ options.current = t.path;
1646
+ }
1647
+ return clickgo.fs.symlink(fPath, linkPath, options);
1648
+ },
1649
+ unlink: function(path: string, options: any = {}): Promise<boolean> {
1650
+ if (!options.current) {
1651
+ options.current = t.path;
1652
+ }
1653
+ return clickgo.fs.unlink(path, options);
1654
+ },
1655
+ stats: function(path: string, options: any = {}): Promise<types.IStats | null> {
1656
+ if (!options.files) {
1657
+ options.files = t.files;
1658
+ }
1659
+ if (!options.current) {
1660
+ options.current = t.path;
1661
+ }
1662
+ return clickgo.fs.stats(path, options);
1663
+ },
1664
+ isDir: function(path: string, options: any = {}): Promise<types.IStats | false> {
1665
+ if (!options.files) {
1666
+ options.files = t.files;
1667
+ }
1668
+ if (!options.current) {
1669
+ options.current = t.path;
1670
+ }
1671
+ return clickgo.fs.isDir(path, options);
1672
+ },
1673
+ isFile: function(path: string, options: any = {}): Promise<types.IStats | false> {
1674
+ if (!options.files) {
1675
+ options.files = t.files;
1676
+ }
1677
+ if (!options.current) {
1678
+ options.current = t.path;
1679
+ }
1680
+ return clickgo.fs.isFile(path, options);
1681
+ },
1682
+ mkdir: function(path: string, mode?: number, options: any = {}): Promise<boolean> {
1683
+ if (!options.current) {
1684
+ options.current = t.path;
1685
+ }
1686
+ return clickgo.fs.mkdir(path, mode, options);
1687
+ },
1688
+ rmdir: function(path: string, options: any = {}): Promise<boolean> {
1689
+ if (!options.current) {
1690
+ options.current = t.path;
1691
+ }
1692
+ return clickgo.fs.rmdir(path, options);
1693
+ },
1694
+ rmdirDeep: function(path: string, options: any = {}): Promise<boolean> {
1695
+ if (!options.current) {
1696
+ options.current = t.path;
1697
+ }
1698
+ return clickgo.fs.rmdirDeep(path, options);
1699
+ },
1700
+ chmod: function(path: string, mod: string | number, options: any = {}): Promise<boolean> {
1701
+ if (!options.current) {
1702
+ options.current = t.path;
1703
+ }
1704
+ return clickgo.fs.chmod(path, mod, options);
1705
+ },
1706
+ rename(oldPath: string, newPath: string, options: any = {}): Promise<boolean> {
1707
+ if (!options.current) {
1708
+ options.current = t.path;
1709
+ }
1710
+ return clickgo.fs.rename(oldPath, newPath, options);
1711
+ },
1712
+ readDir(path: string, options: any = {}): Promise<types.IDirent[]> {
1713
+ if (!options.files) {
1714
+ options.files = t.files;
1715
+ }
1716
+ if (!options.current) {
1717
+ options.current = t.path;
1718
+ }
1719
+ return clickgo.fs.readDir(path, options);
1720
+ },
1721
+ copyFolder(from: string, to: string, options: any = {}): Promise<number> {
1722
+ if (!options.current) {
1723
+ options.current = t.path;
1724
+ }
1725
+ return clickgo.fs.copyFolder(from, to, options);
1726
+ },
1727
+ copyFile(src: string, dest: string, options: any = {}): Promise<boolean> {
1728
+ if (!options.current) {
1729
+ options.current = t.path;
1730
+ }
1731
+ return clickgo.fs.copyFile(src, dest, options);
1732
+ }
1733
+ },
1734
+ 'native': {
1735
+ getListeners: function(): Array<{ 'id': number; 'name': string; 'once': boolean; 'taskId'?: number; }> {
1736
+ return clickgo.native.getListeners();
1737
+ },
1738
+ send: function(
1739
+ name: string,
1740
+ param?: string,
1741
+ handler?: (param?: string) => void | Promise<void>
1742
+ ): number {
1743
+ return clickgo.native.send(name, param, handler, taskId);
1744
+ },
1745
+ on: function(
1746
+ name: string,
1747
+ handler: (param?: string) => void | Promise<void>,
1748
+ id?: number,
1749
+ once: boolean = false
1750
+ ): void {
1751
+ clickgo.native.on(name, handler, id, once, taskId);
1752
+ },
1753
+ once: function(
1754
+ name: string,
1755
+ handler: (param?: string) => void | Promise<void>,
1756
+ id?: number
1757
+ ): void {
1758
+ clickgo.native.once(name, handler, id, taskId);
1759
+ },
1760
+ off: function(name: string, handler: (param?: string) => void | Promise<void>): void {
1761
+ clickgo.native.off(name, handler, taskId);
1762
+ },
1763
+ clearListener: function(): void {
1764
+ clickgo.native.clearListener(taskId);
1765
+ }
1766
+ },
1767
+ 'task': {
1768
+ onFrame: function(fun: () => void | Promise<void>, opt: {
1769
+ 'scope'?: 'form' | 'task';
1770
+ 'count'?: number;
1771
+ 'taskId'?: number;
1772
+ 'formId'?: number;
1773
+ } = {}): number {
1774
+ opt.taskId = taskId;
1775
+ opt.formId = formId;
1776
+ return clickgo.task.onFrame(fun, opt);
1777
+ },
1778
+ offFrame: function(ft: number, opt: {
1779
+ 'taskId'?: number;
1780
+ } = {}): void {
1781
+ opt.taskId = taskId;
1782
+ clickgo.task.offFrame(ft, opt);
1783
+ },
1784
+ get: function(tid: number): types.ITaskInfo | null {
1785
+ return clickgo.task.get(tid);
1786
+ },
1787
+ getList: function(): Record<string, types.ITaskInfo> {
1788
+ return clickgo.task.getList();
1789
+ },
1790
+ run: function(url: string, opt: types.ITaskRunOptions = {}): Promise<number> {
1791
+ opt.taskId = taskId;
1792
+ return clickgo.task.run(url, opt);
1793
+ },
1794
+ end: function(tid: number): boolean {
1795
+ return clickgo.task.end(tid ?? taskId);
1796
+ },
1797
+ loadLocaleData: function(lang: string, data: Record<string, any>, pre: string = ''): void {
1798
+ clickgo.task.loadLocaleData(lang, data, pre, taskId);
1799
+ },
1800
+ loadLocale: function(lang: string, path: string): Promise<boolean> {
1801
+ return clickgo.task.loadLocale(lang, path, taskId, formId);
1802
+ },
1803
+ clearLocale: function(): void {
1804
+ clickgo.task.clearLocale(taskId);
1805
+ },
1806
+ setLocale: function(lang: string, path: string): Promise<boolean> {
1807
+ return clickgo.task.setLocale(lang, path, taskId, formId);
1808
+ },
1809
+ setLocaleLang: function(lang: string): void {
1810
+ clickgo.task.setLocaleLang(lang, taskId);
1811
+ },
1812
+ clearLocaleLang: function(): void {
1813
+ clickgo.task.clearLocaleLang(taskId);
1814
+ },
1815
+ createTimer: function(
1816
+ fun: () => void | Promise<void>,
1817
+ delay: number,
1818
+ opt: types.ICreateTimerOptions = {}
1819
+ ): number {
1820
+ opt.taskId = taskId;
1821
+ if (!opt.formId) {
1822
+ opt.formId = formId;
1823
+ }
1824
+ return clickgo.task.createTimer(fun, delay, opt);
1825
+ },
1826
+ removeTimer: function(timer: number): void {
1827
+ clickgo.task.removeTimer(timer, taskId);
1828
+ },
1829
+ sleep: function(fun: () => void | Promise<void>, delay: number): number {
1830
+ return clickgo.task.sleep(fun, delay, taskId, formId);
1831
+ },
1832
+ systemTaskInfo: clickgo.task.systemTaskInfo,
1833
+ setSystem: function(fid?: number): boolean {
1834
+ return clickgo.task.setSystem(fid ?? formId, taskId);
1835
+ },
1836
+ clearSystem: function(): boolean {
1837
+ return clickgo.task.clearSystem(taskId);
1838
+ }
1839
+ },
1840
+ 'theme': {
1841
+ read: function(blob: Blob): Promise<types.ITheme | false> {
1842
+ return clickgo.theme.read(blob);
1843
+ },
1844
+ load: async function(theme?: types.ITheme): Promise<boolean> {
1845
+ if (!theme) {
1846
+ return false;
1847
+ }
1848
+ return clickgo.theme.load(theme, taskId);
1849
+ },
1850
+ remove: function(name: string): Promise<void> {
1851
+ return clickgo.theme.remove(name, taskId);
1852
+ },
1853
+ clear: function(): Promise<void> {
1854
+ return clickgo.theme.clear(taskId);
1855
+ },
1856
+ setGlobal: function(theme: types.ITheme): Promise<void> {
1857
+ return clickgo.theme.setGlobal(theme);
1858
+ },
1859
+ clearGlobal: function(): void {
1860
+ clickgo.theme.clearGlobal();
1861
+ }
1862
+ },
1863
+ 'tool': {
1864
+ blob2ArrayBuffer: function(blob: Blob): Promise<ArrayBuffer> {
1865
+ return clickgo.tool.blob2ArrayBuffer(blob);
1866
+ },
1867
+ clone: function(obj: Record<string, any> | any[]): any[] | any {
1868
+ return clickgo.tool.clone(obj);
1869
+ },
1870
+ sleep: function(ms: number = 0): Promise<boolean> {
1871
+ return clickgo.tool.sleep(ms);
1872
+ },
1873
+ purify: function(text: string): string {
1874
+ return clickgo.tool.purify(text);
1875
+ },
1876
+ createObjectURL: function(object: Blob): string {
1877
+ return clickgo.tool.createObjectURL(object, taskId);
1878
+ },
1879
+ revokeObjectURL: function(url: string): void {
1880
+ clickgo.tool.revokeObjectURL(url, taskId);
1881
+ },
1882
+ rand: function(min: number, max: number): number {
1883
+ return clickgo.tool.rand(min, max);
1884
+ },
1885
+ 'RANDOM_N': clickgo.tool.RANDOM_N,
1886
+ 'RANDOM_U': clickgo.tool.RANDOM_U,
1887
+ 'RANDOM_L': clickgo.tool.RANDOM_L,
1888
+ 'RANDOM_UN': clickgo.tool.RANDOM_UN,
1889
+ 'RANDOM_LN': clickgo.tool.RANDOM_LN,
1890
+ 'RANDOM_LU': clickgo.tool.RANDOM_LU,
1891
+ 'RANDOM_LUN': clickgo.tool.RANDOM_LUN,
1892
+ 'RANDOM_V': clickgo.tool.RANDOM_V,
1893
+ 'RANDOM_LUNS': clickgo.tool.RANDOM_LUNS,
1894
+ random: function(length: number = 8, source: string = clickgo.tool.RANDOM_LN, block: string = ''): string {
1895
+ return clickgo.tool.random(length, source, block);
1896
+ },
1897
+ getBoolean: function(param: boolean | string | number): boolean {
1898
+ return clickgo.tool.getBoolean(param);
1899
+ },
1900
+ escapeHTML: function(html: string): string {
1901
+ return clickgo.tool.escapeHTML(html);
1902
+ },
1903
+ request: function(url: string, opt: types.IRequestOptions): Promise<null | any> {
1904
+ return clickgo.tool.request(url, opt);
1905
+ },
1906
+ parseUrl: function(url: string): ILoaderUrl {
1907
+ return clickgo.tool.parseUrl(url);
1908
+ },
1909
+ urlResolve: function(from: string, to: string): string {
1910
+ return clickgo.tool.urlResolve(from, to);
1911
+ },
1912
+ blob2Text: function(blob: Blob): Promise<string> {
1913
+ return clickgo.tool.blob2Text(blob);
1914
+ },
1915
+ blob2DataUrl: function(blob: Blob): Promise<string> {
1916
+ return clickgo.tool.blob2DataUrl(blob);
1917
+ },
1918
+ execCommand: function(ac: string): void {
1919
+ clickgo.tool.execCommand(ac);
1920
+ }
1921
+ },
1922
+ 'zip': {
1923
+ get: function(data?: types.TZipInputFileFormat) {
1924
+ return clickgo.zip.get(data);
1925
+ }
1926
+ }
1927
+ };
1928
+ }
1929
+ else {
1930
+ invoke.invokeClickgo = clickgo;
1931
+ }
1932
+ // --- 代码预处理 ---
1933
+ const preprocess = clickgo.getSafe() ? function(code: string, path: string): string {
1934
+ const exec = /eval\W/.exec(code);
1935
+ if (exec) {
1936
+ notify({
1937
+ 'title': 'Error',
1938
+ 'content': `The "eval" is prohibited.\nFile: "${path}".`,
1939
+ 'type': 'danger'
1940
+ });
1941
+ return '';
1942
+ }
1943
+ return code;
1944
+ } : undefined;
1945
+ // --- 获取要定义的控件列表 ---
1946
+ const components = await control.init(t.id, formId, newBase, preprocess, invoke);
1947
+ if (!components) {
1948
+ if (form?.vroot.$refs.form.maskFor !== undefined) {
1949
+ form.vroot.$refs.form.maskFor = undefined;
1950
+ }
1951
+ return -5;
1952
+ }
1953
+ // --- 获取 style、layout ---
1954
+ let style = opt.style;
1955
+ let layout: string | undefined = opt.layout;
1956
+ if (filePath) {
1957
+ if (!filePath.startsWith('/package/')) {
1958
+ return -6;
1959
+ }
1960
+ const file = filePath.slice(8);
1961
+ const layoutFile = app.files[file + '.xml'] as string;
1962
+ if (layoutFile) {
1963
+ layout = layoutFile.replace(/^\ufeff/, '');
1964
+ }
1965
+ const styleFile = app.files[file + '.css'] as string;
1966
+ if (styleFile) {
1967
+ style = styleFile.replace(/^\ufeff/, '');
1968
+ }
1969
+ }
1970
+ if (layout === undefined) {
1971
+ if (form?.vroot.$refs.form.maskFor !== undefined) {
1972
+ form.vroot.$refs.form.maskFor = undefined;
1973
+ }
1974
+ return -7;
1975
+ }
1976
+ // --- 准备相关变量 ---
1977
+ let data: Record<string, any> = {};
1978
+ let methods: any = {};
1979
+ let computed: any = {};
1980
+ let watch = {};
1981
+ let beforeCreate: (() => void) | undefined = undefined;
1982
+ let created: (() => void) | undefined = undefined;
1983
+ let beforeMount: (() => void) | undefined = undefined;
1984
+ let mounted: ((data?: Record<string, any>) => void | Promise<void>) | undefined = undefined;
1985
+ let beforeUpdate: (() => void) | undefined = undefined;
1986
+ let updated: (() => void) | undefined = undefined;
1987
+ let beforeUnmount: (() => void) | undefined = undefined;
1988
+ let unmounted: (() => void) | undefined = undefined;
1989
+ let receive: ((obj: Record<string, any>) => void) | undefined = undefined;
1990
+ // --- 检测是否有 js ---
1991
+ let expo = opt.code;
1992
+ if (filePath?.startsWith('/package/') && app.files[filePath.slice(8) + '.js']) {
1993
+ const file = filePath.slice(8);
1994
+ if (app.files[file + '.js']) {
1995
+ app.files['/invoke/clickgo.js'] = `module.exports = invokeClickgo;`;
1996
+ expo = loader.require(file, app.files, {
1997
+ 'dir': '/',
1998
+ 'invoke': invoke,
1999
+ 'preprocess': preprocess,
2000
+ 'map': {
2001
+ 'clickgo': '/invoke/clickgo'
2002
+ }
2003
+ })[0];
2004
+ }
2005
+ }
2006
+ if (expo) {
2007
+ data = expo.data ?? {};
2008
+ methods = expo.methods || {};
2009
+ computed = expo.computed || {};
2010
+ watch = expo.watch || {};
2011
+ beforeCreate = expo.beforeCreate;
2012
+ created = expo.created;
2013
+ beforeMount = expo.beforeMount;
2014
+ mounted = expo.mounted;
2015
+ beforeUpdate = expo.beforeUpdate;
2016
+ updated = expo.updated;
2017
+ beforeUnmount = expo.beforeUnmount;
2018
+ unmounted = expo.unmounted;
2019
+ receive = expo.receive;
2020
+ }
2021
+ // --- 应用样式表 ---
2022
+ let prep = '';
2023
+ if (style) {
2024
+ // --- 将 style 中的 tag 标签转换为 class,如 button 变为 .tag-button,然后将 class 进行标准程序,添加 prep 进行区分隔离 ---
2025
+ const r = tool.stylePrepend(style);
2026
+ prep = r.prep;
2027
+ style = await tool.styleUrl2DataUrl(newBase, r.style, app.files);
2028
+ }
2029
+ // --- 要创建的 form 的 layout 所有标签增加 cg 前缀,并增加新的 class 为 tag-xxx ---
2030
+ layout = tool.purify(layout);
2031
+ // --- 标签增加 cg- 前缀,增加 class 为 tag-xxx ---
2032
+ layout = tool.layoutAddTagClassAndReTagName(layout, true);
2033
+ // --- 给所有控件传递窗体的 focus 信息 ---
2034
+ layout = tool.layoutInsertAttr(layout, ':cg-focus=\'cgFocus\'', {
2035
+ 'include': [/^cg-.+/]
2036
+ });
2037
+ // --- 给 layout 的 class 增加前置 ---
2038
+ const prepList = ['cg-task' + opt.taskId.toString() + '_'];
2039
+ if (prep !== '') {
2040
+ prepList.push(prep);
2041
+ }
2042
+ layout = tool.layoutClassPrepend(layout, prepList);
2043
+ // --- 给 event 增加包裹 ---
2044
+ layout = tool.eventsAttrWrap(layout);
2045
+ // --- 给 touchstart 增加 .passive 防止 [Violation] Added non-passive event listener to a scroll-blocking ---
2046
+ /*
2047
+ layout = layout.replace(/@(touchstart|touchmove|wheel)=/g, '@$1.passive=');
2048
+ layout = layout.replace(/@(touchstart|touchmove|wheel)\.not=/g, '@$1=');
2049
+ */
2050
+ // --- 插入 HTML 到 Element ---
2051
+ elements.list.insertAdjacentHTML('beforeend', `<div class="cg-form-wrap" data-form-id="${formId.toString()}" data-task-id="${opt.taskId.toString()}"></div>`);
2052
+ // --- 获取刚才的 form wrap element 对象 ---
2053
+ const el: HTMLElement = elements.list.children.item(elements.list.children.length - 1) as HTMLElement;
2054
+ // --- 创建窗体对象 ---
2055
+ // --- 初始化系统初始 data ---
2056
+ computed.taskId = {
2057
+ get: function(): number {
2058
+ return taskId;
2059
+ },
2060
+ set: function(): void {
2061
+ notify({
2062
+ 'title': 'Error',
2063
+ 'content': `The software tries to modify the system variable "taskId".\nPath: ${this.cgPath}`,
2064
+ 'type': 'danger'
2065
+ });
2066
+ return;
2067
+ }
2068
+ };
2069
+ computed.formId = {
2070
+ get: function(): number {
2071
+ return formId;
2072
+ },
2073
+ set: function(): void {
2074
+ notify({
2075
+ 'title': 'Error',
2076
+ 'content': `The software tries to modify the system variable "formId".\nPath: ${this.cgPath}`,
2077
+ 'type': 'danger'
2078
+ });
2079
+ return;
2080
+ }
2081
+ };
2082
+ computed.controlName = {
2083
+ get: function(): string {
2084
+ return 'root';
2085
+ },
2086
+ set: function(): void {
2087
+ notify({
2088
+ 'title': 'Error',
2089
+ 'content': `The software tries to modify the system variable "controlName".\nPath: ${this.cgPath}`,
2090
+ 'type': 'danger'
2091
+ });
2092
+ return;
2093
+ }
2094
+ };
2095
+ data._cgFocus = false;
2096
+ computed.cgFocus = {
2097
+ get: function(): number {
2098
+ return this._cgFocus;
2099
+ },
2100
+ set: function(): void {
2101
+ notify({
2102
+ 'title': 'Error',
2103
+ 'content': `The software tries to modify the system variable "cgFocus".\nPath: ${this.cgPath}`,
2104
+ 'type': 'danger'
2105
+ });
2106
+ return;
2107
+ }
2108
+ };
2109
+ computed.cgPath = {
2110
+ get: function(): string {
2111
+ return newBase;
2112
+ },
2113
+ set: function(): void {
2114
+ notify({
2115
+ 'title': 'Error',
2116
+ 'content': `The software tries to modify the system variable "cgPath".\nPath: ${this.cgPath}`,
2117
+ 'type': 'danger'
2118
+ });
2119
+ return;
2120
+ }
2121
+ };
2122
+ computed.cgPrep = {
2123
+ get: function(): string {
2124
+ return prep;
2125
+ },
2126
+ set: function(): void {
2127
+ notify({
2128
+ 'title': 'Error',
2129
+ 'content': `The software tries to modify the system variable "cgPrep".\nPath: ${this._cgPath}`,
2130
+ 'type': 'danger'
2131
+ });
2132
+ return;
2133
+ }
2134
+ };
2135
+ data._cgCustomZIndex = false;
2136
+ computed.cgCustomZIndex = {
2137
+ get: function(): number {
2138
+ return this._cgCustomZIndex;
2139
+ },
2140
+ set: function(): void {
2141
+ notify({
2142
+ 'title': 'Error',
2143
+ 'content': `The software tries to modify the system variable "cgCustomZIndex".\nPath: ${this.cgPath}`,
2144
+ 'type': 'danger'
2145
+ });
2146
+ return;
2147
+ }
2148
+ };
2149
+ if (topMost) {
2150
+ data._cgTopMost = true;
2151
+ }
2152
+ else {
2153
+ data._cgTopMost = false;
2154
+ }
2155
+ computed.cgTopMost = {
2156
+ get: function(): number {
2157
+ return this._cgTopMost;
2158
+ },
2159
+ set: function(): void {
2160
+ notify({
2161
+ 'title': 'Error',
2162
+ 'content': `The software tries to modify the system variable "cgTopMost".\nPath: ${this.cgPath}`,
2163
+ 'type': 'danger'
2164
+ });
2165
+ return;
2166
+ }
2167
+ };
2168
+ // --- 预设 computed ---
2169
+ computed.cgLocale = function(this: types.IVForm): string {
2170
+ if (task.list[this.taskId].locale.lang === '') {
2171
+ return core.config.locale;
2172
+ }
2173
+ return task.list[this.taskId].locale.lang;
2174
+ };
2175
+ // --- 获取语言 ---
2176
+ computed.l = function(this: types.IVForm): (key: string) => string {
2177
+ return (key: string): string => {
2178
+ return task.list[this.taskId].locale.data[this.cgLocale]?.[key] ?? task.list[this.taskId].locale.data['en']?.[key] ?? 'LocaleError';
2179
+ };
2180
+ };
2181
+ // --- layout 中 :class 的转义 ---
2182
+ methods.cgClassPrepend = function(this: types.IVForm, cla: any): string {
2183
+ if (typeof cla !== 'string') {
2184
+ return cla;
2185
+ }
2186
+ /*
2187
+ if (cla.startsWith('cg-')) {
2188
+ return cla;
2189
+ }
2190
+ */
2191
+ return `cg-task${this.taskId}_${cla} ${this.cgPrep}${cla}`;
2192
+ };
2193
+ // --- 判断当前事件可否执行 ---
2194
+ methods.cgAllowEvent = function(this: types.IVForm, e: MouseEvent | TouchEvent | KeyboardEvent): boolean {
2195
+ return dom.allowEvent(e);
2196
+ };
2197
+ // --- 窗体接收 send 事件 ---
2198
+ methods.cgReceive = function(obj: Record<string, any>) {
2199
+ receive?.call(this, obj);
2200
+ };
2201
+ // --- 挂载 style ---
2202
+ if (style) {
2203
+ // --- 窗体的 style ---
2204
+ dom.pushStyle(taskId, style, 'form', formId);
2205
+ }
2206
+ // --- 创建 app 对象 ---
2207
+ const rtn: {
2208
+ 'vapp': types.IVueApp;
2209
+ 'vroot': types.IVForm;
2210
+ } = await new Promise(function(resolve) {
2211
+ const vapp = Vue.createApp({
2212
+ 'template': layout!.replace(/^<cg-form/, '<cg-form ref="form"'),
2213
+ 'data': function() {
2214
+ return tool.clone(data);
2215
+ },
2216
+ 'methods': methods,
2217
+ 'computed': computed,
2218
+ 'watch': watch,
2219
+
2220
+ 'beforeCreate': beforeCreate,
2221
+ 'created': created,
2222
+ 'beforeMount': beforeMount,
2223
+ 'mounted': async function(this: types.IVForm) {
2224
+ await this.$nextTick();
2225
+ // --- 判断是否有 icon,对 icon 进行第一次读取 ---
2226
+ // --- 为啥要在这搞,因为 form 控件中读取,将可能导致下方的 formCreate 事件获取不到 icon 图标 ---
2227
+ // --- 而如果用延迟的方式获取,将可能导致 changeFocus 的窗体焦点事件先于 formCreate 触发 ---
2228
+ if (this.$refs.form.icon !== '') {
2229
+ const icon = await clickgo.fs.getContent(this.$refs.form.icon);
2230
+ this.$refs.form.iconData = (icon instanceof Blob) ? await clickgo.tool.blob2DataUrl(icon) : '';
2231
+ }
2232
+ // --- 完成 ---
2233
+ resolve({
2234
+ 'vapp': vapp,
2235
+ 'vroot': this
2236
+ });
2237
+ },
2238
+ 'beforeUpdate': beforeUpdate,
2239
+ 'updated': async function(this: types.IVue) {
2240
+ await this.$nextTick();
2241
+ updated?.call(this);
2242
+ },
2243
+ 'beforeUnmount': beforeUnmount,
2244
+ 'unmounted': unmounted,
2245
+ });
2246
+ vapp.config.errorHandler = function(err: Error, vm: types.IVForm, info: string): void {
2247
+ notify({
2248
+ 'title': 'Runtime Error',
2249
+ 'content': `Message: ${err.message}\ntask id: ${vm.taskId}\nForm id: ${vm.formId}`,
2250
+ 'type': 'danger'
2251
+ });
2252
+ core.trigger('error', vm.taskId, vm.formId, err, info);
2253
+ };
2254
+ // --- 挂载控件对象到 vapp ---
2255
+ for (const key in components) {
2256
+ vapp.component(key, components[key]);
2257
+ }
2258
+ vapp.mount(el);
2259
+ });
2260
+ // --- 创建 form 信息对象 ---
2261
+ const nform: types.IForm = {
2262
+ 'id': formId,
2263
+ 'vapp': rtn.vapp,
2264
+ 'vroot': rtn.vroot,
2265
+ 'events': {}
2266
+ };
2267
+ // --- 挂载 form ---
2268
+ t.forms[formId] = nform;
2269
+ // --- 检测 mask ---
2270
+ if (opt.mask && form) {
2271
+ form.vroot.$refs.form.maskFor = formId;
2272
+ nform.vroot.$refs.form.maskFrom = form.id;
2273
+ }
2274
+ // --- 执行 mounted ---
2275
+ await tool.sleep(34);
2276
+ if (mounted) {
2277
+ try {
2278
+ await mounted.call(rtn.vroot, opt.data);
2279
+ }
2280
+ catch (err: any) {
2281
+ if (nform?.vroot.$refs.form.maskFor !== undefined) {
2282
+ nform.vroot.$refs.form.maskFor = undefined;
2283
+ }
2284
+ core.trigger('error', rtn.vroot.taskId, rtn.vroot.formId, err, 'Create form mounted error.');
2285
+ t.forms[formId] = undefined as any;
2286
+ delete t.forms[formId];
2287
+ rtn.vapp.unmount();
2288
+ rtn.vapp._container.remove();
2289
+ dom.removeStyle(rtn.vroot.taskId, 'form', rtn.vroot.formId);
2290
+ return -8;
2291
+ }
2292
+ }
2293
+ // --- 将窗体居中 ---
2294
+ const area = core.getAvailArea();
2295
+ if (!rtn.vroot.$refs.form.stateMaxData) {
2296
+ if (rtn.vroot.$refs.form.left === -1) {
2297
+ rtn.vroot.$refs.form.setPropData('left', (area.width - rtn.vroot.$el.offsetWidth) / 2);
2298
+ }
2299
+ if (rtn.vroot.$refs.form.top === -1) {
2300
+ rtn.vroot.$refs.form.setPropData('top', (area.height - rtn.vroot.$el.offsetHeight) / 2);
2301
+ }
2302
+ }
2303
+ if (rtn.vroot.$refs.form.zIndex !== -1) {
2304
+ rtn.vroot._cgCustomZIndex = true;
2305
+ }
2306
+ if (rtn.vroot.$refs.form.$data.show !== false) {
2307
+ rtn.vroot.$refs.form.$data.showData = true;
2308
+ }
2309
+ // --- 触发 formCreated 事件 ---
2310
+ core.trigger('formCreated', taskId, formId, rtn.vroot.$refs.form.title, rtn.vroot.$refs.form.iconData);
2311
+ // --- 绑定获取焦点事件 ---
2312
+ changeFocus(formId);
2313
+ return nform;
2314
+ }
2315
+
2316
+ /**
2317
+ * --- 显示一个 dialog ---
2318
+ * @param opt 选项或者一段文字
2319
+ */
2320
+ export function dialog(opt: string | types.IFormDialogOptions): Promise<string> {
2321
+ return new Promise(function(resolve) {
2322
+ if (typeof opt === 'string') {
2323
+ opt = {
2324
+ 'content': opt
2325
+ };
2326
+ }
2327
+ const formId = opt.formId;
2328
+ if (!formId) {
2329
+ resolve('');
2330
+ return;
2331
+ }
2332
+ const taskId = getTaskId(formId);
2333
+ const t = task.list[taskId];
2334
+ if (!t) {
2335
+ resolve('');
2336
+ return;
2337
+ }
2338
+ const locale = t.forms[formId].vroot.cgLocale;
2339
+ if (opt.buttons === undefined) {
2340
+ opt.buttons = [info.locale[locale]?.ok ?? info.locale['en'].ok];
2341
+ }
2342
+ create({
2343
+ 'taskId': taskId,
2344
+ 'formId': formId,
2345
+ 'layout': `<form title="${opt.title ?? 'dialog'}" width="auto" height="auto" :min="false" :max="false" :resize="false" border="${opt.title ? 'normal' : 'plain'}" direction="v"><dialog :buttons="buttons" @select="select"${opt.direction ? ` direction="${opt.direction}"` : ''}>${opt.content}</dialog></form>`,
2346
+ 'code': {
2347
+ data: {
2348
+ 'buttons': opt.buttons
2349
+ },
2350
+ methods: {
2351
+ select: function(this: types.IVForm, button: string) {
2352
+ const event = {
2353
+ 'go': true,
2354
+ preventDefault: function() {
2355
+ this.go = false;
2356
+ }
2357
+ };
2358
+ (opt as types.IFormDialogOptions).select?.(event as unknown as Event, button);
2359
+ if (event.go) {
2360
+ close(this.formId);
2361
+ resolve(button);
2362
+ }
2363
+ }
2364
+ }
2365
+ },
2366
+ 'mask': true
2367
+ }).catch((e) => {
2368
+ throw e;
2369
+ });
2370
+ });
2371
+ }
2372
+
2373
+ /**
2374
+ * --- 显示一个 confirm ---
2375
+ * @param opt 选项或者一段文字
2376
+ */
2377
+ export async function confirm(opt: string | types.IFormConfirmOptions): Promise<boolean | number> {
2378
+ if (typeof opt === 'string') {
2379
+ opt = {
2380
+ 'content': opt
2381
+ };
2382
+ }
2383
+ const formId = opt.formId;
2384
+ if (!formId) {
2385
+ return false;
2386
+ }
2387
+ const taskId = getTaskId(formId);
2388
+ const t = task.list[taskId];
2389
+ if (!t) {
2390
+ return false;
2391
+ }
2392
+ const locale = t.forms[formId].vroot.cgLocale;
2393
+ const buttons = [info.locale[locale]?.yes ?? info.locale['en'].yes, info.locale[locale]?.no ?? info.locale['en'].no];
2394
+ if (opt.cancel) {
2395
+ buttons.push(info.locale[locale]?.cancel ?? info.locale['en'].cancel);
2396
+ }
2397
+ const res = await dialog({
2398
+ 'formId': formId,
2399
+
2400
+ 'content': opt.content,
2401
+ 'buttons': buttons
2402
+ });
2403
+ if (res === (info.locale[locale]?.yes ?? info.locale['en'].yes)) {
2404
+ return true;
2405
+ }
2406
+ if (res === (info.locale[locale]?.cancel ?? info.locale['en'].cancel)) {
2407
+ return 0;
2408
+ }
2409
+ return false;
2410
+ }
2411
+
2412
+ /**
2413
+ * --- 设置窗体置顶和取消置顶 ---
2414
+ * @param top true: 置顶, false: 不置顶
2415
+ * @param opt 选项
2416
+ */
2417
+ export function setTopMost(top: boolean, opt: types.IFormSetTopMostOptions = {}): void {
2418
+ const form = getForm(opt.taskId, opt.formId);
2419
+ if (!form) {
2420
+ return;
2421
+ }
2422
+ form.vroot.$data._cgCustomZIndex = false;
2423
+ if (top) {
2424
+ // --- 置顶 ---
2425
+ form.vroot.$data._cgTopMost = true;
2426
+ if (!form.vroot.cgFocus) {
2427
+ changeFocus(form.id);
2428
+ }
2429
+ else {
2430
+ form.vroot.$refs.form.setPropData('zIndex', ++info.topLastZIndex);
2431
+ }
2432
+ }
2433
+ else {
2434
+ // --- 取消置顶 ---
2435
+ form.vroot.$data._cgTopMost = false;
2436
+ form.vroot.$refs.form.setPropData('zIndex', ++info.lastZIndex);
2437
+ }
2438
+ }
2439
+
2440
+ /**
2441
+ * --- 让窗体闪烁 ---
2442
+ * @param formId 要闪烁的窗体 id,必填,但 App 模式下留空为当前窗体
2443
+ * @param taskId 所属的 taskId,必填,但 App 模式下无效
2444
+ */
2445
+ export function flash(formId?: number, taskId?: number): void {
2446
+ const form = getForm(taskId, formId);
2447
+ if (!form) {
2448
+ return;
2449
+ }
2450
+ if (!form.vroot.cgFocus) {
2451
+ changeFocus(form.id);
2452
+ }
2453
+ if (form.vroot.$refs.form?.flashTimer) {
2454
+ clearTimeout(form.vroot.$refs.form.flashTimer);
2455
+ form.vroot.$refs.form.flashTimer = undefined;
2456
+ }
2457
+ form.vroot.$refs.form.flashTimer = setTimeout(() => {
2458
+ if (form.vroot.$refs.form) {
2459
+ form.vroot.$refs.form.flashTimer = undefined;
2460
+ }
2461
+ }, 1000);
2462
+ // --- 触发 formFlash 事件 ---
2463
+ core.trigger('formFlash', taskId, formId);
2464
+ }
2465
+
2466
+ /**
2467
+ * --- 让窗体显示 ---
2468
+ * @param formId 要显示的窗体 id,App 模式下留空为当前窗体
2469
+ * @param taskId 所属的 taskId,App 模式下无效
2470
+ */
2471
+ export function show(formId?: number, taskId?: number): void {
2472
+ const form = getForm(taskId, formId);
2473
+ if (!form) {
2474
+ return;
2475
+ }
2476
+ form.vroot.$refs.form.$data.showData = true;
2477
+ }
2478
+
2479
+ /**
2480
+ * --- 让窗体隐藏 ---
2481
+ * @param formId 要隐藏的窗体 id,App 模式下留空为当前窗体
2482
+ * @param taskId 所属的 taskId,App 模式下无效
2483
+ */
2484
+ export function hide(formId?: number, taskId?: number): void {
2485
+ const form = getForm(taskId, formId);
2486
+ if (!form) {
2487
+ return;
2488
+ }
2489
+ form.vroot.$refs.form.$data.showData = false;
2490
+ }
2491
+
2492
+ // --- 绑定 resize 事件 ---
2493
+ window.addEventListener('resize', function(): void {
2494
+ // --- 触发 screenResize 事件 ---
2495
+ task.refreshSystemPosition(); // 会在里面自动触发 screenResize 事件
2496
+ });