clickgo 3.16.16 → 3.16.18

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/app/demo/form/method/native/native.js +26 -0
  3. package/dist/app/demo/form/method/native/native.xml +2 -0
  4. package/dist/clickgo.d.ts +17 -0
  5. package/dist/clickgo.js +1 -1
  6. package/dist/control/arteditor.cgc +0 -0
  7. package/dist/control/box.cgc +0 -0
  8. package/dist/control/captcha.cgc +0 -0
  9. package/dist/control/common.cgc +0 -0
  10. package/dist/control/desc.cgc +0 -0
  11. package/dist/control/drawer.cgc +0 -0
  12. package/dist/control/echarts.cgc +0 -0
  13. package/dist/control/form.cgc +0 -0
  14. package/dist/control/iconview.cgc +0 -0
  15. package/dist/control/jodit.cgc +0 -0
  16. package/dist/control/map.cgc +0 -0
  17. package/dist/control/monaco.cgc +0 -0
  18. package/dist/control/mpegts.cgc +0 -0
  19. package/dist/control/nav.cgc +0 -0
  20. package/dist/control/page.cgc +0 -0
  21. package/dist/control/pdf.cgc +0 -0
  22. package/dist/control/property.cgc +0 -0
  23. package/dist/control/qrcode.cgc +0 -0
  24. package/dist/control/table.cgc +0 -0
  25. package/dist/control/task.cgc +0 -0
  26. package/dist/control/tplink.cgc +0 -0
  27. package/dist/control/tuieditor.cgc +0 -0
  28. package/dist/control/tuiviewer.cgc +0 -0
  29. package/dist/control/xterm.cgc +0 -0
  30. package/dist/index.d.ts +51 -0
  31. package/dist/lib/control.d.ts +53 -0
  32. package/dist/lib/core.d.ts +47 -0
  33. package/dist/lib/dom.d.ts +74 -0
  34. package/dist/lib/dom.js +7 -7
  35. package/dist/lib/form.d.ts +222 -0
  36. package/dist/lib/fs.d.ts +35 -0
  37. package/dist/lib/fs.js +2 -2
  38. package/dist/lib/native.d.ts +36 -0
  39. package/dist/lib/native.js +8 -0
  40. package/dist/lib/storage.d.ts +6 -0
  41. package/dist/lib/task.d.ts +32 -0
  42. package/dist/lib/task.js +7 -1
  43. package/dist/lib/theme.d.ts +8 -0
  44. package/dist/lib/tool.d.ts +120 -0
  45. package/dist/lib/zip.d.ts +40 -0
  46. package/dist/theme/blue.cgt +0 -0
  47. package/dist/theme/byterun.cgt +0 -0
  48. package/dist/theme/light.cgt +0 -0
  49. package/package.json +7 -5
  50. package/dist/clickgo.ts +0 -68
  51. package/dist/index.ts +0 -282
  52. package/dist/lib/control.ts +0 -751
  53. package/dist/lib/core.ts +0 -1145
  54. package/dist/lib/dom.ts +0 -2728
  55. package/dist/lib/form.ts +0 -3829
  56. package/dist/lib/fs.ts +0 -1324
  57. package/dist/lib/native.ts +0 -236
  58. package/dist/lib/storage.ts +0 -229
  59. package/dist/lib/task.ts +0 -2160
  60. package/dist/lib/theme.ts +0 -199
  61. package/dist/lib/tool.ts +0 -1278
  62. package/dist/lib/zip.ts +0 -444
  63. package/eslint.config.js +0 -22
package/dist/lib/form.ts DELETED
@@ -1,3829 +0,0 @@
1
- /**
2
- * Copyright 2024 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 fs from './fs';
24
- import * as native from './native';
25
-
26
- /** --- 当前有焦点的窗体 id --- */
27
- let focusId: number | null = null;
28
-
29
- /** --- form 相关信息 --- */
30
- const info: {
31
- /** --- 最后一个窗体 id --- */
32
- lastId: number;
33
- /** --- 最后一个 panel id --- */
34
- lastPanelId: number;
35
- /** --- 最后一个置底窗体层级,1000(一千)开始 --- */
36
- bottomLastZIndex: number;
37
- /** --- 最后一个窗体层级,1000000(百万)开始 --- */
38
- lastZIndex: number;
39
- /** --- 最后一个置顶窗体层级,100000000(一亿)开始 --- */
40
- topLastZIndex: number;
41
- /** --- 用到的语言包 --- */
42
- locale: Record<string, {
43
- 'ok': string;
44
- 'yes': string;
45
- 'no': string;
46
- 'cancel': string;
47
- 'search': string;
48
- 'confirmExitStep': string;
49
- }>;
50
- } = {
51
- 'lastId': 0,
52
- 'lastPanelId': 0,
53
- 'bottomLastZIndex': 999,
54
- 'lastZIndex': 999999,
55
- 'topLastZIndex': 99999999,
56
- 'locale': {
57
- 'en': {
58
- 'ok': 'OK',
59
- 'yes': 'Yes',
60
- 'no': 'No',
61
- 'cancel': 'Cancel',
62
- 'search': 'Search',
63
- 'confirmExitStep': 'This operation will exit the current process. Are you sure you want to exit?'
64
- },
65
- 'sc': {
66
- 'ok': '好',
67
- 'yes': '是',
68
- 'no': '否',
69
- 'cancel': '取消',
70
- 'search': '搜索',
71
- 'confirmExitStep': '此操作将退出当前流程,确定退出吗?'
72
- },
73
- 'tc': {
74
- 'ok': '好',
75
- 'yes': '是',
76
- 'no': '否',
77
- 'cancel': '取消',
78
- 'search': '檢索',
79
- 'confirmExitStep': '此操作將結束目前的流程,確定退出嗎?'
80
- },
81
- 'ja': {
82
- 'ok': '好',
83
- 'yes': 'はい',
84
- 'no': 'いいえ',
85
- 'cancel': 'キャンセル',
86
- 'search': '検索',
87
- 'confirmExitStep': 'この操作は現在のプロセスを終了します。本当に終了しますか?'
88
- },
89
- 'ko': {
90
- 'ok': '확인',
91
- 'yes': '예',
92
- 'no': '아니오',
93
- 'cancel': '취소',
94
- 'search': '검색',
95
- 'confirmExitStep': '이 작업은 현재 프로세스를 종료합니다. 종료하시겠습니까?'
96
- },
97
- 'th': {
98
- 'ok': 'ตกลง',
99
- 'yes': 'ใช่',
100
- 'no': 'ไม่',
101
- 'cancel': 'ยกเลิก',
102
- 'search': 'ค้นหา',
103
- 'confirmExitStep': 'การดำเนินการนี้จะออกจากกระบวนการปัจจุบัน ยืนยันที่จะออกไหม?'
104
- },
105
- 'es': {
106
- 'ok': 'Aceptar',
107
- 'yes': 'Sí',
108
- 'no': 'No',
109
- 'cancel': 'Cancelar',
110
- 'search': 'Buscar',
111
- 'confirmExitStep': 'Esta operación cerrará el proceso actual. ¿Estás seguro de que quieres salir?'
112
- },
113
- 'de': {
114
- 'ok': 'OK',
115
- 'yes': 'Ja',
116
- 'no': 'Nein',
117
- 'cancel': 'Abbrechen',
118
- 'search': 'Suchen',
119
- 'confirmExitStep': 'Diese Aktion beendet den aktuellen Prozess. Möchten Sie wirklich beenden?'
120
- },
121
- 'fr': {
122
- 'ok': 'OK',
123
- 'yes': 'Oui',
124
- 'no': 'Non',
125
- 'cancel': 'Annuler',
126
- 'search': 'Rechercher',
127
- 'confirmExitStep': 'Cette opération va quitter le processus en cours. Êtes-vous sûr de vouloir quitter ?'
128
- },
129
- 'pt': {
130
- 'ok': 'OK',
131
- 'yes': 'Sim',
132
- 'no': 'Não',
133
- 'cancel': 'Cancelar',
134
- 'search': 'Buscar',
135
- 'confirmExitStep': 'Esta operação encerrará o processo atual. Você tem certeza de que deseja sair?'
136
- },
137
- 'ru': {
138
- 'ok': 'OK',
139
- 'yes': 'Да',
140
- 'no': 'Нет',
141
- 'cancel': 'Отмена',
142
- 'search': 'Поиск',
143
- 'confirmExitStep': 'Эта операция завершит текущий процесс. Вы уверены, что хотите выйти?'
144
- },
145
- 'vi': {
146
- 'ok': 'OK',
147
- 'yes': 'Có',
148
- 'no': 'Không',
149
- 'cancel': 'Hủy bỏ',
150
- 'search': 'Tìm kiếm',
151
- 'confirmExitStep': 'Thao tác này sẽ thoát khỏi quy trình hiện tại. Bạn có chắc chắn muốn thoát không?'
152
- }
153
- }
154
- };
155
-
156
- /** --- Panel 与 Form 共用的抽象类 --- */
157
- abstract class AbstractCommon {
158
-
159
- /** --- 当前文件路径 --- */
160
- public get filename(): string {
161
- // --- require 时系统自动在继承类中重写本函数 ---
162
- return '';
163
- }
164
-
165
- /** --- 当前控件的名字 --- */
166
- public get controlName(): string {
167
- return 'root';
168
- }
169
-
170
- public set controlName(v: string) {
171
- notify({
172
- 'title': 'Error',
173
- 'content': `The software tries to modify the system variable "controlName".\nPath: ${this.filename}`,
174
- 'type': 'danger'
175
- });
176
- return;
177
- }
178
-
179
- /** --- 当前的任务 ID --- */
180
- public get taskId(): number {
181
- // --- 系统 invoke 继承时重写本函数 ---
182
- return 0;
183
- }
184
-
185
- /** --- 当前的窗体 ID --- */
186
- public get formId(): number {
187
- // --- 窗体创建时 create 系统自动重写本函数 ---
188
- return 0;
189
- }
190
-
191
- public set formFocus(b: boolean) {
192
- notify({
193
- 'title': 'Error',
194
- 'content': `The software tries to modify the system variable "formFocus".\nPath: ${this.filename}`,
195
- 'type': 'danger'
196
- });
197
- }
198
-
199
- /** --- 当前文件的包内路径不以 / 结尾 --- */
200
- public get path(): string {
201
- // --- 将在初始化时系统自动重写本函数 ---
202
- return '';
203
- }
204
-
205
- /** --- 样式独占前缀 --- */
206
- public get prep(): string {
207
- // --- 将在初始化时系统自动重写本函数 ---
208
- return '';
209
- }
210
-
211
- /** --- 当前的语言 --- */
212
- public get locale(): string {
213
- return task.list[this.taskId].locale.lang || core.config.locale;
214
- }
215
-
216
- /**
217
- * --- 获取语言内容 ---
218
- */
219
- public get l(): (key: string, data?: string[]) => string {
220
- return (key: string, data?: string[]): string => {
221
- const loc = task.list[this.taskId].locale.data[this.locale]?.[key] ?? task.list[this.taskId].locale.data['en']?.[key] ?? '[LocaleError]' + key;
222
- if (!data) {
223
- return loc;
224
- }
225
- let i: number = -1;
226
- return loc.replace(/\?/g, function() {
227
- ++i;
228
- if (!data[i]) {
229
- return '';
230
- }
231
- return data[i];
232
- });
233
- };
234
- }
235
-
236
- /** --- layout 中 :class 的转义 --- */
237
- public get classPrepend(): (cla: any) => string {
238
- return (cla: any): string => {
239
- if (typeof cla !== 'string') {
240
- return cla;
241
- }
242
- // --- 没有单独的窗体样式,则只应用任务级样式 ---
243
- return `cg-task${this.taskId}_${cla}${this.prep ? (' ' + this.prep + cla) : ''}`;
244
- };
245
- }
246
-
247
- /**
248
- * --- 监视变动 ---
249
- * @param name 监视的属性
250
- * @param cb 回调
251
- * @param opt 参数
252
- */
253
- public watch<T extends this, TK extends keyof T, TR>(
254
- name: TK | (() => TR),
255
- cb: (val: T[TK] & TR, old: T[TK] & TR) => void | Promise<void>,
256
- opt: {
257
- 'immediate'?: boolean;
258
- 'deep'?: boolean;
259
- } = {}
260
- ): () => void {
261
- return (this as any).$watch(name, cb, opt);
262
- }
263
-
264
- /**
265
- * --- 获取 refs 情况 ---
266
- */
267
- public get refs(): Record<string, HTMLElement & control.AbstractControl & Record<string, any>> {
268
- return (this as any).$refs;
269
- }
270
-
271
- /**
272
- * --- 等待渲染 ---
273
- */
274
- public get nextTick(): () => Promise<void> {
275
- return (this as any).$nextTick;
276
- }
277
-
278
- /**
279
- * --- 判断当前事件可否执行 ---
280
- * @param e 鼠标、触摸、键盘事件
281
- */
282
- public allowEvent(e: MouseEvent | TouchEvent | KeyboardEvent): boolean {
283
- return dom.allowEvent(e);
284
- }
285
-
286
- /**
287
- * --- 触发系统方法 ---
288
- * @param name 方法名
289
- * @param param1 参数1
290
- * @param param2 参数2
291
- */
292
- public trigger(name: types.TGlobalEvent, param1: boolean | Error | string = '', param2: string = ''): void {
293
- if (!['formTitleChanged', 'formIconChanged', 'formStateMinChanged', 'formStateMaxChanged', 'formShowChanged'].includes(name)) {
294
- return;
295
- }
296
- core.trigger(name, this.taskId, this.formId, param1, param2);
297
- }
298
-
299
- /**
300
- * --- 给一个窗体发送一个对象,不会知道成功与失败状态 ---
301
- * @param fid formId 要接收对象的 form id
302
- * @param obj 要发送的对象
303
- */
304
- public send(fid: number, obj: Record<string, any>): void {
305
- obj.taskId = this.taskId;
306
- obj.formId = this.formId;
307
- send(fid, obj);
308
- }
309
-
310
- // --- 控件响应事件,都可由用户重写 ---
311
-
312
- public onBeforeCreate(): void | Promise<void> {
313
- return;
314
- }
315
-
316
- public onCreated(): void | Promise<void> {
317
- return;
318
- }
319
-
320
- public onBeforeMount(): void | Promise<void> {
321
- return;
322
- }
323
-
324
- public onBeforeUpdate(): void | Promise<void> {
325
- return;
326
- }
327
-
328
- public onUpdated(): void | Promise<void> {
329
- return;
330
- }
331
-
332
- public onBeforeUnmount(): void | Promise<void> {
333
- return;
334
- }
335
-
336
- public onUnmounted(): void | Promise<void> {
337
- return;
338
- }
339
-
340
- }
341
-
342
- /** --- Panel 控件抽象类 --- */
343
- export abstract class AbstractPanel extends AbstractCommon {
344
-
345
- /** --- 当前的 panel ID --- */
346
- public get panelId(): number {
347
- // --- panel 创建时 createPanel 自动重写本函数 ---
348
- return 0;
349
- }
350
-
351
- /** --- 当前 panel 所在窗体的窗体对象,系统会在创建时重写本函数 --- */
352
- public get rootForm(): AbstractForm & Record<string, any> {
353
- return {} as any;
354
- }
355
-
356
- /** --- 当前 panel 所在的 panel control 对象,系统会在创建时重写本函数 --- */
357
- public get rootPanel(): control.AbstractControl & Record<string, any> {
358
- return {} as any;
359
- }
360
-
361
- /** --- 获取母窗体的 formHash --- */
362
- public get formHash(): string {
363
- return this.rootForm.formHash;
364
- }
365
-
366
- /** --- 设置母窗体的 formHash --- */
367
- public set formHash(fh: string) {
368
- this.rootForm.formHash = fh;
369
- }
370
-
371
- /** --- 获取 form 的 formhash with data 值 --- */
372
- public get formHashData(): Record<string, any> {
373
- return this.rootForm.formHashData;
374
- }
375
-
376
- public set formHashData(v: Record<string, any>) {
377
- this.rootForm.formHashData = v;
378
- }
379
-
380
- /** --- 将母窗体的 form hash 回退 --- */
381
- public async formHashBack(): Promise<void> {
382
- await this.rootForm.formHashBack();
383
- }
384
-
385
- /** --- 发送一段数据到自己这个 panel 控件,本质上也是调用的 panel 控件的 send 方法,主要用来实现发送给跳转后的 panel --- */
386
- public sendToRootPanel(data: Record<string, any>): void {
387
- this.rootPanel.send(data);
388
- }
389
-
390
- /** --- 母窗体进入 form hash 为源的步进条 --- */
391
- public async enterStep(list: Array<{
392
- /** --- 步骤 hash,第一个必须为当前 hash --- */
393
- 'value': string;
394
- 'label'?: string;
395
- 'icon'?: string;
396
- 'desc'?: string;
397
- }>): Promise<boolean> {
398
- return this.rootForm.enterStep(list);
399
- }
400
-
401
- /** --- 目窗体完成当前步骤 --- */
402
- public async doneStep(): Promise<void> {
403
- await this.rootForm.doneStep();
404
- }
405
-
406
- /** --- 当前的 nav(若有)传递过来的 qs --- */
407
- public qs: Record<string, string> = {};
408
-
409
- /** --- 确定不再使用 qs 时可调用此方法清空,这样再次通过相同 qs 进入本 panel 依然会响应 qschange 事件 --- */
410
- public clearQs(): void {
411
- this.qs = {};
412
- }
413
-
414
- /** --- 当前窗体是否是焦点 --- */
415
- public get formFocus(): boolean {
416
- return this.rootForm.formFocus ?? false;
417
- }
418
-
419
- public onShow(e: types.IAbstractPanelShowEvent): void | Promise<void>;
420
- public onShow(): void {
421
- return;
422
- }
423
-
424
- public onShowed(): void | Promise<void>;
425
- /** --- panel 已经完全显示后所要执行的 --- */
426
- public onShowed(): void {
427
- return;
428
- }
429
-
430
- public onHide(): void | Promise<void>;
431
- public onHide(): void {
432
- return;
433
- }
434
-
435
- public onMounted(): void | Promise<void>;
436
- public onMounted(): void {
437
- return;
438
- }
439
-
440
- /** --- 接收 send 传递过来的 data 数据(是 panel 控件的 send,不是 form 的 send) --- */
441
- public onReceive(data: Record<string, any>): void | Promise<void>;
442
- public onReceive(): void {
443
- return;
444
- }
445
-
446
- /** --- qs 变动时调用,如果只是用来做 qs 数据处理,建议用此方法 --- */
447
- public onQsChange(): void | Promise<void> {
448
- return;
449
- }
450
-
451
- /** --- 无论是 show 还是 qschange 都会触发,优先触发 show 或 qschange 事件本身,之后触发这个 --- */
452
- public onQsChangeShow(e: types.IAbstractPanelQsChangeShowEvent): void | Promise<void>;
453
- public onQsChangeShow(): void {
454
- return;
455
- }
456
-
457
- }
458
-
459
- /** --- 窗体的抽象类 --- */
460
- export abstract class AbstractForm extends AbstractCommon {
461
-
462
- /** --- 当前窗体是否和 native 的实体窗体大小、状态同步 --- */
463
- public isNativeSync: boolean = false;
464
-
465
- // --- 以下为窗体有,但 control 没有 ---
466
-
467
- /** --- 当前是否完全创建完毕 --- */
468
- public isReady: boolean = false;
469
-
470
- /** --- 获取 form 的 hash 值,不是浏览器的 hash --- */
471
- public get formHash(): string {
472
- return '';
473
- }
474
-
475
- public set formHash(v: string) {
476
- // --- 会进行重写 ---
477
- }
478
-
479
- /** --- 获取 form 的 formhash with data 值 --- */
480
- public get formHashData(): Record<string, any> {
481
- return {};
482
- }
483
-
484
- public set formHashData(v: Record<string, any>) {
485
- // --- 会进行重写 ---
486
- }
487
-
488
- /** --- 是否是置顶 --- */
489
- public get topMost(): boolean {
490
- // --- 将在初始化时系统自动重写本函数 ---
491
- return false;
492
- }
493
-
494
- public set topMost(v: boolean) {
495
- // --- 会进行重写 ---
496
- }
497
-
498
- /** --- 是否是置底 --- */
499
- public get bottomMost(): boolean {
500
- // --- 将在初始化时系统自动重写本函数 ---
501
- return false;
502
- }
503
-
504
- public set bottomMost(v: boolean) {
505
- // --- 会进行重写 ---
506
- }
507
-
508
- /**
509
- * --- 是否在本窗体上显示遮罩层 ---
510
- */
511
- public get isMask(): boolean {
512
- return !task.list[this.taskId].runtime.dialogFormIds.length ||
513
- task.list[this.taskId].runtime.dialogFormIds[task.list[this.taskId].runtime.dialogFormIds.length - 1]
514
- === this.formId ? false : true;
515
- }
516
-
517
- /** --- 当前窗体是否是焦点 --- */
518
- public get formFocus(): boolean {
519
- // --- _formFocus 在初始化时由系统设置 ---
520
- return (this as any)._formFocus;
521
- }
522
-
523
- /** --- 当前窗体是否显示在任务栏 --- */
524
- public get showInSystemTask(): boolean {
525
- // --- 将在初始化时系统自动重写本函数 ---
526
- return false;
527
- }
528
-
529
- public set showInSystemTask(v: boolean) {
530
- // --- 会进行重写 ---
531
- }
532
-
533
- /** --- 将在 form 完全装载完后执行,如果已经装载完则立即执行 --- */
534
- public ready(cb: () => void | Promise<void>): void {
535
- // --- 会进行重写 ---
536
- cb() as any;
537
- }
538
-
539
- /** --- form hash 回退 --- */
540
- public async formHashBack(): Promise<void> {
541
- const v = this as any;
542
- if (Date.now() - v.$data._lastFormHashData > 300) {
543
- v.$data._formHashData = {};
544
- }
545
- if (!v.$data._historyHash.length) {
546
- if (v.$data._formHash) {
547
- if (this.inStep) {
548
- if (!await clickgo.form.confirm({
549
- 'taskId': this.taskId,
550
- 'content': info.locale[this.locale].confirmExitStep
551
- })) {
552
- return;
553
- }
554
- this._inStep = false;
555
- this.refs.form.stepHide();
556
- }
557
- v.$data._formHash = '';
558
- core.trigger('formHashChange', this.taskId, this.formId, '', v.$data._formHashData);
559
- return;
560
- }
561
- return;
562
- }
563
- const parent = v.$data._historyHash[v.$data._historyHash.length - 1];
564
- if (this.inStep) {
565
- if (this._stepValues.includes(parent)) {
566
- this.refs.form.stepValue = parent;
567
- }
568
- else {
569
- if (!await clickgo.form.confirm({
570
- 'taskId': this.taskId,
571
- 'content': info.locale[this.locale].confirmExitStep
572
- })) {
573
- return;
574
- }
575
- this._inStep = false;
576
- this.refs.form.stepHide();
577
- }
578
- }
579
- v.$data._formHash = parent;
580
- v.$data._historyHash.splice(-1);
581
- core.trigger('formHashChange', this.taskId, this.formId, parent, v.$data._formHashData);
582
- }
583
-
584
- /** --- 发送一段数据到 panel 控件,本质上也是调用的 panel 控件的 send 方法 --- */
585
- public sendToPanel(panel: control.AbstractControl & Record<string, any>, data: Record<string, any>): void {
586
- panel.send(data);
587
- }
588
-
589
- /** --- 覆盖整个窗体的 loading(实际值) --- */
590
- private _loading: boolean = false;
591
-
592
- /** --- 覆盖整个窗体的 loading --- */
593
- public get loading(): boolean {
594
- return this._loading;
595
- }
596
-
597
- public set loading(val: boolean) {
598
- if (this.lockLoading) {
599
- return;
600
- }
601
- this._loading = val;
602
- }
603
-
604
- /** --- 是否阻止任何人修改 loading --- */
605
- public lockLoading: boolean = false;
606
-
607
- // --- step 相关 ---
608
-
609
- private _inStep: boolean = false;
610
-
611
- /** --- 当前是否在 step 环节中 --- */
612
- public get inStep(): boolean {
613
- return this._inStep;
614
- }
615
-
616
- /** --- 序列化后的 step value 值 --- */
617
- private readonly _stepValues: string[] = [];
618
-
619
- /** --- 进入 form hash 为源的步进条 --- */
620
- public async enterStep(list: Array<{
621
- /** --- 步骤 hash,第一个必须为当前 hash --- */
622
- 'value': string;
623
- 'label'?: string;
624
- 'icon'?: string;
625
- 'desc'?: string;
626
- }>): Promise<boolean> {
627
- if (this._inStep) {
628
- return false;
629
- }
630
- if (list[0].value !== this.formHash) {
631
- return false;
632
- }
633
- // --- 进入当前页面步骤 ---
634
- this._inStep = true;
635
- this._stepValues.length = 0;
636
- for (const item of list) {
637
- this._stepValues.push(item.value);
638
- }
639
- this.refs.form.stepData = list;
640
- this.refs.form.stepValue = this.formHash;
641
- await this.nextTick();
642
- this.refs.form.stepShow();
643
- return true;
644
- }
645
-
646
- /** --- 更新步进条,用于动态改变某个项的 hash 值时使用 --- */
647
- public updateStep(index: number, value: string): boolean {
648
- if (this._inStep) {
649
- return false;
650
- }
651
- if (this._stepValues[index] === undefined) {
652
- return false;
653
- }
654
- this._stepValues[index] = value;
655
- return true;
656
- }
657
-
658
- /** --- 完成当前步骤条 --- */
659
- public async doneStep(): Promise<void> {
660
- if (!this._inStep) {
661
- return;
662
- }
663
- this._inStep = false;
664
- await this.refs.form.stepDone();
665
- }
666
-
667
- /** --- 当前是不是初次显示 --- */
668
- private _firstShow: boolean = true;
669
-
670
- /**
671
- * --- 显示窗体 ---
672
- */
673
- public show(): void {
674
- // --- 创建成功的窗体,可以直接显示 ---
675
- const v = this as unknown as types.IVue;
676
- if (this._firstShow) {
677
- this._firstShow = false;
678
- // --- 将窗体居中 ---
679
- const area = core.getAvailArea();
680
- if (!v.$refs.form.stateMaxData) {
681
- if (v.$refs.form.left === -1) {
682
- v.$refs.form.setPropData('left', (area.width - v.$el.offsetWidth) / 2);
683
- }
684
- if (v.$refs.form.top === -1) {
685
- v.$refs.form.setPropData('top', (area.height - v.$el.offsetHeight) / 2);
686
- }
687
- }
688
- v.$refs.form.$data.isShow = true;
689
- changeFocus(this.formId);
690
- }
691
- else {
692
- v.$refs.form.$data.isShow = true;
693
- }
694
- }
695
-
696
- /**
697
- * --- 显示独占的窗体 ---
698
- */
699
- public async showDialog(): Promise<string> {
700
- task.list[this.taskId].runtime.dialogFormIds.push(this.formId);
701
- this.show();
702
- this.topMost = true;
703
- return new Promise((resolve) => {
704
- (this as any).cgDialogCallback = () => {
705
- resolve(this.dialogResult);
706
- };
707
- });
708
- }
709
-
710
- /**
711
- * --- 让窗体隐藏 ---
712
- */
713
- public hide(): void {
714
- const v = this as any;
715
- v.$refs.form.$data.isShow = false;
716
- }
717
-
718
- /**
719
- * --- 关闭当前窗体 ---
720
- */
721
- public close(): void {
722
- close(this.formId);
723
- }
724
-
725
- /**
726
- * --- dialog mask 窗体返回值,在 close 之后会进行传导 ---
727
- */
728
- public dialogResult: string = '';
729
-
730
- // --- 窗体可以接收到的事件 ---
731
-
732
- public onMounted(data: Record<string, any>): void | Promise<void>;
733
- public onMounted(): void {
734
- return;
735
- }
736
-
737
- /** --- 接收 send 传递过来的 data 数据 --- */
738
- public onReceive(data: Record<string, any>): void | Promise<void>;
739
- public onReceive(): void {
740
- return;
741
- }
742
-
743
- /** --- 屏幕大小改变事件 --- */
744
- public onScreenResize(): void | Promise<void>;
745
- public onScreenResize(): void {
746
- return;
747
- }
748
-
749
- /** --- 系统配置变更事件 --- */
750
- public onConfigChanged<T extends keyof types.IConfig>(n: keyof types.IConfig, v: types.IConfig[T]): void;
751
- public onConfigChanged(): void {
752
- return;
753
- }
754
-
755
- /** --- 窗体创建事件 --- */
756
- public onFormCreated(
757
- taskId: number, formId: number, title: string, icon: string, showInSystemTask: boolean
758
- ): void | Promise<void>;
759
- public onFormCreated(): void {
760
- return;
761
- }
762
-
763
- /** --- 窗体销毁事件 */
764
- public onFormRemoved(taskId: number, formId: number, title: string, icon: string): void | Promise<void>;
765
- public onFormRemoved(): void {
766
- return;
767
- }
768
-
769
- /** --- 窗体标题改变事件 */
770
- public onFormTitleChanged(taskId: number, formId: number, title: string): void | Promise<void>;
771
- public onFormTitleChanged(): void {
772
- return;
773
- }
774
-
775
- /** --- 窗体图标改变事件 --- */
776
- public onFormIconChanged(taskId: number, formId: number, icon: string): void | Promise<void>;
777
- public onFormIconChanged(): void {
778
- return;
779
- }
780
-
781
- /** --- 窗体最小化状态改变事件 --- */
782
- public onFormStateMinChanged(taskId: number, formId: number, state: boolean): void | Promise<void>;
783
- public onFormStateMinChanged(): void {
784
- return;
785
- }
786
-
787
- /** --- 窗体最大化状态改变事件 --- */
788
- public onFormStateMaxChanged(taskId: number, formId: number, state: boolean): void | Promise<void>;
789
- public onFormStateMaxChanged(): void {
790
- return;
791
- }
792
-
793
- /** --- 窗体显示状态改变事件 --- */
794
- public onFormShowChanged(taskId: number, formId: number, state: boolean): void | Promise<void>;
795
- public onFormShowChanged(): void {
796
- return;
797
- }
798
-
799
- /** --- 窗体获得焦点事件 --- */
800
- public onFormFocused(taskId: number, formId: number): void | Promise<void>;
801
- public onFormFocused(): void {
802
- return;
803
- }
804
-
805
- /** --- 窗体丢失焦点事件 --- */
806
- public onFormBlurred(taskId: number, formId: number): void | Promise<void>;
807
- public onFormBlurred(): void {
808
- return;
809
- }
810
-
811
- /** --- 窗体闪烁事件 --- */
812
- public onFormFlash(taskId: number, formId: number): void | Promise<void>;
813
- public onFormFlash(): void {
814
- return;
815
- }
816
-
817
- /** --- 窗体是否显示在任务栏属性改变事件 --- */
818
- public onFormShowInSystemTaskChange(taskId: number, formId: number, value: boolean): void | Promise<void>;
819
- public onFormShowInSystemTaskChange(): void {
820
- return;
821
- }
822
-
823
- /** --- 窗体的 formHash 改变事件 --- */
824
- public onFormHashChange(
825
- taskId: number, formId: number, value: string, data: Record<string, any>
826
- ): void | Promise<void>;
827
- public onFormHashChange(): void {
828
- return;
829
- }
830
-
831
- /** --- 任务开始事件 --- */
832
- public onTaskStarted(taskId: number): void | Promise<void>;
833
- public onTaskStarted(): void {
834
- return;
835
- }
836
-
837
- /** --- 任务结束事件 --- */
838
- public onTaskEnded(taskId: number): void | Promise<void>;
839
- public onTaskEnded(): void {
840
- return;
841
- }
842
-
843
- /** --- launcher 文件夹名称修改事件 --- */
844
- public onLauncherFolderNameChanged(id: string, name: string): void | Promise<void>;
845
- public onLauncherFolderNameChanged(): void {
846
- return;
847
- }
848
-
849
- /** --- location hash 改变事件 --- */
850
- public onHashChanged(hash: string): void | Promise<void>;
851
- public onHashChanged(): void {
852
- return;
853
- }
854
-
855
- /** --- 键盘按下事件 --- */
856
- public onKeydown(e: KeyboardEvent): void | Promise<void>;
857
- public onKeydown(): void {
858
- return;
859
- }
860
-
861
- /** --- 键盘弹起事件 --- */
862
- public onKeyup(e: KeyboardEvent): void | Promise<void>;
863
- public onKeyup(): void {
864
- return;
865
- }
866
-
867
- }
868
-
869
- /** --- pop 相关信息 --- */
870
- const popInfo: {
871
- /** --- 当前显示的 pop 列表 --- */
872
- 'list': HTMLElement[];
873
- /** --- 当前显示的 pop 的母标签 --- */
874
- 'elList': HTMLElement[];
875
- /** --- 当前展示的 pop 托管方式(normal、click、hover) --- */
876
- 'wayList': Array<'normal' | 'click' | 'hover'>;
877
- /** --- 每个层弹出时的时间戳 --- */
878
- 'time': number[];
879
- /** --- pop 最后一个层级 --- */
880
- 'lastZIndex': number;
881
- } = {
882
- 'list': [],
883
- 'elList': [],
884
- 'wayList': [],
885
- 'time': [],
886
- 'lastZIndex': 0
887
- };
888
-
889
- export let simpleSystemTaskRoot: types.IVue;
890
- export let launcherRoot: types.IVue;
891
-
892
- /** --- 系统级 confirm 的用户回调函数 --- */
893
- let superConfirmHandler: undefined | ((result: boolean) => void | Promise<void>) = undefined;
894
-
895
- export const elements: {
896
- 'wrap': HTMLDivElement;
897
- 'list': HTMLDivElement;
898
- 'popList': HTMLDivElement;
899
- 'circular': HTMLDivElement;
900
- 'rectangle': HTMLDivElement;
901
- 'gesture': HTMLDivElement;
902
- 'drag': HTMLDivElement;
903
- 'notify': HTMLElement;
904
- 'alert': HTMLElement;
905
- 'simpletask': HTMLDivElement;
906
- 'launcher': HTMLDivElement;
907
- 'confirm': HTMLDivElement;
908
- 'init': () => void;
909
- } = {
910
- 'wrap': document.createElement('div'),
911
- 'list': document.createElement('div'),
912
- 'popList': document.createElement('div'),
913
- 'circular': document.createElement('div'),
914
- 'rectangle': document.createElement('div'),
915
- 'gesture': document.createElement('div'),
916
- 'drag': document.createElement('div'),
917
- 'notify': document.createElement('div'),
918
- 'alert': document.createElement('div'),
919
- 'simpletask': document.createElement('div'),
920
- 'launcher': document.createElement('div'),
921
- 'confirm': document.createElement('div'),
922
- 'init': function() {
923
- /** --- clickgo 所有的 div wrap --- */
924
- this.wrap.id = 'cg-wrap';
925
- document.getElementsByTagName('body')[0].appendChild(this.wrap);
926
- this.wrap.addEventListener('touchmove', function(e): void {
927
- // --- 防止拖动时整个网页跟着动 ---
928
- if (e.target && dom.findParentByData(e.target as HTMLElement, 'cg-scroll')) {
929
- return;
930
- }
931
- if (e.cancelable) {
932
- e.preventDefault();
933
- }
934
- // --- 为啥要在这加,因为有些设备性能不行,在 touchstart 之时添加的 touchmove 不能立马响应,导致网页还是跟着动,所以增加此函数 ---
935
- }, {
936
- 'passive': false
937
- });
938
- this.wrap.addEventListener('wheel', function(e): void {
939
- // --- 防止不小心前进后退,或上下缓动滚动(Mac、触摸板) ---
940
- if (e.target && (((e.target as HTMLElement).dataset.cgScroll !== undefined) || dom.findParentByData(e.target as HTMLElement, 'cg-scroll'))) {
941
- return;
942
- }
943
- e.preventDefault();
944
- }, {
945
- 'passive': false
946
- });
947
- this.wrap.addEventListener('contextmenu', function(e): void {
948
- e.preventDefault();
949
- });
950
- if (clickgo.isImmersion()) {
951
- // --- 只有沉浸式模式(Windows 下非 frame 的 native)才会绑定这个事件 ---
952
- this.wrap.addEventListener('mouseenter', function() {
953
- native.invoke('cg-mouse-ignore', native.getToken(), false) as any;
954
- });
955
- this.wrap.addEventListener('mouseleave', function() {
956
- native.invoke('cg-mouse-ignore', native.getToken(), true) as any;
957
- });
958
- }
959
-
960
- /** --- form list 的 div --- */
961
- this.list.id = 'cg-form-list';
962
- this.wrap.appendChild(this.list);
963
-
964
- /** --- pop list 的 div --- */
965
- this.popList.id = 'cg-pop-list';
966
- this.wrap.appendChild(this.popList);
967
-
968
- // --- 从鼠标指针处从小到大缩放然后淡化的圆圈动画特效对象 ---=
969
- this.circular.id = 'cg-circular';
970
- this.wrap.appendChild(this.circular);
971
-
972
- // --- 从鼠标指针处开始从小到大缩放并铺满屏幕(或任意大小矩形)的对象 ---
973
- this.rectangle.setAttribute('data-pos', '');
974
- this.rectangle.id = 'cg-rectangle';
975
- this.wrap.appendChild(this.rectangle);
976
-
977
- // --- 手势有效无效的圆圈 ---
978
- this.gesture.id = 'cg-gesture';
979
- this.wrap.appendChild(this.gesture);
980
-
981
- // --- drag drop 的拖动占位符 ---
982
- this.drag.id = 'cg-drag';
983
- this.drag.innerHTML = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none" stroke="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M8 8L40 40" stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter"/><path d="M8 40L40 8" stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter"/></svg>';
984
- this.wrap.appendChild(this.drag);
985
-
986
- // --- 添加 cg-system 的 dom ---
987
- this.notify.id = 'cg-notify';
988
- this.wrap.appendChild(this.notify);
989
- this.alert.id = 'cg-alert';
990
- this.wrap.appendChild(this.alert);
991
-
992
- // --- 添加 cg-simpletask 的 dom ---
993
- this.simpletask.id = 'cg-simpletask';
994
- this.wrap.appendChild(this.simpletask);
995
- const simpletaskApp = clickgo.vue.createApp({
996
- 'template': '<div v-for="(item, formId) of forms" class="cg-simpletask-item" @click="click(parseInt(formId))"><div v-if="item.icon" class="cg-simpletask-icon" :style="{\'background-image\': \'url(\' + item.icon + \')\'}"></div><div>{{item.title}}</div></div>',
997
- 'data': function() {
998
- return {
999
- 'forms': {}
1000
- };
1001
- },
1002
- 'watch': {
1003
- 'forms': {
1004
- handler: function(this: types.IVue) {
1005
- const length = Object.keys(this.forms).length;
1006
- if (length > 0) {
1007
- if (elements.simpletask.style.bottom !== '0px') {
1008
- elements.simpletask.style.bottom = '0px';
1009
- core.trigger('screenResize');
1010
- }
1011
- }
1012
- else {
1013
- if (elements.simpletask.style.bottom === '0px') {
1014
- elements.simpletask.style.bottom = '-46px';
1015
- core.trigger('screenResize');
1016
- }
1017
- }
1018
- },
1019
- 'deep': true
1020
- }
1021
- },
1022
- 'methods': {
1023
- click: function(this: types.IVue, formId: number): void {
1024
- changeFocus(formId);
1025
- }
1026
- },
1027
- 'mounted': function(this: types.IVue): void {
1028
- simpleSystemTaskRoot = this as any;
1029
- }
1030
- });
1031
- simpletaskApp.mount('#cg-simpletask');
1032
-
1033
- // --- cg-launcher ---
1034
- this.launcher.id = 'cg-launcher';
1035
- this.wrap.appendChild(this.launcher);
1036
- // --- Vue 挂载在这里 ---
1037
- const waiting = function(): void {
1038
- // --- 必须在这里执行,要不然 computed 无法更新,因为 core 还没加载进来 ---
1039
- if (!core.config) {
1040
- setTimeout(function() {
1041
- waiting();
1042
- }, 150);
1043
- return;
1044
- }
1045
- const launcherApp = clickgo.vue.createApp({
1046
- 'template': `<div class="cg-launcher-search">` +
1047
- `<input v-if="folderName === ''" class="cg-launcher-sinput" :placeholder="search" v-model="name">` +
1048
- `<input v-else class="cg-launcher-foldername" :value="folderName" @change="folderNameChange">` +
1049
- `</div>` +
1050
- `<div class="cg-launcher-list" @mousedown="mousedown" @click="listClick" :class="[folderName === '' ? '' : 'cg-folder-open']">` +
1051
- `<div v-for="item of list" class="cg-launcher-item">` +
1052
- `<div class="cg-launcher-inner">` +
1053
- `<div v-if="!item.list || item.list.length === 0" class="cg-launcher-icon" :style="{'background-image': 'url(' + item.icon + ')'}" @click="iconClick($event, item)"></div>` +
1054
- `<div v-else class="cg-launcher-folder" @click="openFolder($event, item)">` +
1055
- `<div>` +
1056
- `<div v-for="sub of item.list" class="cg-launcher-item">` +
1057
- `<div class="cg-launcher-inner">` +
1058
- `<div class="cg-launcher-icon" :style="{'background-image': 'url(' + sub.icon + ')'}" @click="subIconClick($event, sub)"></div>` +
1059
- `<div class="cg-launcher-name">{{sub.name}}</div>` +
1060
- `</div>` +
1061
- `<div class="cg-launcher-space"></div>` +
1062
- `</div>` +
1063
- `</div>` +
1064
- `</div>` +
1065
- `<div class="cg-launcher-name">{{item.name}}</div>` +
1066
- `</div>` +
1067
- `<div class="cg-launcher-space"></div>` +
1068
- `</div>` +
1069
- `</div>`,
1070
- 'data': function() {
1071
- return {
1072
- 'name': '',
1073
- 'folderName': '',
1074
- 'folderItem': {}
1075
- };
1076
- },
1077
- 'computed': {
1078
- 'search': function() {
1079
- return info.locale[core.config.locale]?.search ?? info.locale['en'].search;
1080
- },
1081
- 'list': function(this: types.IVue) {
1082
- if (this.name === '') {
1083
- return core.config['launcher.list'];
1084
- }
1085
- const list = [];
1086
- for (const item of core.config['launcher.list']) {
1087
- if (item.list && item.list.length > 0) {
1088
- for (const sub of item.list) {
1089
- if (sub.name.toLowerCase().includes(this.name.toLowerCase())) {
1090
- list.push(sub);
1091
- }
1092
- }
1093
- }
1094
- else {
1095
- if (item.name.toLowerCase().includes(this.name.toLowerCase())) {
1096
- list.push(item);
1097
- }
1098
- }
1099
- }
1100
- return list;
1101
- }
1102
- },
1103
- 'methods': {
1104
- mousedown: function(this: types.IVue, e: MouseEvent): void {
1105
- this.md = e.pageX + e.pageY;
1106
- },
1107
- listClick: function(this: types.IVue, e: MouseEvent) {
1108
- if (this.md !== e.pageX + e.pageY) {
1109
- return;
1110
- }
1111
- if (e.currentTarget !== e.target) {
1112
- return;
1113
- }
1114
- if (this.folderName === '') {
1115
- hideLauncher();
1116
- }
1117
- else {
1118
- this.closeFolder();
1119
- }
1120
- },
1121
- iconClick: async function(
1122
- this: types.IVue,
1123
- e: MouseEvent,
1124
- item: types.IConfigLauncherItem
1125
- ): Promise<void> {
1126
- if (this.md !== e.pageX + e.pageY) {
1127
- return;
1128
- }
1129
- hideLauncher();
1130
- await clickgo.task.run(item.path!, {
1131
- 'icon': item.icon
1132
- });
1133
- },
1134
- subIconClick: async function(
1135
- this: types.IVue,
1136
- e: MouseEvent,
1137
- item: types.IConfigLauncherItem
1138
- ): Promise<void> {
1139
- if (this.md !== e.pageX + e.pageY) {
1140
- return;
1141
- }
1142
- hideLauncher();
1143
- await clickgo.task.run(item.path!, {
1144
- 'icon': item.icon
1145
- });
1146
- },
1147
- closeFolder: function(this: types.IVue): void {
1148
- // --- 关闭文件夹 ---
1149
- this.folderName = '';
1150
- const el = this.folderEl as HTMLDivElement;
1151
- const rect = (el.parentNode as HTMLDivElement).getBoundingClientRect();
1152
- el.classList.remove('cg-show');
1153
- el.style.left = (rect.left + 30).toString() + 'px';
1154
- el.style.top = rect.top.toString() + 'px';
1155
- el.style.width = '';
1156
- el.style.height = '';
1157
- setTimeout(() => {
1158
- el.style.position = '';
1159
- el.style.left = '';
1160
- el.style.top = '';
1161
- }, 150);
1162
- },
1163
- openFolder: function(this: types.IVue, e: MouseEvent, item: types.IConfigLauncherItem): void {
1164
- if (this.md !== e.pageX + e.pageY) {
1165
- return;
1166
- }
1167
- if ((e.currentTarget as HTMLElement).childNodes[0] !== e.target) {
1168
- return;
1169
- }
1170
- if (this.folderName !== '') {
1171
- this.closeFolder();
1172
- return;
1173
- }
1174
- this.folderName = item.name;
1175
- this.folderItem = item;
1176
- const el = (e.currentTarget as HTMLDivElement).childNodes.item(0) as HTMLDivElement;
1177
- this.folderEl = el;
1178
- const searchEl = document.getElementsByClassName('cg-launcher-search')[0] as HTMLDivElement;
1179
- const rect = el.getBoundingClientRect();
1180
- el.style.left = rect.left.toString() + 'px';
1181
- el.style.top = rect.top.toString() + 'px';
1182
- el.style.position = 'fixed';
1183
- requestAnimationFrame(() => {
1184
- el.classList.add('cg-show');
1185
- el.style.left = '50px';
1186
- el.style.top = searchEl.offsetHeight.toString() + 'px';
1187
- el.style.width = 'calc(100% - 100px)';
1188
- el.style.height = 'calc(100% - 50px - ' + searchEl.offsetHeight.toString() + 'px)';
1189
- });
1190
- },
1191
- folderNameChange: function(this: types.IVue, e: InputEvent): void {
1192
- const input = e.target as HTMLInputElement;
1193
- const val = input.value.trim();
1194
- if (val === '') {
1195
- input.value = this.folderName;
1196
- return;
1197
- }
1198
- this.folderName = val;
1199
- // --- 触发 folder name change 事件 ---
1200
- core.trigger('launcherFolderNameChanged', this.folderItem.id ?? '', val);
1201
- }
1202
- },
1203
- 'mounted': function(this: types.IVue): void {
1204
- launcherRoot = this as any;
1205
- }
1206
- });
1207
- launcherApp.mount('#cg-launcher');
1208
- };
1209
- waiting();
1210
-
1211
- // --- cg-confirm ---
1212
- this.confirm.id = 'cg-confirm';
1213
- this.wrap.appendChild(this.confirm);
1214
-
1215
- this.confirm.innerHTML = `<div class="cg-confirm-box">` +
1216
- `<div id="cg-confirm-content"></div>` +
1217
- `<div class="cg-confirm-controls">` +
1218
- `<div id="cg-confirm-cancel"></div>` +
1219
- `<div id="cg-confirm-ok"></div>` +
1220
- `</div>` +
1221
- `</div>`;
1222
- this.confirm.style.display = 'none';
1223
- document.getElementById('cg-confirm-cancel')!.addEventListener('click', () => {
1224
- superConfirmHandler?.(false) as any;
1225
- this.confirm.style.display = 'none';
1226
- const fid = getMaxZIndexID();
1227
- if (fid) {
1228
- changeFocus(fid);
1229
- }
1230
- });
1231
- document.getElementById('cg-confirm-ok')!.addEventListener('click', () => {
1232
- superConfirmHandler?.(true) as any;
1233
- this.confirm.style.display = 'none';
1234
- const fid = getMaxZIndexID();
1235
- if (fid) {
1236
- changeFocus(fid);
1237
- }
1238
- });
1239
-
1240
- }
1241
- };
1242
- elements.init();
1243
-
1244
- /** --- 显示系统级询问框,App 模式下无效 --- */
1245
- export function superConfirm(html: string): Promise<boolean> {
1246
- return new Promise((resolve) => {
1247
- if (superConfirmHandler !== undefined) {
1248
- resolve(false);
1249
- return;
1250
- }
1251
- elements.confirm.style.display = 'flex';
1252
- document.getElementById('cg-confirm-content')!.innerHTML = html;
1253
- document.getElementById('cg-confirm-cancel')!.innerHTML = info.locale[core.config.locale]?.cancel ?? info.locale['en'].cancel;
1254
- document.getElementById('cg-confirm-ok')!.innerHTML = info.locale[core.config.locale]?.ok ?? info.locale['en'].ok;
1255
- superConfirmHandler = (result: boolean) => {
1256
- superConfirmHandler = undefined;
1257
- resolve(result);
1258
- };
1259
- });
1260
- }
1261
-
1262
- /**
1263
- * --- 修改窗体的最大化、最小化状态,外部或不可调整 state 时才调用 ---
1264
- * @param state 最大化、最小化或关闭
1265
- * @param formId 窗体 id
1266
- */
1267
- function changeState(state: 'min' | 'max' | 'close', formId: number): boolean {
1268
- const tid: number = getTaskId(formId);
1269
- const t = task.list[tid];
1270
- if (!t) {
1271
- return false;
1272
- }
1273
- switch (state) {
1274
- case 'max': {
1275
- t.forms[formId].vroot.$refs.form.maxMethod();
1276
- break;
1277
- }
1278
- case 'min': {
1279
- t.forms[formId].vroot.$refs.form.minMethod();
1280
- break;
1281
- }
1282
- default: {
1283
- remove(formId);
1284
- }
1285
- }
1286
- return true;
1287
- }
1288
-
1289
- /**
1290
- * --- 最小化某个窗体 ---
1291
- * @param formId 窗体 id
1292
- */
1293
- export function min(formId: number): boolean {
1294
- return changeState('min', formId);
1295
- }
1296
-
1297
- /**
1298
- * --- 最大化某个窗体 ---
1299
- * @param formId 窗体 id
1300
- */
1301
- export function max(formId: number): boolean {
1302
- return changeState('max', formId);
1303
- }
1304
-
1305
- /**
1306
- * --- 关闭一个窗体 ---
1307
- * @param formId 窗体 id
1308
- */
1309
- export function close(formId: number): boolean {
1310
- return changeState('close', formId);
1311
- }
1312
-
1313
- /**
1314
- * --- 绑定窗体拖动大小事件,在 mousedown、touchstart 中绑定 ---
1315
- * @param e 事件源
1316
- * @param border 调整大小的方位
1317
- */
1318
- export function bindResize(e: MouseEvent | TouchEvent, border: types.TDomBorder): void {
1319
- const formWrap = dom.findParentByClass(e.target as HTMLElement, 'cg-form-wrap');
1320
- if (!formWrap) {
1321
- return;
1322
- }
1323
- const formId = formWrap.dataset.formId;
1324
- if (!formId) {
1325
- return;
1326
- }
1327
- const fid = parseInt(formId);
1328
- const tid = getTaskId(fid);
1329
- const t = task.list[tid];
1330
- if (!t) {
1331
- return;
1332
- }
1333
- t.forms[fid].vroot.$refs.form.resizeMethod(e, border);
1334
- }
1335
-
1336
- /**
1337
- * --- 绑定窗体拖动事件,在 mousedown、touchstart 中绑定 ---
1338
- * @param e 事件源
1339
- */
1340
- export function bindDrag(e: MouseEvent | TouchEvent): void {
1341
- const formWrap = dom.findParentByClass(e.target as HTMLElement, 'cg-form-wrap');
1342
- if (!formWrap) {
1343
- return;
1344
- }
1345
- const formId = formWrap.dataset.formId;
1346
- if (!formId) {
1347
- return;
1348
- }
1349
- const fid = parseInt(formId);
1350
- const tid = getTaskId(fid);
1351
- const t = task.list[tid];
1352
- if (!t) {
1353
- return;
1354
- }
1355
- t.forms[fid].vroot.$refs.form.moveMethod(e, true);
1356
- }
1357
-
1358
- /**
1359
- * --- 重置所有已经最大化的窗体大小和位置,App 模式下无效 ---
1360
- */
1361
- export function refreshMaxPosition(): void {
1362
- const area = core.getAvailArea();
1363
- for (let i = 0; i < elements.list.children.length; ++i) {
1364
- const el = elements.list.children.item(i) as HTMLElement;
1365
- const ef = el.children.item(0) as HTMLElement;
1366
- if (ef.dataset.cgMax === undefined) {
1367
- continue;
1368
- }
1369
- const taskId = parseInt(el.getAttribute('data-task-id')!);
1370
- const formId = parseInt(el.getAttribute('data-form-id')!);
1371
- if (!task.list[taskId]) {
1372
- continue;
1373
- }
1374
- const vroot = task.list[taskId].forms[formId].vroot;
1375
- if (ef.dataset.cgBottomMost === undefined) {
1376
- // --- 不是置底窗体 ---
1377
- vroot.$refs.form.setPropData('left', area.left);
1378
- vroot.$refs.form.setPropData('top', area.top);
1379
- vroot.$refs.form.setPropData('width', area.width);
1380
- vroot.$refs.form.setPropData('height', area.height);
1381
- }
1382
- else {
1383
- // --- 是置底窗体 ---
1384
- vroot.$refs.form.setPropData('width', area.owidth);
1385
- vroot.$refs.form.setPropData('height', area.oheight);
1386
- }
1387
- }
1388
- }
1389
-
1390
- /**
1391
- * --- 根据窗体 id 获取 task id ---
1392
- * @param formId 窗体 id
1393
- */
1394
- export function getTaskId(formId: number): number {
1395
- const formElement = elements.list.querySelector(`[data-form-id='${formId}']`);
1396
- if (!formElement) {
1397
- return 0;
1398
- }
1399
- // --- 获取 task id ---
1400
- const taskIdAttr = formElement.getAttribute('data-task-id');
1401
- if (!taskIdAttr) {
1402
- return 0;
1403
- }
1404
- return parseInt(taskIdAttr);
1405
- }
1406
-
1407
- /**
1408
- * --- 获取窗体信息 ---
1409
- * @param formId 窗体 id
1410
- */
1411
- export function get(formId: number): types.IFormInfo | null {
1412
- const taskId: number = getTaskId(formId);
1413
- if (taskId === 0) {
1414
- return null;
1415
- }
1416
- if (!task.list[taskId].forms[formId]) {
1417
- return null;
1418
- }
1419
- const item = task.list[taskId].forms[formId];
1420
- return {
1421
- 'taskId': taskId,
1422
- 'title': item.vroot.$refs.form.title,
1423
- 'icon': item.vroot.$refs.form.iconDataUrl,
1424
- 'stateMax': item.vroot.$refs.form.stateMaxData,
1425
- 'stateMin': item.vroot.$refs.form.stateMinData,
1426
- 'show': item.vroot.$refs.form.isShow,
1427
- 'focus': item.vroot.formFocus,
1428
- 'showInSystemTask': item.vroot.showInSystemTask
1429
- };
1430
- }
1431
-
1432
- /**
1433
- * --- 给一个窗体发送一个对象,不会知道成功与失败状态,APP 模式下无效用 this.send 替代 ---
1434
- * @param formId 要接收对象的 form id
1435
- * @param obj 要发送的对象
1436
- */
1437
- export function send(formId: number, obj: Record<string, any>): void {
1438
- const taskId: number = getTaskId(formId);
1439
- if (taskId === 0) {
1440
- return;
1441
- }
1442
- const item = task.list[taskId].forms[formId];
1443
- item.vroot.onReceive(obj);
1444
- }
1445
-
1446
- /**
1447
- * --- 获取 form list 的简略情况 ---
1448
- * @param taskId 任务 ID
1449
- */
1450
- export function getList(taskId: number): Record<string, types.IFormInfo> {
1451
- if (!task.list[taskId]) {
1452
- return {};
1453
- }
1454
- const list: Record<string, types.IFormInfo> = {};
1455
- for (const fid in task.list[taskId].forms) {
1456
- const item = task.list[taskId].forms[fid];
1457
- list[fid] = {
1458
- 'taskId': taskId,
1459
- 'title': item.vroot.$refs.form.title,
1460
- 'icon': item.vroot.$refs.form.iconDataUrl,
1461
- 'stateMax': item.vroot.$refs.form.stateMaxData,
1462
- 'stateMin': item.vroot.$refs.form.stateMinData,
1463
- 'show': item.vroot.$refs.form.isShow,
1464
- 'focus': item.vroot.formFocus,
1465
- 'showInSystemTask': item.vroot.showInSystemTask
1466
- };
1467
- }
1468
- return list;
1469
- }
1470
-
1471
- /**
1472
- * --- 获取当前有焦点的窗体 form id ---
1473
- */
1474
- export function getFocus(): number | null {
1475
- return focusId;
1476
- }
1477
-
1478
- /**
1479
- * --- 当前活跃中的 panelId 列表 ---
1480
- */
1481
- export const activePanels: Record<string, number[]> = {};
1482
-
1483
- /**
1484
- * --- 获取窗体当前活跃中的 panelId 列表 ---
1485
- * @param formId 要获取的窗体 id
1486
- */
1487
- export function getActivePanel(formId: number): number[] {
1488
- return activePanels[formId] ?? [];
1489
- }
1490
-
1491
- /**
1492
- * --- 移除 form 中正在活跃中的 panel id (panel 本身被置于隐藏时)
1493
- * @param panelId panel id
1494
- * @param formId 所属 form id
1495
- * @param taskId task id 校验,App 模式下无效
1496
- */
1497
- export function removeActivePanel(panelId: number, formId: number, taskId?: number): boolean {
1498
- if (!taskId) {
1499
- return false;
1500
- }
1501
- if (!task.list[taskId]) {
1502
- return false;
1503
- }
1504
- if (!task.list[taskId].forms[formId]) {
1505
- return false;
1506
- }
1507
- if (!activePanels[formId]) {
1508
- return true;
1509
- }
1510
- const io = activePanels[formId].indexOf(panelId);
1511
- if (io === -1) {
1512
- return true;
1513
- }
1514
- activePanels[formId].splice(io, 1);
1515
- if (!activePanels[formId].length) {
1516
- delete activePanels[formId];
1517
- }
1518
- return true;
1519
- }
1520
-
1521
- /**
1522
- * --- 将 form 中某个 panel 设置为活动的 ---
1523
- * @param panelId panel id
1524
- * @param formId 所属 form id
1525
- * @param taskId task id 校验,App 模式下无效
1526
- */
1527
- export function setActivePanel(panelId: number, formId: number, taskId?: number): boolean {
1528
- if (!taskId) {
1529
- return false;
1530
- }
1531
- if (!task.list[taskId]) {
1532
- return false;
1533
- }
1534
- if (!task.list[taskId].forms[formId]) {
1535
- return false;
1536
- }
1537
- if (!activePanels[formId]) {
1538
- activePanels[formId] = [];
1539
- }
1540
- const io = activePanels[formId].indexOf(panelId);
1541
- if (io !== -1) {
1542
- return true;
1543
- }
1544
- activePanels[formId].push(panelId);
1545
- return true;
1546
- }
1547
-
1548
- /**
1549
- * --- 修改窗体 hash ---
1550
- * @param hash 修改的值,不含 #
1551
- * @param formId 要修改的窗体 ID
1552
- */
1553
- export function hash(hash: string, formId: number): boolean {
1554
- const taskId = getTaskId(formId);
1555
- if (taskId === 0) {
1556
- return false;
1557
- }
1558
- const t = task.list[taskId];
1559
- if (!t) {
1560
- return false;
1561
- }
1562
- const item = task.list[taskId].forms[formId];
1563
- if (!item) {
1564
- return false;
1565
- }
1566
- item.vroot.formHash = hash;
1567
- return true;
1568
- }
1569
-
1570
- /**
1571
- * --- 获取窗体的 hash ---
1572
- */
1573
- export function getHash(formId: number): string {
1574
- const taskId = getTaskId(formId);
1575
- if (taskId === 0) {
1576
- return '';
1577
- }
1578
- const t = task.list[taskId];
1579
- if (!t) {
1580
- return '';
1581
- }
1582
- const item = task.list[taskId].forms[formId];
1583
- if (!item) {
1584
- return '';
1585
- }
1586
- return item.vroot.$data._formHash;
1587
- }
1588
-
1589
- /**
1590
- * --- 将窗体的 hash 退回上一个 ---
1591
- */
1592
- export async function hashBack(formId: number): Promise<boolean> {
1593
- const taskId = getTaskId(formId);
1594
- if (taskId === 0) {
1595
- return false;
1596
- }
1597
- const t = task.list[taskId];
1598
- if (!t) {
1599
- return false;
1600
- }
1601
- const item = task.list[taskId].forms[formId];
1602
- if (!item) {
1603
- return false;
1604
- }
1605
- await item.vroot.formHashBack();
1606
- return true;
1607
- }
1608
-
1609
- /**
1610
- * --- 改变 form 的焦点 class ---
1611
- * @param formId 变更后的 form id
1612
- */
1613
- export function changeFocus(formId: number | string = 0): void {
1614
- if (typeof formId === 'string') {
1615
- formId = parseInt(formId);
1616
- }
1617
- const dataFormId = getFocus();
1618
- if (dataFormId) {
1619
- if (dataFormId === formId) {
1620
- return;
1621
- }
1622
- else {
1623
- const t = task.list[task.getFocus()!];
1624
- t.forms[dataFormId].vapp._container.removeAttribute('data-form-focus');
1625
- t.forms[dataFormId].vroot._formFocus = false;
1626
- // --- 触发 formBlurred 事件 ---
1627
- core.trigger('formBlurred', t.id, dataFormId);
1628
- }
1629
- }
1630
- focusId = null;
1631
- task.setFocus();
1632
- if (formId === 0) {
1633
- return;
1634
- }
1635
- const el = elements.list.querySelector(`.cg-form-wrap[data-form-id='${formId}']`);
1636
- if (!el) {
1637
- return;
1638
- }
1639
- // --- 如果是最小化状态的话,需要还原 ---
1640
- if ((el.children.item(0) as HTMLElement).dataset.cgMin !== undefined) {
1641
- min(formId);
1642
- }
1643
- // --- 获取所属的 taskId ---
1644
- const taskId: number = parseInt(el.getAttribute('data-task-id') ?? '0');
1645
- const t = task.list[taskId];
1646
- // --- 检测是否有 dialog mask 层 ---
1647
- if (t.runtime.dialogFormIds.length) {
1648
- // --- 有 dialog ---
1649
- const dialogFormId = t.runtime.dialogFormIds[t.runtime.dialogFormIds.length - 1];
1650
- // --- 如果是最小化状态的话,需要还原 ---
1651
- if (get(dialogFormId)!.stateMin) {
1652
- min(dialogFormId);
1653
- }
1654
- if (t.forms[dialogFormId].vroot._topMost) {
1655
- t.forms[dialogFormId].vroot.$refs.form.$data.zIndex = ++info.topLastZIndex;
1656
- }
1657
- else if (t.forms[dialogFormId].vroot._bottomMost) {
1658
- t.forms[dialogFormId].vroot.$refs.form.$data.zIndex = ++info.bottomLastZIndex;
1659
- }
1660
- else {
1661
- t.forms[dialogFormId].vroot.$refs.form.$data.zIndex = ++info.lastZIndex;
1662
- }
1663
- // --- 开启 focus ---
1664
- t.forms[dialogFormId].vapp._container.dataset.formFocus = '';
1665
- t.forms[dialogFormId].vroot._formFocus = true;
1666
- focusId = dialogFormId;
1667
- task.setFocus(t.id);
1668
- // --- 触发 formFocused 事件 ---
1669
- core.trigger('formFocused', taskId, dialogFormId);
1670
- // --- 判断点击的窗体是不是就是 dialog mask 窗体本身 ---
1671
- if (dialogFormId !== formId) {
1672
- // --- 闪烁 ---
1673
- clickgo.form.flash(dialogFormId, taskId);
1674
- }
1675
- }
1676
- else {
1677
- // --- 没有 dialog,才修改 zindex ---
1678
- if (t.forms[formId].vroot._topMost) {
1679
- t.forms[formId].vroot.$refs.form.$data.zIndex = ++info.topLastZIndex;
1680
- }
1681
- else if (t.forms[formId].vroot._bottomMost) {
1682
- t.forms[formId].vroot.$refs.form.$data.zIndex = ++info.bottomLastZIndex;
1683
- }
1684
- else {
1685
- t.forms[formId].vroot.$refs.form.$data.zIndex = ++info.lastZIndex;
1686
- }
1687
- // --- 正常开启 focus ---
1688
- t.forms[formId].vapp._container.dataset.formFocus = '';
1689
- t.forms[formId].vroot._formFocus = true;
1690
- focusId = formId;
1691
- task.setFocus(t.id);
1692
- // --- 触发 formFocused 事件 ---
1693
- core.trigger('formFocused', taskId, formId);
1694
- }
1695
- }
1696
-
1697
- /**
1698
- * --- 获取当前 z-index 值最大的 form id(除了 top 模式的窗体和最小化的窗体) ---
1699
- * @param out 排除选项
1700
- */
1701
- export function getMaxZIndexID(out: {
1702
- 'taskIds'?: number[];
1703
- 'formIds'?: number[];
1704
- } = {}): number | null {
1705
- let zIndex: number = 0;
1706
- let formId: number | null = null;
1707
- for (let i = 0; i < elements.list.children.length; ++i) {
1708
- const formWrap = elements.list.children.item(i) as HTMLDivElement;
1709
- const formInner = formWrap.children.item(0) as HTMLDivElement;
1710
- if (!formInner) {
1711
- continue;
1712
- }
1713
- // --- 排除 bottom most 窗体 ---
1714
- const z = parseInt(formInner.style.zIndex);
1715
- if (z < 1000000) {
1716
- continue;
1717
- }
1718
- // --- 排除最小化窗体 ---
1719
- if (formInner.dataset.cgMin !== undefined) {
1720
- continue;
1721
- }
1722
- // --- 排除用户定义的 task id 窗体 ---
1723
- const tid = parseInt(formWrap.getAttribute('data-task-id')!);
1724
- if (tid === task.systemTaskInfo.taskId) {
1725
- // --- 系统任务排除掉 ---
1726
- continue;
1727
- }
1728
- if (out.taskIds?.includes(tid)) {
1729
- continue;
1730
- }
1731
- // --- 排除用户定义的 form id 窗体 ---
1732
- const fid = parseInt(formWrap.getAttribute('data-form-id')!);
1733
- if (out.formIds?.includes(fid)) {
1734
- continue;
1735
- }
1736
- if (z > zIndex) {
1737
- zIndex = z;
1738
- formId = fid;
1739
- }
1740
- }
1741
- return formId;
1742
- }
1743
-
1744
- /**
1745
- * --- 根据 border 方向 获取理论窗体大小 ---
1746
- * @param border 显示的位置代号
1747
- */
1748
- export function getRectByBorder(border: types.TDomBorderCustom): { 'width': number; 'height': number; 'left': number; 'top': number; } {
1749
- const area = core.getAvailArea();
1750
- let width!: number, height!: number, left!: number, top!: number;
1751
- if (typeof border === 'string') {
1752
- switch (border) {
1753
- case 'lt': {
1754
- width = area.width / 2;
1755
- height = area.height / 2;
1756
- left = area.left;
1757
- top = area.top;
1758
- break;
1759
- }
1760
- case 't': {
1761
- width = area.width;
1762
- height = area.height;
1763
- left = area.left;
1764
- top = area.top;
1765
- break;
1766
- }
1767
- case 'tr': {
1768
- width = area.width / 2;
1769
- height = area.height / 2;
1770
- left = area.left + area.width / 2;
1771
- top = area.top;
1772
- break;
1773
- }
1774
- case 'r': {
1775
- width = area.width / 2;
1776
- height = area.height;
1777
- left = area.left + area.width / 2;
1778
- top = area.top;
1779
- break;
1780
- }
1781
- case 'rb': {
1782
- width = area.width / 2;
1783
- height = area.height / 2;
1784
- left = area.left + area.width / 2;
1785
- top = area.top + area.height / 2;
1786
- break;
1787
- }
1788
- case 'b': {
1789
- width = area.width;
1790
- height = area.height / 2;
1791
- left = area.left;
1792
- top = area.top + area.height / 2;
1793
- break;
1794
- }
1795
- case 'bl': {
1796
- width = area.width / 2;
1797
- height = area.height / 2;
1798
- left = area.left;
1799
- top = area.top + area.height / 2;
1800
- break;
1801
- }
1802
- case 'l': {
1803
- width = area.width / 2;
1804
- height = area.height;
1805
- left = area.left;
1806
- top = area.top;
1807
- break;
1808
- }
1809
- default: {
1810
- break;
1811
- }
1812
- }
1813
- }
1814
- else {
1815
- width = border.width ?? area.width;
1816
- height = border.height ?? area.height;
1817
- left = border.left ?? area.left;
1818
- top = border.top ?? area.top;
1819
- }
1820
- return {
1821
- 'width': width,
1822
- 'height': height,
1823
- 'left': left,
1824
- 'top': top
1825
- };
1826
- }
1827
-
1828
- /**
1829
- * --- 显示从小到大的圆圈动画特效对象 ---
1830
- * @param x X 坐标
1831
- * @param y Y 坐标
1832
- */
1833
- export function showCircular(x: number, y: number): void {
1834
- elements.circular.style.transition = 'none';
1835
- requestAnimationFrame(function() {
1836
- elements.circular.style.width = '6px';
1837
- elements.circular.style.height = '6px';
1838
- elements.circular.style.left = (x - 3).toString() + 'px';
1839
- elements.circular.style.top = (y - 3).toString() + 'px';
1840
- elements.circular.style.opacity = '1';
1841
- requestAnimationFrame(function() {
1842
- elements.circular.style.transition = 'all .3s var(--g-cubic)';
1843
- requestAnimationFrame(function() {
1844
- elements.circular.style.width = '60px';
1845
- elements.circular.style.height = '60px';
1846
- elements.circular.style.left = (x - 30).toString() + 'px';
1847
- elements.circular.style.top = (y - 30).toString() + 'px';
1848
- elements.circular.style.opacity = '0';
1849
- });
1850
- });
1851
- });
1852
- }
1853
-
1854
- /**
1855
- * --- 移动矩形到新位置 ---
1856
- * @param border 显示的位置代号
1857
- */
1858
- export function moveRectangle(border: types.TDomBorderCustom): void {
1859
- const dataReady = elements.rectangle.getAttribute('data-ready') ?? '0';
1860
- if (dataReady === '0') {
1861
- return;
1862
- }
1863
- const dataBorder = elements.rectangle.getAttribute('data-border') ?? '';
1864
- const setDataBorder = typeof border === 'string' ? border : `o-${border.left}-${border.top ?? 'n'}-${border.width}-${border.height ?? 'n'}`;
1865
- if (dataBorder === setDataBorder) {
1866
- return;
1867
- }
1868
- elements.rectangle.setAttribute('data-dir', setDataBorder);
1869
- const pos = getRectByBorder(border);
1870
- const width = pos.width - 20;
1871
- const height = pos.height - 20;
1872
- const left = pos.left + 10;
1873
- const top = pos.top + 10;
1874
- if (width !== undefined && height !== undefined && left !== undefined && top !== undefined) {
1875
- elements.rectangle.style.width = width.toString() + 'px';
1876
- elements.rectangle.style.height = height.toString() + 'px';
1877
- elements.rectangle.style.left = left.toString() + 'px';
1878
- elements.rectangle.style.top = top.toString() + 'px';
1879
- }
1880
- }
1881
-
1882
- /**
1883
- * --- 显示从小到大的矩形动画特效对象 ---
1884
- * @param x 起始位置
1885
- * @param y 起始位置
1886
- * @param border 最大时位置代号
1887
- */
1888
- export function showRectangle(x: number, y: number, border: types.TDomBorderCustom): void {
1889
- elements.rectangle.style.transition = 'none';
1890
- requestAnimationFrame(function() {
1891
- elements.rectangle.style.width = '5px';
1892
- elements.rectangle.style.height = '5px';
1893
- elements.rectangle.style.left = (x - 10).toString() + 'px';
1894
- elements.rectangle.style.top = (y - 10).toString() + 'px';
1895
- elements.rectangle.style.opacity = '1';
1896
- elements.rectangle.setAttribute('data-ready', '0');
1897
- elements.rectangle.setAttribute('data-dir', '');
1898
- requestAnimationFrame(function() {
1899
- elements.rectangle.style.transition = 'all .3s var(--g-cubic)';
1900
- requestAnimationFrame(function() {
1901
- elements.rectangle.setAttribute('data-ready', '1');
1902
- moveRectangle(border);
1903
- });
1904
- });
1905
- });
1906
- }
1907
-
1908
- /**
1909
- * --- 结束时请隐藏矩形 ---
1910
- */
1911
- export function hideRectangle(): void {
1912
- elements.rectangle.style.opacity = '0';
1913
- }
1914
-
1915
- // --- DRAG ---
1916
-
1917
- /** --- 是否马上要隐藏的 timer --- */
1918
- let dragTimeOut: number = 0;
1919
-
1920
- /**
1921
- * --- 显示 drag 虚拟框 ---
1922
- */
1923
- export function showDrag(): void {
1924
- if (dragTimeOut) {
1925
- clearTimeout(dragTimeOut);
1926
- dragTimeOut = 0;
1927
- }
1928
- elements.drag.style.opacity = '1';
1929
- elements.drag.style.transform = 'perspective(100px) rotateX(15deg) translateZ(15px)';
1930
- elements.drag.style.borderBottomWidth = '2px';
1931
- }
1932
-
1933
- /**
1934
- * --- 移动 drag 到新位置 ---
1935
- * @param opt top:顶部位置,left:左侧位置,width:宽度位置,height:高度位置
1936
- */
1937
- export function moveDrag(opt: types.IMoveDragOptions): void {
1938
- if (opt.top) {
1939
- elements.drag.style.top = opt.top.toString() + 'px';
1940
- }
1941
- if (opt.left) {
1942
- elements.drag.style.left = opt.left.toString() + 'px';
1943
- }
1944
- let perspective = 0;
1945
- if (opt.width) {
1946
- elements.drag.style.width = opt.width.toString() + 'px';
1947
- if (perspective < opt.width) {
1948
- perspective = opt.width;
1949
- }
1950
- }
1951
- if (opt.height) {
1952
- elements.drag.style.height = opt.height.toString() + 'px';
1953
- if (perspective < opt.height) {
1954
- perspective = opt.height;
1955
- }
1956
- }
1957
- if (perspective) {
1958
- elements.drag.style.transform = 'perspective(' + (perspective + 50) + 'px) rotateX(15deg) translateZ(15px)';
1959
- }
1960
- if (opt.icon) {
1961
- (elements.drag.childNodes[0] as HTMLElement).style.display = 'block';
1962
- }
1963
- else {
1964
- (elements.drag.childNodes[0] as HTMLElement).style.display = 'none';
1965
- }
1966
- }
1967
-
1968
- /**
1969
- * --- 隐藏拖拽框框 ---
1970
- */
1971
- export function hideDrag(): void {
1972
- elements.drag.style.transform = 'initial';
1973
- elements.drag.style.borderBottomWidth = '1px';
1974
- dragTimeOut = window.setTimeout(() => {
1975
- dragTimeOut = 0;
1976
- elements.drag.style.opacity = '0';
1977
- }, 300);
1978
- }
1979
-
1980
- // --- Alert ---
1981
-
1982
- let alertBottom: number = 0;
1983
- let alertId: number = 0;
1984
-
1985
- /**
1986
- * --- 从下方弹出 alert ---
1987
- * @param content 内容
1988
- * @param type 样式,可留空
1989
- */
1990
- export function alert(content: string, type?: 'default' | 'primary' | 'info' | 'warning' | 'danger'): number {
1991
- // --- 申请 aid ---
1992
- const nid = ++alertId;
1993
- // --- 设置 timeout ---
1994
- const timeout = 3000;
1995
- // --- 创建 notify element ---
1996
- const el = document.createElement('div');
1997
- const y = alertBottom;
1998
- el.classList.add('cg-alert-wrap');
1999
- el.classList.add('cg-' + (type ?? 'default'));
2000
- el.setAttribute('data-alertid', nid.toString());
2001
- el.style.transform = `translateY(${y + 10}px)`;
2002
- el.style.opacity = '0';
2003
- el.innerHTML = `<div class="cg-alert-content">` +
2004
- `<div class="cg-alert-icon"></div>` +
2005
- `<div>${tool.escapeHTML(content)}</div>` +
2006
- '</div>';
2007
- elements.alert.appendChild(el);
2008
- alertBottom -= el.offsetHeight + 10;
2009
- requestAnimationFrame(function() {
2010
- el.style.transform = `translateY(${y}px)`;
2011
- el.style.opacity = '1';
2012
- const timer = window.setTimeout(function() {
2013
- // --- 隐藏 alert ---
2014
- clearTimeout(timer);
2015
- const alertHeight = el.offsetHeight;
2016
- el.style.opacity = '0';
2017
- setTimeout(function() {
2018
- alertBottom += alertHeight + 10;
2019
- const alertElementList = document.getElementsByClassName('cg-alert-wrap') as HTMLCollectionOf<HTMLDivElement>;
2020
- let needSub = false;
2021
- for (const alertElement of alertElementList) {
2022
- if (alertElement === el) {
2023
- // --- el 之后的 alert 都要往下移动 ---
2024
- needSub = true;
2025
- continue;
2026
- }
2027
- if (needSub) {
2028
- alertElement.style.transform = alertElement.style.transform.replace(/translateY\(([-0-9]+)px\)/,
2029
- function(t: string, t1: string): string {
2030
- return `translateY(${parseInt(t1) + alertHeight + 10}px)`;
2031
- }
2032
- );
2033
- }
2034
- }
2035
- el.remove();
2036
- }, 100);
2037
- }, timeout);
2038
- });
2039
- return nid;
2040
- }
2041
-
2042
- // --- Notify ---
2043
-
2044
- let notifyBottom: number = -10;
2045
- let notifyId: number = 0;
2046
- /**
2047
- * --- 弹出右上角信息框 ---
2048
- * @param opt timeout 默认 5 秒,最大 10 分钟
2049
- */
2050
- export function notify(opt: types.INotifyOptions): number {
2051
- // --- 申请 nid ---
2052
- const nid = ++notifyId;
2053
- // --- 设置 timeout ---
2054
- let timeout = 5_000;
2055
- /** --- 限定的最大 maxTimeout --- */
2056
- const maxTimeout = 60_000 * 10;
2057
- if (opt.timeout !== undefined) {
2058
- if (opt.timeout <= 0) {
2059
- timeout = 5_000;
2060
- }
2061
- else if (opt.timeout > maxTimeout) {
2062
- timeout = maxTimeout;
2063
- }
2064
- else {
2065
- timeout = opt.timeout;
2066
- }
2067
- }
2068
- // --- 设置 type ---
2069
- if (opt.progress && !opt.type) {
2070
- opt.type = 'progress';
2071
- }
2072
- // --- 创建 notify element ---
2073
- const el = document.createElement('div');
2074
- let y = notifyBottom;
2075
- let x = -10;
2076
- if (task.systemTaskInfo.taskId > 0) {
2077
- if (core.config['task.position'] === 'bottom') {
2078
- y -= task.systemTaskInfo.length;
2079
- }
2080
- else if (core.config['task.position'] === 'right') {
2081
- x -= task.systemTaskInfo.length;
2082
- }
2083
- }
2084
- el.classList.add('cg-notify-wrap');
2085
- el.setAttribute('data-notifyid', nid.toString());
2086
- el.style.transform = `translateY(${y}px) translateX(280px)`;
2087
- el.style.opacity = '0';
2088
- el.classList.add((opt.title && opt.content) ? 'cg-notify-full' : 'cg-notify-only');
2089
- el.innerHTML = `<div class="cg-notify-icon cg-${tool.escapeHTML(opt.type ?? 'primary')}"></div>` +
2090
- '<div style="flex: 1;">' +
2091
- (opt.title ? `<div class="cg-notify-title">${tool.escapeHTML(opt.title)}</div>` : '') +
2092
- (opt.content ? `<div class="cg-notify-content">${tool.escapeHTML(opt.content).replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n/g, '<br>')}</div>` +
2093
- `${opt.progress ? '<div class="cg-notify-progress"></div>' : ''}` : '') +
2094
- '</div>';
2095
- if (opt.icon) {
2096
- (el.childNodes.item(0) as HTMLElement).style.background = 'url(' + opt.icon + ')';
2097
- (el.childNodes.item(0) as HTMLElement).style.backgroundSize = '14px';
2098
- }
2099
- elements.notify.appendChild(el);
2100
- notifyBottom -= el.offsetHeight + 10;
2101
- requestAnimationFrame(function() {
2102
- el.style.transform = `translateY(${y}px) translateX(${x}px)`;
2103
- el.style.opacity = '1';
2104
- const timer = window.setTimeout(function() {
2105
- hideNotify(nid);
2106
- }, timeout);
2107
- el.setAttribute('data-timer', timer.toString());
2108
- });
2109
- return nid;
2110
- }
2111
-
2112
- /**
2113
- * --- 修改 notify 的进度条进度 ---
2114
- * @param notifyId notify id
2115
- * @param per 进度,0 - 100 或 0% - 100% (0 - 1)
2116
- */
2117
- export function notifyProgress(notifyId: number, per: number): void {
2118
- const el: HTMLElement = elements.notify.querySelector(`[data-notifyid="${notifyId}"]`)!;
2119
- if (!el) {
2120
- return;
2121
- }
2122
- const progress: HTMLElement = el.querySelector('.cg-notify-progress')!;
2123
- if (!progress) {
2124
- return;
2125
- }
2126
- if (per > 100) {
2127
- per = 100;
2128
- }
2129
- if (per === 1) {
2130
- const o = parseFloat(progress.style.width);
2131
- if (o > 1) {
2132
- per = 100;
2133
- }
2134
- }
2135
- if (per === 100) {
2136
- progress.style.transitionDelay = '.1s';
2137
- }
2138
- progress.style.width = (per < 1 ? per * 100 : per).toString() + '%';
2139
- }
2140
-
2141
- /**
2142
- * --- 修改 notify 的提示信息 ---
2143
- * @param notifyId notify id
2144
- * @param opt 参数
2145
- */
2146
- export function notifyContent(notifyId: number, opt: types.INotifyContentOptions): void {
2147
- const el: HTMLElement = elements.notify.querySelector(`[data-notifyid="${notifyId}"]`)!;
2148
- if (!el) {
2149
- return;
2150
- }
2151
- if (opt.title) {
2152
- const title: HTMLElement = el.querySelector('.cg-notify-title')!;
2153
- if (!title) {
2154
- return;
2155
- }
2156
- title.innerHTML = tool.escapeHTML(opt.title);
2157
- }
2158
- if (opt.content) {
2159
- const content: HTMLElement = el.querySelector('.cg-notify-content')!;
2160
- if (!content) {
2161
- return;
2162
- }
2163
- content.innerHTML = tool.escapeHTML(opt.content);
2164
- }
2165
- }
2166
-
2167
- /**
2168
- * --- 隐藏 notify ---
2169
- * @param notifyId 要隐藏的 notify id
2170
- */
2171
- export function hideNotify(notifyId: number): void {
2172
- const el: HTMLElement = elements.notify.querySelector(`[data-notifyid="${notifyId}"]`)!;
2173
- if (!el) {
2174
- return;
2175
- }
2176
- clearTimeout(parseInt(el.getAttribute('data-timer')!));
2177
- const notifyHeight = el.offsetHeight;
2178
- el.style.opacity = '0';
2179
- setTimeout(function() {
2180
- notifyBottom += notifyHeight + 10;
2181
- const notifyElementList = document.getElementsByClassName('cg-notify-wrap') as HTMLCollectionOf<HTMLDivElement>;
2182
- let needSub = false;
2183
- for (const notifyElement of notifyElementList) {
2184
- if (notifyElement === el) {
2185
- // --- el 之后的 notify 都要往下移动 ---
2186
- needSub = true;
2187
- continue;
2188
- }
2189
- if (needSub) {
2190
- notifyElement.style.transform = notifyElement.style.transform.replace(/translateY\(([-0-9]+)px\)/,
2191
- function(t: string, t1: string): string {
2192
- return `translateY(${parseInt(t1) + notifyHeight + 10}px)`;
2193
- }
2194
- );
2195
- }
2196
- }
2197
- el.remove();
2198
- }, 100);
2199
- }
2200
-
2201
- /**
2202
- * --- 将标签追加到 pop 层,App 模式下无效 ---
2203
- * @param el 要追加的标签
2204
- */
2205
- export function appendToPop(el: HTMLElement): void {
2206
- elements.popList.appendChild(el);
2207
- }
2208
-
2209
- /**
2210
- * --- 将标签从 pop 层移除,App 模式下无效 ---
2211
- * @param el 要移除的标签
2212
- */
2213
- export function removeFromPop(el: HTMLElement): void {
2214
- elements.popList.removeChild(el);
2215
- }
2216
-
2217
- /** --- 重新调整 pop 的位置 --- */
2218
- function refreshPopPosition(el: HTMLElement, pop: HTMLElement, direction: 'h' | 'v' | 't' | MouseEvent | TouchEvent | { x: number; y: number; }, size: { width?: number; height?: number; } = {}): void {
2219
- // --- 最终 pop 的大小 ---
2220
- const width = size.width ?? pop.offsetWidth;
2221
- const height = size.height ?? pop.offsetHeight;
2222
- // --- 最终显示位置 ---
2223
- let left: number, top: number;
2224
- if (typeof direction === 'string') {
2225
- /** --- 母对象的位置 --- */
2226
- const bcr = el.getBoundingClientRect();
2227
- if (direction === 'v') {
2228
- // --- 垂直弹出 ---
2229
- left = bcr.left;
2230
- top = bcr.top + bcr.height;
2231
- }
2232
- else if (direction === 'h') {
2233
- // --- 水平弹出 ---
2234
- left = bcr.left + bcr.width - 2;
2235
- top = bcr.top - 2;
2236
- }
2237
- else {
2238
- // --- 垂直水平居中 ---
2239
- pop.removeAttribute('data-pop-t-bottom');
2240
- left = bcr.left + bcr.width / 2 - width / 2;
2241
- top = bcr.top - height - 10;
2242
- }
2243
- // --- 下面检测是否出框 ---
2244
- // --- 检查水平是否出框 ---
2245
- if (width + left > window.innerWidth) {
2246
- if (direction === 'v') {
2247
- // --- 垂直弹出 ---
2248
- left = bcr.left + bcr.width - width;
2249
- }
2250
- else if (direction === 'h') {
2251
- // --- 水平弹出,右边位置不够弹到左边 ---
2252
- left = bcr.left - width + 2;
2253
- }
2254
- else {
2255
- // --- 垂直水平居中,水平超出 ---
2256
- left = window.innerWidth - width;
2257
- }
2258
- }
2259
- // --- 检测垂直是否下侧出框 ---
2260
- if (height + top > window.innerHeight) {
2261
- if (direction === 'v') {
2262
- top = bcr.top - height;
2263
- }
2264
- else if (direction === 'h') {
2265
- top = bcr.top + bcr.height - height + 2;
2266
- }
2267
- else {
2268
- // --- 垂直水平居中,下侧出框不管 ---
2269
- }
2270
- }
2271
- else if (top < 0 && direction === 't') {
2272
- // --- 垂直水平居中,上侧出框 ---
2273
- top = bcr.top + bcr.height + 10;
2274
- pop.dataset.popTBottom = '';
2275
- }
2276
- }
2277
- else {
2278
- let x: number;
2279
- let y: number;
2280
- if (direction instanceof MouseEvent || (direction as any).type === 'mousedown') {
2281
- x = (direction as MouseEvent).clientX;
2282
- y = (direction as MouseEvent).clientY;
2283
- }
2284
- else if (direction instanceof TouchEvent || (direction as any).type === 'touchstart') {
2285
- x = (direction as TouchEvent).touches[0].clientX;
2286
- y = (direction as TouchEvent).touches[0].clientY;
2287
- }
2288
- else {
2289
- x = direction.x;
2290
- y = direction.y;
2291
- }
2292
- left = x + 5;
2293
- top = y + 7;
2294
- // --- 水平 ---
2295
- if (width + left > window.innerWidth) {
2296
- left = x - width - 5;
2297
- }
2298
- // --- 垂直 ---
2299
- if (height + top > window.innerHeight) {
2300
- top = y - height - 5;
2301
- }
2302
- }
2303
- if (left < 0) {
2304
- left = 0;
2305
- }
2306
- if (top < 0) {
2307
- top = 0;
2308
- }
2309
- pop.style.left = left.toString() + 'px';
2310
- pop.style.top = top.toString() + 'px';
2311
- pop.style.zIndex = (++popInfo.lastZIndex).toString();
2312
- if (size.width) {
2313
- pop.style.width = size.width.toString() + 'px';
2314
- }
2315
- if (size.height) {
2316
- pop.style.height = size.height.toString() + 'px';
2317
- }
2318
- }
2319
-
2320
- /** --- 最后一次 touchstart 的时间戳 */
2321
- let lastShowPopTime: number = 0;
2322
-
2323
- /**
2324
- * --- 获取 pop 显示出来的坐标并报系统全局记录 ---
2325
- * @param el 响应的元素
2326
- * @param pop 要显示 pop 元素
2327
- * @param direction 要显示方向(以 $el 为准的 h 水平 v 垂直 t 垂直水平居中)或坐标
2328
- * @param opt width / height 显示的 pop 定义自定义宽/高度,可省略;null,true 代表为空也会显示,默认为 false; autoPosition, 因 pop 内部内容变动导致的自动更新 pop 位置,默认 false,autoScroll,因原元素位置变更导致 pop 位置变更,默认 false,flow: 是否加入 pop 流,默认加入,不加入的话将不会自动隐藏
2329
- */
2330
- export function showPop(el: HTMLElement | types.IVue, pop: HTMLElement | types.IVue | undefined, direction: 'h' | 'v' | 't' | MouseEvent | TouchEvent | { x: number; y: number; }, opt: {
2331
- 'size'?: { width?: number; height?: number; };
2332
- 'null'?: boolean;
2333
- 'autoPosition'?: boolean;
2334
- 'autoScroll'?: boolean;
2335
- 'flow'?: boolean;
2336
- /** --- 展示托管方式 --- */
2337
- 'way'?: 'normal' | 'click' | 'hover';
2338
- } = {}): void {
2339
- if (!(el instanceof Element)) {
2340
- if (!el.$el) {
2341
- return;
2342
- }
2343
- el = el.$el;
2344
- }
2345
- if (pop && !(pop instanceof Element)) {
2346
- if (!pop.$el) {
2347
- return;
2348
- }
2349
- pop = pop.$el;
2350
- }
2351
- // --- opt.null 为 true 代表可为空,为空也会被显示,默认为 false ---
2352
- if (opt.null === undefined) {
2353
- opt.null = false;
2354
- }
2355
- if (opt.size === undefined) {
2356
- opt.size = {};
2357
- }
2358
- if (opt.flow === undefined) {
2359
- opt.flow = true;
2360
- }
2361
- // --- 也可能不执行本次显示 ---
2362
- if (!pop && !opt.null) {
2363
- return;
2364
- }
2365
- // --- 是否不进入 pop 流 ---
2366
- if (pop && !opt.flow) {
2367
- pop.removeAttribute('data-cg-pop-none');
2368
- pop.dataset.cgFlow = '';
2369
- clickgo.tool.sleep(34).then(() => {
2370
- if (pop.dataset.cgFlow === undefined) {
2371
- // --- 已经隐藏掉了 ---
2372
- return;
2373
- }
2374
- refreshPopPosition(el, pop, direction, opt.size);
2375
- if (opt.autoPosition) {
2376
- clickgo.dom.watchSize(pop, () => {
2377
- refreshPopPosition(el, pop, direction, opt.size);
2378
- });
2379
- }
2380
- pop.dataset.cgOpen = '';
2381
- }).catch(() => {
2382
- //
2383
- });
2384
- return;
2385
- }
2386
- // --- 如果短时间内已经有了 pop 被展示,则可能是冒泡序列,本次则不展示 ---
2387
- const now = Date.now();
2388
- if (now - lastShowPopTime < 5) {
2389
- lastShowPopTime = now;
2390
- return;
2391
- }
2392
- lastShowPopTime = now;
2393
- // --- 检测是不是已经显示了 ---
2394
- if (el.dataset.cgPopOpen !== undefined) {
2395
- return;
2396
- }
2397
- /** --- 要不要隐藏别的 pop --- */
2398
- const parentPop = dom.findParentByData(el, 'cg-pop');
2399
- if (parentPop?.dataset.cgLevel !== undefined) {
2400
- const nextlevel = parseInt(parentPop.dataset.cgLevel) + 1;
2401
- if (popInfo.elList[nextlevel]) {
2402
- // --- 要隐藏别的 pop ---
2403
- hidePop(popInfo.elList[nextlevel]);
2404
- }
2405
- }
2406
- else {
2407
- // --- 本层不是 pop,因此要隐藏所有 pop ---
2408
- hidePop();
2409
- }
2410
- // --- 检测如果 pop 是 undefined 还显示吗 ---
2411
- if (!pop) {
2412
- popInfo.elList.push(el);
2413
- el.dataset.cgPopOpen = '';
2414
- el.dataset.cgLevel = (popInfo.elList.length - 1).toString();
2415
- return;
2416
- }
2417
- // --- 准备显示 pop ---
2418
- pop.removeAttribute('data-cg-pop-none');
2419
- popInfo.list.push(pop);
2420
- popInfo.elList.push(el);
2421
- popInfo.wayList.push(opt.way ?? 'normal');
2422
- popInfo.time.push(Date.now());
2423
- pop.dataset.cgLevel = (popInfo.list.length - 1).toString();
2424
- el.dataset.cgLevel = (popInfo.elList.length - 1).toString();
2425
- clickgo.tool.sleep(34).then(() => {
2426
- if (pop.dataset.cgLevel === undefined) {
2427
- // --- 已经隐藏掉了 ---
2428
- return;
2429
- }
2430
- // --- 设定 pop 位置 ---
2431
- refreshPopPosition(el, pop, direction, opt.size);
2432
- if (opt.autoPosition && typeof direction === 'string') {
2433
- // --- 可能要重置 pop 位置 ---
2434
- clickgo.dom.watchSize(pop, () => {
2435
- refreshPopPosition(el, pop, direction, opt.size);
2436
- });
2437
- }
2438
- if (opt.autoScroll && typeof direction === 'string') {
2439
- // --- 可能根据原元素重置 pop 位置 ---
2440
- clickgo.dom.watchPosition(el, () => {
2441
- refreshPopPosition(el, pop, direction, opt.size);
2442
- });
2443
- }
2444
- pop.dataset.cgOpen = '';
2445
- el.dataset.cgPopOpen = '';
2446
- }).catch(() => {
2447
- //
2448
- });
2449
- }
2450
-
2451
- /**
2452
- * --- 隐藏正在显示中的所有 pop,或指定 pop/el ---
2453
- */
2454
- export function hidePop(pop?: HTMLElement | types.IVue): void {
2455
- if (pop && !(pop instanceof HTMLElement)) {
2456
- if (!pop.$el) {
2457
- return;
2458
- }
2459
- pop = pop.$el;
2460
- }
2461
- if (!pop) {
2462
- if (popInfo.elList.length === 0) {
2463
- return;
2464
- }
2465
- hidePop(popInfo.elList[0]);
2466
- return;
2467
- }
2468
- if (pop.dataset.cgFlow !== undefined) {
2469
- pop.removeAttribute('data-cg-flow');
2470
- pop.removeAttribute('data-cg-open');
2471
- clickgo.dom.unwatchSize(pop);
2472
- clickgo.tool.sleep(334).then(() => {
2473
- if (pop.dataset.cgFlow !== undefined) {
2474
- return;
2475
- }
2476
- pop.dataset.cgPopNone = '';
2477
- }).catch(() => {
2478
- //
2479
- });
2480
- return;
2481
- }
2482
- if (pop.dataset.cgLevel === undefined) {
2483
- return;
2484
- }
2485
- /** --- 是 pop 还是 el 基 --- */
2486
- const isPop: boolean = pop.dataset.cgPop !== undefined ? true : false;
2487
- /** --- 当前层级 --- */
2488
- const level = pop.dataset.cgLevel ? parseInt(pop.dataset.cgLevel) : -1;
2489
- if (level === -1) {
2490
- return;
2491
- }
2492
- if (popInfo.elList[level + 1]) {
2493
- hidePop(popInfo.elList[level + 1]);
2494
- }
2495
- if (isPop) {
2496
- pop.removeAttribute('data-cg-open');
2497
- pop.removeAttribute('data-cg-level');
2498
- clickgo.dom.unwatchSize(pop);
2499
- clickgo.dom.unwatchPosition(popInfo.elList[level]);
2500
- popInfo.elList[level].removeAttribute('data-cg-pop-open');
2501
- popInfo.elList[level].removeAttribute('data-cg-level');
2502
- clickgo.tool.sleep(334).then(() => {
2503
- if (pop.dataset.cgLevel !== undefined) {
2504
- return;
2505
- }
2506
- pop.dataset.cgPopNone = '';
2507
- }).catch(() => {
2508
- //
2509
- });
2510
- }
2511
- else {
2512
- if (popInfo.list[level]) {
2513
- /** --- el 对应的 pop --- */
2514
- const opop = popInfo.list[level];
2515
- opop.removeAttribute('data-cg-open');
2516
- opop.removeAttribute('data-cg-level');
2517
- clickgo.dom.unwatchSize(popInfo.list[level]);
2518
- clickgo.dom.unwatchPosition(pop);
2519
- clickgo.tool.sleep(334).then(() => {
2520
- if (opop.dataset.cgLevel !== undefined) {
2521
- return;
2522
- }
2523
- opop.dataset.cgPopNone = '';
2524
- }).catch(() => {
2525
- //
2526
- });
2527
- }
2528
- pop.removeAttribute('data-cg-pop-open');
2529
- pop.removeAttribute('data-cg-level');
2530
- }
2531
- popInfo.list.splice(level);
2532
- popInfo.elList.splice(level);
2533
- popInfo.wayList.splice(level);
2534
- popInfo.time.splice(level);
2535
- }
2536
-
2537
- /** --- 检测 pop 是不是刚刚显示的 --- */
2538
- export function isJustPop(el: HTMLElement | number): boolean {
2539
- if (el instanceof HTMLElement) {
2540
- const level = el.dataset.cgLevel;
2541
- if (!level) {
2542
- return false;
2543
- }
2544
- el = parseInt(level);
2545
- }
2546
- const time = popInfo.time[el];
2547
- if (Date.now() - time >= 100) {
2548
- return false;
2549
- }
2550
- return true;
2551
- }
2552
-
2553
- /**
2554
- * --- 点下 (mousedown / touchstart) 屏幕任意一位置时根据点击处处理隐藏 pop 和焦点丢失事件,鼠标和 touch 只会响应一个,App 模式下无效 ---
2555
- * @param e 事件对象
2556
- */
2557
- export function doFocusAndPopEvent(e: MouseEvent | TouchEvent): void {
2558
- if (dom.hasTouchButMouse(e)) {
2559
- return;
2560
- }
2561
- const target = e.target;
2562
- if (!target) {
2563
- return;
2564
- }
2565
- const paths: HTMLElement[] = (e as any).path ?? (e.composedPath ? e.composedPath() : []);
2566
- // --- 检测是不是窗体内部点击 ---
2567
- /** --- 当前点在了触发弹出层的原元素上 --- */
2568
- let isCgPopOpen: HTMLElement | null = null;
2569
- for (const item of paths) {
2570
- if (!item.tagName) {
2571
- continue;
2572
- }
2573
- if (item.dataset.cgLevel !== undefined && item.dataset.cgPop === undefined) {
2574
- isCgPopOpen = item;
2575
- continue;
2576
- }
2577
- if (item.classList.contains('cg-form-wrap')) {
2578
- // --- 窗体内部点击,转换焦点到当前窗体,但触发隐藏 pop ---
2579
- const formId = parseInt(item.getAttribute('data-form-id') ?? '0');
2580
- changeFocus(formId);
2581
- if (isCgPopOpen) {
2582
- if (!isJustPop(isCgPopOpen)) {
2583
- hidePop();
2584
- }
2585
- }
2586
- else {
2587
- hidePop();
2588
- }
2589
- return;
2590
- }
2591
- if (item.tagName.toLowerCase() === 'body') {
2592
- break;
2593
- }
2594
- }
2595
- // --- 检测是不是弹出层 ---
2596
- for (const item of paths) {
2597
- if (!item.tagName) {
2598
- continue;
2599
- }
2600
- if (item.tagName.toLowerCase() === 'body') {
2601
- break;
2602
- }
2603
- if (item.dataset.cgPop !== undefined) {
2604
- // --- 弹出层内点击,不触发丢失焦点
2605
- // --- normal: 不触发隐藏 pop,是否隐藏请自行处理 ---
2606
- // --- click: 自动隐藏下一层 pop,若有 ---
2607
- if (item.dataset.cgLevel) {
2608
- const nextlevel = parseInt(item.dataset.cgLevel) + 1;
2609
- if (popInfo.wayList[nextlevel] === 'click' && !isJustPop(nextlevel)) {
2610
- // --- 隐藏 ---
2611
- clickgo.form.hidePop(popInfo.list[nextlevel]);
2612
- }
2613
- }
2614
- return;
2615
- }
2616
- }
2617
- // --- 普罗大众的状态,要隐藏 menu,并且丢失窗体焦点 ---
2618
- hidePop();
2619
- changeFocus();
2620
- }
2621
- window.addEventListener('touchstart', doFocusAndPopEvent, {
2622
- 'passive': true
2623
- });
2624
- window.addEventListener('mousedown', doFocusAndPopEvent);
2625
-
2626
- /**
2627
- * --- 移除一个 form(关闭窗口),App 模式下无效 ---
2628
- * @param formId 要移除的 form id
2629
- */
2630
- export function remove(formId: number): boolean {
2631
- const taskId: number = getTaskId(formId);
2632
- if (!task.list[taskId].forms[formId]) {
2633
- return false;
2634
- }
2635
- if (task.list[taskId].forms[formId].closed) {
2636
- return false;
2637
- }
2638
- task.list[taskId].forms[formId].closed = true;
2639
- const title = task.list[taskId].forms[formId].vroot.$refs.form.title;
2640
- const icon = task.list[taskId].forms[formId].vroot.$refs.form.iconDataUrl;
2641
- const io = task.list[taskId].runtime.dialogFormIds.indexOf(formId);
2642
- if (io > -1) {
2643
- // --- 取消 dialog mask 记录 ---
2644
- task.list[taskId].runtime.dialogFormIds.splice(io, 1);
2645
- }
2646
- task.list[taskId].forms[formId].vroot.$refs.form.$data.isShow = false;
2647
- setTimeout(function() {
2648
- // --- 获取最大的 z index 窗体,并让他获取焦点 ---
2649
- const fid = getMaxZIndexID({
2650
- 'formIds': [formId]
2651
- });
2652
- if (fid) {
2653
- changeFocus(fid);
2654
- }
2655
- else {
2656
- changeFocus();
2657
- }
2658
- // --- 延长 100 秒是为了响应 100 毫秒的动画 ---
2659
- if (!task.list[taskId]) {
2660
- // --- 可能这时候 task 已经被结束了 ---
2661
- return true;
2662
- }
2663
- task.list[taskId].forms[formId].vapp.unmount();
2664
- task.list[taskId].forms[formId].vapp._container.remove();
2665
- elements.popList.querySelector('[data-form-id="' + formId.toString() + '"]')?.remove();
2666
- if (io > -1) {
2667
- // --- 如果是 dialog 则要执行回调 ---
2668
- task.list[taskId].forms[formId].vroot.cgDialogCallback();
2669
- }
2670
- delete task.list[taskId].forms[formId];
2671
- // --- 移除 form 的 style ---
2672
- dom.removeStyle(taskId, 'form', formId);
2673
- // --- 触发 formRemoved 事件 ---
2674
- core.trigger('formRemoved', taskId, formId, title, icon);
2675
- dom.clearWatchStyle(formId);
2676
- dom.clearWatchProperty(formId);
2677
- dom.clearWatchPosition(formId);
2678
- native.clear(formId, taskId);
2679
- delete activePanels[formId];
2680
- // --- 检测是否已经没有窗体了,如果没有了的话就要结束任务了 ---
2681
- if (Object.keys(task.list[taskId].forms).length === 0) {
2682
- task.end(taskId);
2683
- }
2684
- }, 300);
2685
- return true;
2686
- }
2687
-
2688
- /**
2689
- * --- 移除 panel 挂载,通常发生在 panel 控件的 onBeforeUnmount 中 ---
2690
- * @param id panel id
2691
- * @param vapp panel 的 vapp 对象 ---
2692
- * @param el panel 控件
2693
- */
2694
- export function removePanel(id: number, vapp: types.IVApp, el: HTMLElement): boolean {
2695
- const formWrap = dom.findParentByClass(el, 'cg-form-wrap');
2696
- if (!formWrap) {
2697
- return false;
2698
- }
2699
- const formId = formWrap.dataset.formId;
2700
- if (!formId) {
2701
- return false;
2702
- }
2703
- const taskId = formWrap.dataset.taskId;
2704
- if (!taskId) {
2705
- return false;
2706
- }
2707
- const tid = parseInt(taskId);
2708
- vapp.unmount();
2709
- vapp._container.remove();
2710
- el.querySelector('[data-panel-id="' + id.toString() + '"]')?.remove();
2711
- // --- 移除 form 的 style ---
2712
- dom.removeStyle(tid, 'form', formId, id);
2713
- dom.clearWatchStyle(formId, id);
2714
- dom.clearWatchProperty(formId, id);
2715
- dom.clearWatchPosition(formId, id);
2716
- if (activePanels[formId]) {
2717
- const io = activePanels[formId].indexOf(id);
2718
- if (io >= 0) {
2719
- activePanels[formId].splice(io, 1);
2720
- }
2721
- if (!activePanels[formId].length) {
2722
- delete activePanels[formId];
2723
- }
2724
- }
2725
- return true;
2726
- }
2727
-
2728
- /**
2729
- * --- 根据任务 id 和 form id 获取 IForm 对象,App 模式下无效 ---
2730
- * @param taskId 任务 id
2731
- * @param formId 窗体 id
2732
- */
2733
- function getForm(taskId: number, formId: number): types.IForm | null {
2734
- /** --- 当前的 task 对象 --- */
2735
- const t = task.list[taskId];
2736
- if (!t) {
2737
- return null;
2738
- }
2739
- const form = t.forms[formId];
2740
- if (!form) {
2741
- return null;
2742
- }
2743
- return form;
2744
- }
2745
-
2746
- /**
2747
- * --- 创建 panel 对象,一般情况下无需使用 ---
2748
- * @param cls 路径字符串或 AbstractPanel 类
2749
- * @param el 要挂载的节点
2750
- * @param formId 当前窗体 ID
2751
- * @param taskId 任务ID,App 模式下无效
2752
- */
2753
- export async function createPanel<T extends AbstractPanel>(
2754
- rootPanel: control.AbstractControl,
2755
- cls: string | (new () => T),
2756
- opt: {
2757
- 'layout'?: string;
2758
- 'style'?: string;
2759
- /** --- cls 为 string 时,path 参数才有效,为基准路径,如果不以 / 结尾则以最后一个 / 字符为准 --- */
2760
- 'path'?: string;
2761
- } = {},
2762
- taskId?: number
2763
- ): Promise<{
2764
- 'id': number;
2765
- 'vapp': types.IVApp;
2766
- 'vroot': T;
2767
- }> {
2768
- if (rootPanel.element.dataset.cgControl !== 'panel') {
2769
- const err = new Error('form.createPanel: -0');
2770
- core.trigger('error', 0, 0, err, err.message);
2771
- throw err;
2772
- }
2773
- const formWrap = dom.findParentByData(rootPanel.element, 'form-id');
2774
- if (!formWrap) {
2775
- const err = new Error('form.createPanel: -0');
2776
- core.trigger('error', 0, 0, err, err.message);
2777
- throw err;
2778
- }
2779
- const formId = parseInt(formWrap.dataset.formId!);
2780
- if (!taskId) {
2781
- const err = new Error('form.createPanel: -1');
2782
- core.trigger('error', 0, 0, err, err.message);
2783
- throw err;
2784
- }
2785
- /** --- 当前的 task 对象 --- */
2786
- const t = task.list[taskId];
2787
- if (!t) {
2788
- const err = new Error('form.createPanel: -2');
2789
- core.trigger('error', 0, 0, err, err.message);
2790
- throw err;
2791
- }
2792
- /** --- 布局内容 --- */
2793
- let layout: string = '';
2794
- if (opt.layout) {
2795
- layout = opt.layout;
2796
- }
2797
- /** --- 样式内容 --- */
2798
- let style: string = '';
2799
- /** --- 样式前缀 --- */
2800
- let prep = '';
2801
- if (opt.style) {
2802
- style = opt.style;
2803
- }
2804
- /** --- 文件在包内的路径,不以 / 结尾 --- */
2805
- let filename = '';
2806
- if (typeof cls === 'string') {
2807
- filename = tool.urlResolve(opt.path ?? '/', cls);
2808
- if (!layout) {
2809
- const l = t.app.files[filename + '.xml'];
2810
- if (typeof l !== 'string') {
2811
- const err = new Error('form.createPanel: -3');
2812
- core.trigger('error', 0, 0, err, err.message);
2813
- throw err;
2814
- }
2815
- layout = l;
2816
- }
2817
- if (!style) {
2818
- const s = t.app.files[filename + '.css'];
2819
- if (typeof s === 'string') {
2820
- style = s;
2821
- }
2822
- }
2823
- cls = class extends AbstractPanel {
2824
- public get filename(): string {
2825
- return filename + '.js';
2826
- }
2827
-
2828
- public get taskId(): number {
2829
- return t.id;
2830
- }
2831
- } as (new () => T);
2832
- }
2833
-
2834
- // --- 申请 panelId ---
2835
- const panelId = ++info.lastPanelId;
2836
- /** --- 要新建的 panel 类对象 --- */
2837
- const panel = new cls();
2838
- if (!filename) {
2839
- filename = panel.filename;
2840
- }
2841
- const lio = filename.lastIndexOf('/');
2842
- const path = filename.slice(0, lio);
2843
-
2844
- // --- 布局 ---
2845
- if (!layout) {
2846
- const l = t.app.files[filename.slice(0, -2) + 'xml'];
2847
- if (typeof l !== 'string') {
2848
- const err = new Error('form.createPanel: -4');
2849
- core.trigger('error', 0, 0, err, err.message);
2850
- throw err;
2851
- }
2852
- layout = l;
2853
- }
2854
-
2855
- // --- 样式 ---
2856
- if (!style) {
2857
- const s = t.app.files[filename.slice(0, -2) + 'css'];
2858
- if (typeof s === 'string') {
2859
- style = s;
2860
- }
2861
- }
2862
- if (style) {
2863
- // --- 将 style 中的 tag 标签转换为 class,如 button 变为 .tag-button,然后将 class 进行标准程序,添加 prep 进行区分隔离 ---
2864
- const r = tool.stylePrepend(style);
2865
- prep = r.prep;
2866
- style = await tool.styleUrl2DataUrl(path + '/', r.style, t.app.files);
2867
- }
2868
-
2869
- // --- 纯净化 ---
2870
- layout = tool.purify(layout);
2871
- // --- 标签增加 cg- 前缀,增加 class 为 tag-xxx ---
2872
- layout = tool.layoutAddTagClassAndReTagName(layout, true);
2873
- // --- 给所有控件传递窗体的 focus 信息 ---
2874
- /*
2875
- layout = tool.layoutInsertAttr(layout, ':form-focus=\'formFocus\'', {
2876
- 'include': [/^cg-.+/]
2877
- });
2878
- */
2879
- // --- 给 layout 的 class 增加前置 ---
2880
- const prepList = ['cg-task' + t.id.toString() + '_'];
2881
- if (prep !== '') {
2882
- prepList.push(prep);
2883
- }
2884
- layout = tool.layoutClassPrepend(layout, prepList);
2885
- // --- 给 event 增加包裹 ---
2886
- layout = tool.eventsAttrWrap(layout);
2887
- // --- 给 teleport 做处理 ---
2888
- if (layout.includes('<teleport')) {
2889
- layout = tool.teleportGlue(layout, formId);
2890
- }
2891
- // --- 获取要定义的控件列表 ---
2892
- const components = control.buildComponents(t.id, formId, path);
2893
- if (!components) {
2894
- const err = new Error('form.createPanel: -5');
2895
- core.trigger('error', 0, 0, err, err.message);
2896
- throw err;
2897
- }
2898
- /** --- class 对象类的属性列表 --- */
2899
- const idata: Record<string, any> = {};
2900
- const cdata = Object.entries(panel);
2901
- for (const item of cdata) {
2902
- if (item[0] === 'access') {
2903
- // --- access 属性不放在 data 当中 ---
2904
- continue;
2905
- }
2906
- idata[item[0]] = item[1];
2907
- }
2908
- /** --- class 对象的方法和 getter/setter 列表 --- */
2909
- const prot = tool.getClassPrototype(panel);
2910
- const methods = prot.method;
2911
- const computed = prot.access;
2912
- computed.formId = {
2913
- get: function(): number {
2914
- return formId;
2915
- },
2916
- set: function(this: types.IVue): void {
2917
- notify({
2918
- 'title': 'Error',
2919
- 'content': `The software tries to modify the system variable "formId".\nPath: ${this.filename}`,
2920
- 'type': 'danger'
2921
- });
2922
- return;
2923
- }
2924
- };
2925
- computed.panelId = {
2926
- get: function(): number {
2927
- return panelId;
2928
- },
2929
- set: function(this: types.IVue): void {
2930
- notify({
2931
- 'title': 'Error',
2932
- 'content': `The software tries to modify the system variable "panelId".\nPath: ${this.filename}`,
2933
- 'type': 'danger'
2934
- });
2935
- return;
2936
- }
2937
- };
2938
- computed.path = {
2939
- get: function(): string {
2940
- return path;
2941
- },
2942
- set: function(this: types.IVue): void {
2943
- notify({
2944
- 'title': 'Error',
2945
- 'content': `The software tries to modify the system variable "path".\nPath: ${this.filename}`,
2946
- 'type': 'danger'
2947
- });
2948
- return;
2949
- }
2950
- };
2951
- computed.prep = {
2952
- get: function(): string {
2953
- return prep;
2954
- },
2955
- set: function(this: types.IVue): void {
2956
- notify({
2957
- 'title': 'Error',
2958
- 'content': `The software tries to modify the system variable "prep".\nPath: ${this.filename}`,
2959
- 'type': 'danger'
2960
- });
2961
- return;
2962
- }
2963
- };
2964
- computed.rootForm = {
2965
- get: function(): AbstractForm & Record<string, any> {
2966
- return t.forms[formId].vroot as any;
2967
- },
2968
- set: function(this: types.IVue): void {
2969
- notify({
2970
- 'title': 'Error',
2971
- 'content': `The software tries to modify the system variable "rootForm".\nPath: ${this.filename}`,
2972
- 'type': 'danger'
2973
- });
2974
- return;
2975
- }
2976
- };
2977
- computed.rootPanel = {
2978
- get: function(): control.AbstractControl & Record<string, any> {
2979
- return rootPanel;
2980
- },
2981
- set: function(this: types.IVue): void {
2982
- notify({
2983
- 'title': 'Error',
2984
- 'content': `The software tries to modify the system variable "rootPanel".\nPath: ${this.filename}`,
2985
- 'type': 'danger'
2986
- });
2987
- return;
2988
- }
2989
- };
2990
-
2991
- // --- 插入 dom ---
2992
- rootPanel.element.insertAdjacentHTML('beforeend', `<div data-panel-id="${panelId.toString()}"></div>`);
2993
- if (style) {
2994
- dom.pushStyle(t.id, style, 'form', formId, panelId);
2995
- }
2996
- /** --- panel wrap element 对象 --- */
2997
- const mel: HTMLElement = rootPanel.element.children.item(rootPanel.element.children.length - 1) as HTMLElement;
2998
- mel.style.position = 'absolute';
2999
- /*
3000
- mel.style.pointerEvents = 'none';
3001
- mel.style.opacity = '0';
3002
- */
3003
- mel.style.display = 'none';
3004
-
3005
- // --- 创建 app 对象 ---
3006
- const rtn: {
3007
- 'vapp': types.IVApp;
3008
- 'vroot': types.IVue;
3009
- } = await new Promise(function(resolve) {
3010
- const vapp = clickgo.vue.createApp({
3011
- 'template': layout.replace(/^<cg-panel([\s\S]+)-panel>$/, '<cg-layout$1-layout>'),
3012
- 'data': function() {
3013
- return tool.clone(idata);
3014
- },
3015
- 'methods': methods,
3016
- 'computed': computed,
3017
-
3018
- 'beforeCreate': (panel as any).onBeforeCreate,
3019
- 'created': function(this: types.IVue) {
3020
- if ((panel as any).access) {
3021
- this.access = tool.clone((panel as any).access);
3022
- }
3023
- this.onCreated();
3024
- },
3025
- 'beforeMount': function(this: types.IVue) {
3026
- this.onBeforeMount();
3027
- },
3028
- 'mounted': async function(this: types.IVue) {
3029
- await this.$nextTick();
3030
- (mel.children.item(0) as HTMLElement).style.flex = '1';
3031
- // --- 完成 ---
3032
- resolve({
3033
- 'vapp': vapp,
3034
- 'vroot': this
3035
- });
3036
- },
3037
- 'beforeUpdate': function(this: types.IVue) {
3038
- this.onBeforeUpdate();
3039
- },
3040
- 'updated': async function(this: types.IVue) {
3041
- await this.$nextTick();
3042
- this.onUpdated();
3043
- },
3044
- 'beforeUnmount': function(this: types.IVue) {
3045
- this.onBeforeUnmount();
3046
- },
3047
- 'unmounted': async function(this: types.IVue) {
3048
- await this.$nextTick();
3049
- this.onUnmounted();
3050
- }
3051
- });
3052
- vapp.config.errorHandler = function(err: Error, vm: types.IVue, info: string): void {
3053
- notify({
3054
- 'title': 'Runtime Error',
3055
- 'content': `Message: ${err.message}\nTask id: ${vm.taskId}\nForm id: ${vm.formId}`,
3056
- 'type': 'danger'
3057
- });
3058
- console.error('Runtime Error [form.createPanel.errorHandler]', `Message: ${err.message}\nTask id: ${vm.taskId}\nForm id: ${vm.formId}`, err, info);
3059
- core.trigger('error', vm.taskId, vm.formId, err, info + '(-3,' + vm.taskId + ',' + vm.formId + ')');
3060
- };
3061
- // --- 挂载控件对象到 vapp ---
3062
- for (const key in components) {
3063
- vapp.component(key, components[key]);
3064
- }
3065
- try {
3066
- vapp.mount(mel);
3067
- }
3068
- catch (err: any) {
3069
- notify({
3070
- 'title': 'Runtime Error',
3071
- 'content': `Message: ${err.message}\nTask id: ${t.id}\nForm id: ${formId}`,
3072
- 'type': 'danger'
3073
- });
3074
- console.error('Runtime Error [form.createPanel.mount]', `Message: ${err.message}\nTask id: ${t.id}\nForm id: ${formId}`, err);
3075
- core.trigger('error', t.id, formId, err, err.message);
3076
- }
3077
- });
3078
- // --- 执行 mounted ---
3079
- await tool.sleep(34);
3080
- try {
3081
- await panel.onMounted.call(rtn.vroot);
3082
- }
3083
- catch (err: any) {
3084
- // --- 创建失败,做垃圾回收 ---
3085
- core.trigger('error', rtn.vroot.taskId, rtn.vroot.formId, err, 'Create panel mounted error: -6.');
3086
- try {
3087
- rtn.vapp.unmount();
3088
- }
3089
- catch (err: any) {
3090
- const msg = `Message: ${err.message}\nTask id: ${t.id}\nForm id: ${formId}\nFunction: form.createPanel, unmount.`;
3091
- notify({
3092
- 'title': 'Panel Unmount Error',
3093
- 'content': msg,
3094
- 'type': 'danger'
3095
- });
3096
- console.log('Panel Unmount Error', msg, err);
3097
- }
3098
- rtn.vapp._container.remove();
3099
- dom.clearWatchStyle(rtn.vroot.formId, panelId);
3100
- dom.clearWatchProperty(rtn.vroot.formId, panelId);
3101
- dom.clearWatchPosition(rtn.vroot.formId, panelId);
3102
- // --- 移除 style ---
3103
- dom.removeStyle(rtn.vroot.taskId, 'form', rtn.vroot.formId, panelId);
3104
- throw err;
3105
- }
3106
- return {
3107
- 'id': panelId,
3108
- 'vapp': rtn.vapp,
3109
- 'vroot': rtn.vroot as any
3110
- };
3111
- }
3112
-
3113
- /**
3114
- * --- 创建一个窗体 ---
3115
- * @param cls 路径字符串或 AbstractForm 类
3116
- * @param data 要传递的对象
3117
- * @param opt 其他替换选项
3118
- * @param taskId App 模式下无效
3119
- */
3120
- export async function create<T extends AbstractForm>(
3121
- cls: string | (new () => T),
3122
- data?: Record<string, any>,
3123
- opt: {
3124
- 'layout'?: string;
3125
- 'style'?: string;
3126
- /** --- cls 为 string 时,path 参数才有效,为基准路径,如果不以 / 结尾则以最后一个 / 字符为准 --- */
3127
- 'path'?: string;
3128
- } = {},
3129
- taskId?: number
3130
- ): Promise<T> {
3131
- if (!taskId) {
3132
- const err = new Error('form.create: -1');
3133
- core.trigger('error', 0, 0, err, err.message);
3134
- throw err;
3135
- }
3136
- /** --- 当前的 task 对象 --- */
3137
- const t = task.list[taskId];
3138
- if (!t) {
3139
- const err = new Error('form.create: -2');
3140
- core.trigger('error', 0, 0, err, err.message);
3141
- throw err;
3142
- }
3143
- /** --- 布局内容 --- */
3144
- let layout: string = '';
3145
- if (opt.layout) {
3146
- layout = opt.layout;
3147
- }
3148
- /** --- 样式内容 --- */
3149
- let style: string = '';
3150
- /** --- 样式前缀 --- */
3151
- let prep = '';
3152
- if (opt.style) {
3153
- style = opt.style;
3154
- }
3155
- /** --- 文件在包内的路径,不以 / 结尾 --- */
3156
- let filename = '';
3157
- if (typeof cls === 'string') {
3158
- filename = tool.urlResolve(opt.path ?? '/', cls);
3159
- if (!layout) {
3160
- const l = t.app.files[filename + '.xml'];
3161
- if (typeof l !== 'string') {
3162
- const err = new Error('form.create: -3');
3163
- core.trigger('error', 0, 0, err, err.message);
3164
- throw err;
3165
- }
3166
- layout = l;
3167
- }
3168
- if (!style) {
3169
- const s = t.app.files[filename + '.css'];
3170
- if (typeof s === 'string') {
3171
- style = s;
3172
- }
3173
- }
3174
- cls = class extends AbstractForm {
3175
- public get filename(): string {
3176
- return filename + '.js';
3177
- }
3178
-
3179
- public get taskId(): number {
3180
- return t.id;
3181
- }
3182
- } as (new () => T);
3183
- }
3184
-
3185
- // --- 申请 formId ---
3186
- const formId = ++info.lastId;
3187
- /** --- 要新建的窗体类对象 --- */
3188
- const frm = new cls();
3189
- if (!filename) {
3190
- filename = frm.filename;
3191
- }
3192
- const lio = filename.lastIndexOf('/');
3193
- const path = filename.slice(0, lio);
3194
-
3195
- // --- 布局 ---
3196
- if (!layout) {
3197
- const l = t.app.files[filename.slice(0, -2) + 'xml'];
3198
- if (typeof l !== 'string') {
3199
- const err = new Error('form.create: -4');
3200
- core.trigger('error', 0, 0, err, err.message);
3201
- throw err;
3202
- }
3203
- layout = l;
3204
- }
3205
-
3206
- // --- 样式 ---
3207
- if (!style) {
3208
- const s = t.app.files[filename.slice(0, -2) + 'css'];
3209
- if (typeof s === 'string') {
3210
- style = s;
3211
- }
3212
- }
3213
- if (style) {
3214
- // --- 将 style 中的 tag 标签转换为 class,如 button 变为 .tag-button,然后将 class 进行标准程序,添加 prep 进行区分隔离 ---
3215
- const r = tool.stylePrepend(style);
3216
- prep = r.prep;
3217
- style = await tool.styleUrl2DataUrl(path + '/', r.style, t.app.files);
3218
- }
3219
-
3220
- // --- 纯净化 ---
3221
- layout = tool.purify(layout);
3222
- // --- 标签增加 cg- 前缀,增加 class 为 tag-xxx ---
3223
- layout = tool.layoutAddTagClassAndReTagName(layout, true);
3224
- // --- 给所有控件传递窗体的 focus 信息 ---
3225
- /*
3226
- layout = tool.layoutInsertAttr(layout, ':form-focus=\'formFocus\'', {
3227
- 'include': [/^cg-.+/]
3228
- });
3229
- */
3230
- // --- 给 layout 的 class 增加前置 ---
3231
- const prepList = ['cg-task' + t.id.toString() + '_'];
3232
- if (prep !== '') {
3233
- prepList.push(prep);
3234
- }
3235
- layout = tool.layoutClassPrepend(layout, prepList);
3236
- // --- 给 event 增加包裹 ---
3237
- layout = tool.eventsAttrWrap(layout);
3238
- // --- 给 touchstart 增加 .passive 防止 [Violation] Added non-passive event listener to a scroll-blocking ---
3239
- /*
3240
- layout = layout.replace(/@(touchstart|touchmove|wheel)=/g, '@$1.passive=');
3241
- layout = layout.replace(/@(touchstart|touchmove|wheel)\.not=/g, '@$1=');
3242
- */
3243
- // --- 给 teleport 做处理 ---
3244
- if (layout.includes('<teleport')) {
3245
- layout = tool.teleportGlue(layout, formId);
3246
- }
3247
- // --- 获取要定义的控件列表 ---
3248
- const components = control.buildComponents(t.id, formId, path);
3249
- if (!components) {
3250
- const err = new Error('form.create: -5');
3251
- core.trigger('error', 0, 0, err, err.message);
3252
- throw err;
3253
- }
3254
- /** --- class 对象类的属性列表 --- */
3255
- const idata: Record<string, any> = {};
3256
- const cdata = Object.entries(frm);
3257
- for (const item of cdata) {
3258
- if (item[0] === 'access') {
3259
- // --- access 属性不放在 data 当中 ---
3260
- continue;
3261
- }
3262
- idata[item[0]] = item[1];
3263
- }
3264
- idata._formFocus = false;
3265
- // --- 判断是否要与 native 实体窗体大小同步 ---
3266
- if (clickgo.isNative() && (formId === 1) && !clickgo.isImmersion() && !clickgo.hasFrame()) {
3267
- idata.isNativeSync = true;
3268
- }
3269
- /** --- class 对象的方法和 getter/setter 列表 --- */
3270
- const prot = tool.getClassPrototype(frm);
3271
- const methods = prot.method;
3272
- const computed = prot.access;
3273
- computed.formId = {
3274
- get: function(): number {
3275
- return formId;
3276
- },
3277
- set: function(this: types.IVue): void {
3278
- notify({
3279
- 'title': 'Error',
3280
- 'content': `The software tries to modify the system variable "formId".\nPath: ${this.filename}`,
3281
- 'type': 'danger'
3282
- });
3283
- return;
3284
- }
3285
- };
3286
- computed.path = {
3287
- get: function(): string {
3288
- return path;
3289
- },
3290
- set: function(this: types.IVue): void {
3291
- notify({
3292
- 'title': 'Error',
3293
- 'content': `The software tries to modify the system variable "path".\nPath: ${this.filename}`,
3294
- 'type': 'danger'
3295
- });
3296
- return;
3297
- }
3298
- };
3299
- computed.prep = {
3300
- get: function(): string {
3301
- return prep;
3302
- },
3303
- set: function(this: types.IVue): void {
3304
- notify({
3305
- 'title': 'Error',
3306
- 'content': `The software tries to modify the system variable "prep".\nPath: ${this.filename}`,
3307
- 'type': 'danger'
3308
- });
3309
- return;
3310
- }
3311
- };
3312
- // --- 是否在底层的窗体 ---
3313
- idata._bottomMost = false;
3314
- computed.bottomMost = {
3315
- get: function(this: types.IVue): boolean {
3316
- return this._bottomMost;
3317
- },
3318
- set: function(this: types.IVue, v: boolean): void {
3319
- if (v) {
3320
- // --- 置底 ---
3321
- this._bottomMost = true;
3322
- this.$el.dataset.cgBottomMost = '';
3323
- if (this._topMost) {
3324
- this._topMost = false;
3325
- }
3326
- this.$refs.form.$data.zIndex = ++info.bottomLastZIndex;
3327
- }
3328
- else {
3329
- // --- 取消置底 ---
3330
- this._bottomMost = false;
3331
- this.$el.removeAttribute('data-cg-bottom-most');
3332
- this.$refs.form.$data.zIndex = ++info.lastZIndex;
3333
- }
3334
- }
3335
- };
3336
- // --- 是否在顶层的窗体 ---
3337
- idata._topMost = false;
3338
- computed.topMost = {
3339
- get: function(this: types.IVue): boolean {
3340
- return this._topMost;
3341
- },
3342
- set: function(this: types.IVue, v: boolean): void {
3343
- if (v) {
3344
- // --- 置顶 ---
3345
- this._topMost = true;
3346
- if (this._bottomMost) {
3347
- this._bottomMost = false;
3348
- this.$el.removeAttribute('data-cg-bottom-most');
3349
- }
3350
- if (!this._formFocus) {
3351
- changeFocus(this.formId);
3352
- }
3353
- else {
3354
- this.$refs.form.$data.zIndex = ++info.topLastZIndex;
3355
- }
3356
- }
3357
- else {
3358
- // --- 取消置顶 ---
3359
- this._topMost = false;
3360
- this.$refs.form.$data.zIndex = ++info.lastZIndex;
3361
- }
3362
- }
3363
- };
3364
- // --- 获取和设置 form hash ---
3365
- idata._historyHash = [];
3366
- idata._formHash = '';
3367
- computed.formHash = {
3368
- get: function(this: types.IVue): string {
3369
- return this._formHash;
3370
- },
3371
- set: function(this: types.IVue, v: string): void {
3372
- if (v === this._formHash) {
3373
- return;
3374
- }
3375
- if (Date.now() - this._lastFormHashData > 300) {
3376
- this._formHashData = {};
3377
- }
3378
- if (this.inStep) {
3379
- // --- 在 step 中,要判断 step 是否正确 ---
3380
- (async (): Promise<void> => {
3381
- if (this._stepValues.includes(v)) {
3382
- this.$refs.form.stepValue = v;
3383
- }
3384
- else {
3385
- // --- 不应该 ---
3386
- if (!await clickgo.form.confirm({
3387
- 'taskId': this.taskId,
3388
- 'content': info.locale[this.locale].confirmExitStep
3389
- })) {
3390
- return;
3391
- }
3392
- // --- 退出了 ---
3393
- this._inStep = false;
3394
- this.$refs.form.stepHide();
3395
- }
3396
- if (this._formHash) {
3397
- this._historyHash.push(this._formHash);
3398
- }
3399
- this._formHash = v;
3400
- core.trigger('formHashChange', t.id, formId, v, this._formHashData);
3401
- })() as any;
3402
- return;
3403
- }
3404
- if (this._formHash) {
3405
- this._historyHash.push(this._formHash);
3406
- }
3407
- this._formHash = v;
3408
- core.trigger('formHashChange', t.id, formId, v, this._formHashData);
3409
- }
3410
- };
3411
- // --- 获取和设置 form hash with data 的数据 ---
3412
- idata._lastFormHashData = 0;
3413
- idata._formHashData = {};
3414
- computed.formHashData = {
3415
- get: function(this: types.IVue): string {
3416
- return this._formHashData;
3417
- },
3418
- set: function(this: types.IVue, v: Record<string, any>): void {
3419
- this._formHashData = v;
3420
- this._lastFormHashData = Date.now();
3421
- }
3422
- };
3423
- // --- 当前窗体是否显示在任务栏 ---
3424
- idata._showInSystemTask = true;
3425
- computed.showInSystemTask = {
3426
- get: function(this: types.IVue): number {
3427
- return this._showInSystemTask;
3428
- },
3429
- set: function(this: types.IVue, v: boolean): void {
3430
- this._showInSystemTask = v;
3431
- core.trigger('formShowInSystemTaskChange', t.id, formId, v);
3432
- }
3433
- };
3434
- // --- ready 方法 ---
3435
- const cbs: Array<() => void | Promise<void>> = [];
3436
- methods.ready = function(cb: () => void | Promise<void>): void {
3437
- if (this.isReady) {
3438
- cb() as any;
3439
- return;
3440
- }
3441
- cbs.push(cb);
3442
- };
3443
-
3444
- // --- 插入 dom ---
3445
- elements.list.insertAdjacentHTML('beforeend', `<div class="cg-form-wrap" data-form-id="${formId.toString()}" data-task-id="${t.id.toString()}"></div>`);
3446
- elements.popList.insertAdjacentHTML('beforeend', `<div data-form-id="${formId.toString()}" data-task-id="${t.id.toString()}"></div>`);
3447
- if (style) {
3448
- dom.pushStyle(t.id, style, 'form', formId);
3449
- }
3450
- /** --- form wrap element 对象 --- */
3451
- const el: HTMLElement = elements.list.children.item(elements.list.children.length - 1) as HTMLElement;
3452
-
3453
- // --- 创建 app 对象 ---
3454
- const rtn: {
3455
- 'vapp': types.IVApp;
3456
- 'vroot': types.IVue;
3457
- } = await new Promise(function(resolve) {
3458
- const vapp = clickgo.vue.createApp({
3459
- 'template': layout.replace(/^<cg-form/, '<cg-form ref="form"'),
3460
- 'data': function() {
3461
- return tool.clone(idata);
3462
- },
3463
- 'methods': methods,
3464
- 'computed': computed,
3465
-
3466
- 'beforeCreate': (frm as any).onBeforeCreate,
3467
- 'created': function(this: types.IVue) {
3468
- if ((frm as any).access) {
3469
- this.access = tool.clone((frm as any).access);
3470
- }
3471
- this.onCreated();
3472
- },
3473
- 'beforeMount': function(this: types.IVue) {
3474
- this.onBeforeMount();
3475
- },
3476
- 'mounted': async function(this: types.IVue) {
3477
- await this.$nextTick();
3478
- // --- 判断是否有 icon,对 icon 进行第一次读取 ---
3479
- // --- 为啥要在这搞,因为 form 控件中读取,将可能导致下方的 formCreate 事件获取不到 icon 图标 ---
3480
- // --- 而如果用延迟的方式获取,将可能导致 changeFocus 的窗体焦点事件先于 formCreate 触发 ---
3481
- if (this.$refs.form.icon) {
3482
- const icon = await fs.getContent(this.$refs.form.icon, undefined, taskId);
3483
- this.$refs.form.iconDataUrl = (icon instanceof Blob) ? await tool.blob2DataUrl(icon) : '';
3484
- }
3485
- // --- 完成 ---
3486
- resolve({
3487
- 'vapp': vapp,
3488
- 'vroot': this
3489
- });
3490
- },
3491
- 'beforeUpdate': function(this: types.IVue) {
3492
- this.onBeforeUpdate();
3493
- },
3494
- 'updated': async function(this: types.IVue) {
3495
- await this.$nextTick();
3496
- this.onUpdated();
3497
- },
3498
- 'beforeUnmount': function(this: types.IVue) {
3499
- this.onBeforeUnmount();
3500
- },
3501
- 'unmounted': async function(this: types.IVue) {
3502
- await this.$nextTick();
3503
- this.onUnmounted();
3504
- }
3505
- });
3506
- vapp.config.errorHandler = function(err: Error, vm: types.IVue, info: string): void {
3507
- notify({
3508
- 'title': 'Runtime Error',
3509
- 'content': `Message: ${err.message}\nTask id: ${vm.taskId}\nForm id: ${vm.formId}`,
3510
- 'type': 'danger'
3511
- });
3512
- console.error('Runtime Error [form.create.errorHandler]', `Message: ${err.message}\nTask id: ${vm.taskId}\nForm id: ${vm.formId}`, err, info);
3513
- core.trigger('error', vm.taskId, vm.formId, err, info + '(-3,' + vm.taskId + ',' + vm.formId + ')');
3514
- };
3515
- // --- 挂载控件对象到 vapp ---
3516
- for (const key in components) {
3517
- vapp.component(key, components[key]);
3518
- }
3519
- try {
3520
- vapp.mount(el);
3521
- }
3522
- catch (err: any) {
3523
- notify({
3524
- 'title': 'Runtime Error',
3525
- 'content': `Message: ${err.message}\nTask id: ${t.id}\nForm id: ${formId}`,
3526
- 'type': 'danger'
3527
- });
3528
- console.error('Runtime Error [form.create.mount]', `Message: ${err.message}\nTask id: ${t.id}\nForm id: ${formId}`, err);
3529
- core.trigger('error', t.id, formId, err, err.message);
3530
- }
3531
- });
3532
- // --- 创建 form 信息对象 ---
3533
- const nform: types.IForm = {
3534
- 'id': formId,
3535
- 'vapp': rtn.vapp,
3536
- 'vroot': rtn.vroot,
3537
- 'closed': false
3538
- };
3539
- // --- 挂载 form ---
3540
- t.forms[formId] = nform;
3541
- // --- 执行 mounted ---
3542
- await tool.sleep(34);
3543
- try {
3544
- await frm.onMounted.call(rtn.vroot, data ?? {});
3545
- }
3546
- catch (err: any) {
3547
- // --- 窗体创建失败,做垃圾回收 ---
3548
- core.trigger('error', rtn.vroot.taskId, rtn.vroot.formId, err, 'Create form mounted error: -6.');
3549
- delete t.forms[formId];
3550
- try {
3551
- rtn.vapp.unmount();
3552
- }
3553
- catch (err: any) {
3554
- const msg = `Message: ${err.message}\nTask id: ${t.id}\nForm id: ${formId}\nFunction: form.create, unmount.`;
3555
- notify({
3556
- 'title': 'Form Unmount Error',
3557
- 'content': msg,
3558
- 'type': 'danger'
3559
- });
3560
- console.log('Form Unmount Error', msg, err);
3561
- }
3562
- rtn.vapp._container.remove();
3563
- elements.popList.querySelector('[data-form-id="' + rtn.vroot.formId + '"]')?.remove();
3564
- dom.clearWatchStyle(rtn.vroot.formId);
3565
- dom.clearWatchProperty(rtn.vroot.formId);
3566
- dom.clearWatchPosition(rtn.vroot.formId);
3567
- native.clear(formId, t.id);
3568
- // --- 移除 style ---
3569
- dom.removeStyle(rtn.vroot.taskId, 'form', rtn.vroot.formId);
3570
- throw err;
3571
- }
3572
- // --- 触发 formCreated 事件 ---
3573
- core.trigger('formCreated', t.id, formId, rtn.vroot.$refs.form.title, rtn.vroot.$refs.form.iconDataUrl, rtn.vroot.showInSystemTask);
3574
- // --- 同步的窗体先进行同步一下 ---
3575
- if (rtn.vroot.isNativeSync) {
3576
- await native.invoke('cg-set-size', native.getToken(), rtn.vroot.$refs.form.$el.offsetWidth, rtn.vroot.$refs.form.$el.offsetHeight);
3577
- window.addEventListener('resize', function(): void {
3578
- rtn.vroot.$refs.form.setPropData('width', window.innerWidth);
3579
- rtn.vroot.$refs.form.setPropData('height', window.innerHeight);
3580
- });
3581
- }
3582
- // --- 完全创建完毕 ---
3583
- rtn.vroot.isReady = true;
3584
- for (const cb of cbs) {
3585
- cb.call(rtn.vroot) as any;
3586
- }
3587
- return rtn.vroot as any;
3588
- }
3589
-
3590
- /**
3591
- * --- 显示一个 dialog ---
3592
- * @param opt 选项或者一段文字
3593
- */
3594
- export function dialog(opt: string | types.IFormDialogOptions): Promise<string> {
3595
- return new Promise(function(resolve) {
3596
- if (typeof opt === 'string') {
3597
- opt = {
3598
- 'content': opt
3599
- };
3600
- }
3601
- const filename = tool.urlResolve(opt.path ?? '/', './tmp' + (Math.random() * 100000000000000).toFixed() + '.js');
3602
- const nopt = opt;
3603
- const taskId = nopt.taskId;
3604
- if (!taskId) {
3605
- resolve('');
3606
- return;
3607
- }
3608
- const t = task.list[taskId];
3609
- if (!t) {
3610
- resolve('');
3611
- return;
3612
- }
3613
- const locale = t.locale.lang || core.config.locale;
3614
- if (nopt.buttons === undefined) {
3615
- nopt.buttons = [info.locale[locale]?.ok ?? info.locale['en'].ok];
3616
- }
3617
- const cls = class extends AbstractForm {
3618
- public buttons = nopt.buttons;
3619
-
3620
- public data = nopt.data ?? {};
3621
-
3622
- public methods = nopt.methods ?? {};
3623
-
3624
- public get filename(): string {
3625
- return filename;
3626
- }
3627
-
3628
- public get taskId(): number {
3629
- return taskId;
3630
- }
3631
-
3632
- public select(button: string): void {
3633
- const event: types.IFormDialogSelectEvent = {
3634
- 'go': true,
3635
- preventDefault: function() {
3636
- this.go = false;
3637
- },
3638
- 'detail': {
3639
- 'button': button
3640
- }
3641
- };
3642
- if (nopt.select) {
3643
- nopt.select.call(this, event, button);
3644
- }
3645
- if (event.go) {
3646
- if (nopt.autoDialogResult !== false) {
3647
- this.dialogResult = button;
3648
- }
3649
- close(this.formId);
3650
- }
3651
- }
3652
- };
3653
- create(cls, undefined, {
3654
- 'layout': `<form title="${nopt.title ?? 'dialog'}" min="false" max="false" resize="false" height="0" width="0" border="${nopt.title ? 'normal' : 'plain'}" direction="v"><dialog :buttons="buttons" @select="select"${nopt.direction ? ` direction="${nopt.direction}"` : ''}${nopt.gutter ? ` gutter="${nopt.gutter}"` : ''}>${nopt.content}</dialog></form>`,
3655
- 'style': nopt.style
3656
- }, t.id).then((frm) => {
3657
- if (typeof frm === 'number') {
3658
- resolve('');
3659
- return;
3660
- }
3661
- frm.showDialog().then((v) => {
3662
- resolve(v);
3663
- }).catch(() => {
3664
- resolve('');
3665
- });
3666
- }).catch(() => {
3667
- resolve('');
3668
- });
3669
- });
3670
- }
3671
-
3672
- /**
3673
- * --- 显示一个 confirm ---
3674
- * @param opt 选项或者一段文字
3675
- */
3676
- export async function confirm(opt: string | types.IFormConfirmOptions): Promise<boolean | number> {
3677
- if (typeof opt === 'string') {
3678
- opt = {
3679
- 'content': opt
3680
- };
3681
- }
3682
- const taskId = opt.taskId;
3683
- if (!taskId) {
3684
- return false;
3685
- }
3686
- const t = task.list[taskId];
3687
- if (!t) {
3688
- return false;
3689
- }
3690
- const locale = t.locale.lang || core.config.locale;
3691
- const buttons = [info.locale[locale]?.no ?? info.locale['en'].no, info.locale[locale]?.yes ?? info.locale['en'].yes];
3692
- if (opt.cancel) {
3693
- buttons.unshift(info.locale[locale]?.cancel ?? info.locale['en'].cancel);
3694
- }
3695
- const res = await dialog({
3696
- 'taskId': taskId,
3697
-
3698
- 'title': opt.title,
3699
- 'content': opt.content,
3700
- 'buttons': buttons
3701
- });
3702
- if (res === (info.locale[locale]?.yes ?? info.locale['en'].yes)) {
3703
- return true;
3704
- }
3705
- if (res === (info.locale[locale]?.cancel ?? info.locale['en'].cancel)) {
3706
- return 0;
3707
- }
3708
- return false;
3709
- }
3710
-
3711
- /**
3712
- * --- 显示一个输入框 dialog ---
3713
- * @param opt 选项或者提示文字
3714
- */
3715
- export async function prompt(opt: string | types.IFormPromptOptions): Promise<string> {
3716
- if (typeof opt === 'string') {
3717
- opt = {
3718
- 'content': opt
3719
- };
3720
- }
3721
- const taskId = opt.taskId;
3722
- if (!taskId) {
3723
- return '';
3724
- }
3725
- const t = task.list[taskId];
3726
- if (!t) {
3727
- return '';
3728
- }
3729
- const locale = t.locale.lang || core.config.locale;
3730
- const buttons = [info.locale[locale]?.ok ?? info.locale['en'].ok];
3731
- const cancelBtn = info.locale[locale]?.cancel ?? info.locale['en'].cancel;
3732
- if (opt.cancel === true || opt.cancel === undefined) {
3733
- buttons.unshift(cancelBtn);
3734
- }
3735
- const res = await dialog({
3736
- 'taskId': taskId,
3737
-
3738
- 'title': opt.title,
3739
- 'direction': 'v',
3740
- 'gutter': 10,
3741
- 'content': '<block>' + opt.content + '</block><text v-model="data.text" />',
3742
- 'data': {
3743
- 'text': opt.text ?? ''
3744
- },
3745
- 'select': function(e: types.IFormDialogSelectEvent, button: string) {
3746
- const event: types.IFormPromptSelectEvent = {
3747
- 'go': true,
3748
- preventDefault: function() {
3749
- this.go = false;
3750
- },
3751
- 'detail': {
3752
- 'button': button,
3753
- 'value': this.data.text
3754
- }
3755
- };
3756
- opt.select?.call(this, event, button);
3757
- if (!event.go) {
3758
- e.preventDefault();
3759
- }
3760
- if (e.detail.button === cancelBtn) {
3761
- this.dialogResult = '';
3762
- return;
3763
- }
3764
- this.dialogResult = this.data.text;
3765
- },
3766
- 'buttons': buttons,
3767
- 'autoDialogResult': false
3768
- });
3769
- return res;
3770
- }
3771
-
3772
- /**
3773
- * --- 让窗体闪烁 ---
3774
- * @param formId 要闪烁的窗体 id,必填
3775
- * @param taskId 所属的 taskId,必填,App 模式下仅能闪烁本任务的窗体
3776
- */
3777
- export function flash(formId: number, taskId?: number): void {
3778
- if (!taskId) {
3779
- return;
3780
- }
3781
- const form = getForm(taskId, formId);
3782
- if (!form) {
3783
- return;
3784
- }
3785
- if (!form.vroot._formFocus) {
3786
- changeFocus(form.id);
3787
- }
3788
- if (form.vroot.$refs.form.flashTimer) {
3789
- clearTimeout(form.vroot.$refs.form.flashTimer);
3790
- form.vroot.$refs.form.flashTimer = undefined;
3791
- }
3792
- form.vroot.$refs.form.flashTimer = setTimeout(() => {
3793
- if (form.vroot.$refs.form) {
3794
- form.vroot.$refs.form.flashTimer = undefined;
3795
- }
3796
- }, 1000);
3797
- // --- 触发 formFlash 事件 ---
3798
- core.trigger('formFlash', taskId, formId);
3799
- }
3800
-
3801
- /**
3802
- * --- 显示 launcher 界面 ---
3803
- */
3804
- export function showLauncher(): void {
3805
- elements.launcher.style.display = 'flex';
3806
- requestAnimationFrame(function() {
3807
- elements.launcher.classList.add('cg-show');
3808
- });
3809
- }
3810
-
3811
- /**
3812
- * --- 隐藏 launcher 界面 ---
3813
- */
3814
- export function hideLauncher(): void {
3815
- elements.launcher.classList.remove('cg-show');
3816
- setTimeout(function() {
3817
- if (launcherRoot.folderName !== '') {
3818
- launcherRoot.closeFolder();
3819
- }
3820
- launcherRoot.name = '';
3821
- elements.launcher.style.display = 'none';
3822
- }, 300);
3823
- }
3824
-
3825
- // --- 绑定 resize 事件 ---
3826
- window.addEventListener('resize', function(): void {
3827
- // --- 触发 screenResize 事件 ---
3828
- task.refreshSystemPosition(); // 会在里面自动触发 screenResize 事件
3829
- });