cloud-web-corejs 1.1.0-dev.19 → 1.1.0-dev.20

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 (18) hide show
  1. package/package.json +1 -1
  2. package/src/components/baseInputExport/mixins.js +0 -6
  3. package/src/components/xform/docs/2026-09 /345/212/250/346/200/201/345/255/227/346/256/265/350/247/204/345/210/231/344/270/216/346/265/201/347/250/213/346/216/247/345/210/266/346/211/247/350/241/214/351/241/272/345/272/217/350/220/275/345/234/260/346/226/271/346/241/210.md" +409 -0
  4. package/src/components/xform/form-designer/designer.js +2101 -2084
  5. package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +34 -0
  6. package/src/components/xform/form-designer/form-widget/field-widget/fieldMixin.js +13 -1
  7. package/src/components/xform/form-designer/setting-panel/form-dynamicField-setting.vue +660 -602
  8. package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +8 -0
  9. package/src/components/xform/form-render/container-item/data-table-mixin.js +6306 -6285
  10. package/src/components/xform/form-render/container-item/dynamicFieldMixin.js +54 -0
  11. package/src/components/xform/form-render/container-item/tab-item.vue +138 -129
  12. package/src/components/xform/form-render/fieldControlEngine.js +200 -0
  13. package/src/components/xform/form-render/fieldControlMixin.js +524 -0
  14. package/src/components/xform/form-render/formDynamicFieldMixin.js +2 -0
  15. package/src/components/xform/form-render/index.vue +161 -153
  16. package/src/components/xform/form-render/indexMixin.js +5404 -5331
  17. package/src/components/xform/mixins/defaultHandle.js +713 -707
  18. package/src/components/xform/utils/util.js +1711 -1708
@@ -1,602 +1,660 @@
1
- <template>
2
- <div class="form-dynamic-field-setting">
3
- <el-form-item label="启用动态字段">
4
- <el-switch v-model="formConfig.dynamicFieldEnabled"></el-switch>
5
- <div class="tip-text">
6
- 根据表单数据/后台脚本动态控制整个表单内任意字段、容器的显隐等状态
7
- </div>
8
- </el-form-item>
9
-
10
- <template v-if="formConfig.dynamicFieldEnabled">
11
- <el-form-item label="数据来源" class="form-item-label-top">
12
- <el-radio-group v-model="formConfig.dynamicFieldSourceType" size="mini">
13
- <el-radio-button label="local">本地规则</el-radio-button>
14
- <el-radio-button label="script">后台脚本</el-radio-button>
15
- </el-radio-group>
16
- </el-form-item>
17
-
18
- <template v-if="formConfig.dynamicFieldSourceType === 'local'">
19
- <el-form-item label="字段规则" class="el-form-item-second">
20
- <el-button
21
- type="primary"
22
- plain
23
- size="mini"
24
- icon="el-icon-setting"
25
- @click="openRuleDialog"
26
- >
27
- 配置规则({{ (formConfig.dynamicFieldRules || []).length }})
28
- </el-button>
29
- </el-form-item>
30
- </template>
31
-
32
- <template v-if="formConfig.dynamicFieldSourceType === 'script'">
33
- <el-form-item class="form-item-label-top el-form-item-second" label="脚本编码">
34
- <el-input
35
- v-model="formConfig.dynamicFieldScriptCode"
36
- size="mini"
37
- placeholder="scriptCode 或 formCode/scriptCode"
38
- ></el-input>
39
- </el-form-item>
40
- <el-form-item label="额外参数" class="el-form-item-second">
41
- <a
42
- href="javascript:void(0);"
43
- class="a-link link-oneLind"
44
- @click="editDynamicFieldScriptParam"
45
- >
46
- <span>{{ formConfig.dynamicFieldScriptParam }}</span>
47
- <i class="el-icon-edit"></i>
48
- </a>
49
- <div class="tip-text">
50
- 格式: return { stage:
51
- formModel.status }
52
- </div>
53
- </el-form-item>
54
- <el-form-item class="form-item-label-top el-form-item-second" label="触发字段">
55
- <el-select
56
- v-model="formConfig.dynamicFieldTriggerFields"
57
- multiple
58
- filterable
59
- size="mini"
60
- style="width: 100%"
61
- placeholder="字段值变化时重新请求后台"
62
- >
63
- <el-option
64
- v-for="(n, idx) in treeOptions"
65
- :key="n.value + '#' + idx"
66
- :value="n.value"
67
- :label="optPlainLabel(n)"
68
- :disabled="n.isContainer"
69
- >
70
- <span
71
- :style="{ paddingLeft: n.depth * 14 + 'px' }"
72
- :class="{ 'opt-container': n.isContainer }"
73
- >
74
- <span v-if="n.isContainer" class="opt-tag"
75
- >[{{ containerTypeLabel(n.type) }}]</span
76
- >{{ n.baseLabel }}
77
- </span>
78
- </el-option>
79
- </el-select>
80
- <div class="tip-text">为空时仅在初始化时请求一次后台脚本</div>
81
- </el-form-item>
82
- <div class="tip-text">
83
- 后台返回 fieldStates 的 key
84
- 可为字段名,也可为「命名容器」名以控制其显隐(容器仅 visible 生效)
85
- </div>
86
- </template>
87
- </template>
88
-
89
- <el-dialog
90
- title="表单动态字段规则"
91
- :visible.sync="ruleDialogVisible"
92
- :append-to-body="true"
93
- width="760px"
94
- custom-class="dynamic-field-rule-dialog"
95
- >
96
- <div class="rule-list">
97
- <div
98
- v-for="(rule, rIdx) in editingRules"
99
- :key="rule.id"
100
- class="rule-card"
101
- >
102
- <div class="rule-card-header">
103
- <span class="rule-title">规则 {{ rIdx + 1 }}</span>
104
- <el-button
105
- type="text"
106
- icon="el-icon-delete"
107
- class="danger-text"
108
- @click="removeRule(rIdx)"
109
- >
110
- 删除规则
111
- </el-button>
112
- </div>
113
-
114
- <div class="rule-section">
115
- <div class="rule-section-title">
116
- <span>当满足</span>
117
- <el-radio-group v-model="rule.logic" size="mini">
118
- <el-radio-button label="and">全部条件</el-radio-button>
119
- <el-radio-button label="or">任一条件</el-radio-button>
120
- </el-radio-group>
121
- </div>
122
- <div
123
- v-for="(cond, cIdx) in rule.conditions"
124
- :key="cIdx"
125
- class="rule-row"
126
- >
127
- <el-select
128
- v-model="cond.field"
129
- filterable
130
- size="mini"
131
- placeholder="字段"
132
- class="cell-field"
133
- >
134
- <el-option
135
- v-for="(n, idx) in treeOptions"
136
- :key="n.value + '#' + idx"
137
- :value="n.value"
138
- :label="optPlainLabel(n)"
139
- :disabled="n.isContainer"
140
- >
141
- <span
142
- :style="{ paddingLeft: n.depth * 14 + 'px' }"
143
- :class="{ 'opt-container': n.isContainer }"
144
- >
145
- <span v-if="n.isContainer" class="opt-tag"
146
- >[{{ containerTypeLabel(n.type) }}]</span
147
- >{{ n.baseLabel }}
148
- </span>
149
- </el-option>
150
- </el-select>
151
- <el-select
152
- v-model="cond.operator"
153
- size="mini"
154
- placeholder="操作符"
155
- class="cell-op"
156
- >
157
- <el-option
158
- v-for="op in operatorOptions"
159
- :key="op.value"
160
- :label="op.label"
161
- :value="op.value"
162
- ></el-option>
163
- </el-select>
164
- <el-input
165
- v-model="cond.value"
166
- size="mini"
167
- placeholder=""
168
- class="cell-value"
169
- :disabled="op_noValue(cond.operator)"
170
- ></el-input>
171
- <el-button
172
- type="text"
173
- icon="el-icon-remove-outline"
174
- class="danger-text"
175
- @click="removeCondition(rule, cIdx)"
176
- ></el-button>
177
- </div>
178
- <el-button
179
- type="text"
180
- icon="el-icon-plus"
181
- @click="addCondition(rule)"
182
- >添加条件</el-button
183
- >
184
- </div>
185
-
186
- <div class="rule-section">
187
- <div class="rule-section-title"><span>则设置字段/容器</span></div>
188
- <div
189
- v-for="(t, tIdx) in rule.targets"
190
- :key="tIdx"
191
- class="rule-row target-row"
192
- >
193
- <el-select
194
- v-model="t.field"
195
- filterable
196
- size="mini"
197
- placeholder="目标字段/容器"
198
- class="cell-field"
199
- >
200
- <el-option
201
- v-for="(n, idx) in treeOptions"
202
- :key="n.value + '#' + idx"
203
- :value="n.value"
204
- :label="optPlainLabel(n)"
205
- :disabled="n.isContainer && !n.hasName"
206
- >
207
- <span
208
- :style="{ paddingLeft: n.depth * 14 + 'px' }"
209
- :class="{ 'opt-container': n.isContainer }"
210
- >
211
- <span v-if="n.isContainer" class="opt-tag"
212
- >[{{ containerTypeLabel(n.type) }}]</span
213
- >{{ n.baseLabel }}
214
- </span>
215
- </el-option>
216
- </el-select>
217
- <span class="target-attr">
218
- <span class="attr-label">显示</span>
219
- <el-select
220
- v-model="t.visible"
221
- size="mini"
222
- class="cell-tri"
223
- clearable
224
- placeholder="不变"
225
- >
226
- <el-option label="显示" :value="true"></el-option>
227
- <el-option label="隐藏" :value="false"></el-option>
228
- </el-select>
229
- </span>
230
- <span class="target-attr">
231
- <span class="attr-label">必填</span>
232
- <el-select
233
- v-model="t.required"
234
- size="mini"
235
- class="cell-tri"
236
- clearable
237
- placeholder="不变"
238
- >
239
- <el-option label="必填" :value="true"></el-option>
240
- <el-option label="非必填" :value="false"></el-option>
241
- </el-select>
242
- </span>
243
- <span class="target-attr">
244
- <span class="attr-label">只读</span>
245
- <el-select
246
- v-model="t.readonly"
247
- size="mini"
248
- class="cell-tri"
249
- clearable
250
- placeholder="不变"
251
- >
252
- <el-option label="只读" :value="true"></el-option>
253
- <el-option label="可编辑" :value="false"></el-option>
254
- </el-select>
255
- </span>
256
- <span class="target-attr">
257
- <span class="attr-label">禁用</span>
258
- <el-select
259
- v-model="t.disabled"
260
- size="mini"
261
- class="cell-tri"
262
- clearable
263
- placeholder="不变"
264
- >
265
- <el-option label="禁用" :value="true"></el-option>
266
- <el-option label="启用" :value="false"></el-option>
267
- </el-select>
268
- </span>
269
- <el-button
270
- type="text"
271
- icon="el-icon-remove-outline"
272
- class="danger-text"
273
- @click="removeTarget(rule, tIdx)"
274
- ></el-button>
275
- </div>
276
- <el-button type="text" icon="el-icon-plus" @click="addTarget(rule)"
277
- >添加目标字段</el-button
278
- >
279
- <div class="tip-text">
280
- 目标为容器时仅「显示」生效,必填/只读/禁用将被忽略
281
- </div>
282
- </div>
283
- </div>
284
-
285
- <el-button
286
- type="primary"
287
- plain
288
- icon="el-icon-plus"
289
- size="mini"
290
- @click="addRule"
291
- style="width: 100%"
292
- >
293
- 添加规则
294
- </el-button>
295
- </div>
296
-
297
- <div slot="footer">
298
- <el-button size="mini" @click="ruleDialogVisible = false"
299
- >取消</el-button
300
- >
301
- <el-button size="mini" type="primary" @click="saveRules"
302
- >确定</el-button
303
- >
304
- </div>
305
- </el-dialog>
306
-
307
- <el-dialog
308
- title="额外参数"
309
- :visible.sync="scriptParamDialogVisible"
310
- v-if="scriptParamDialogVisible"
311
- :show-close="true"
312
- custom-class="dialog-style list-dialog"
313
- v-dialog-drag
314
- :close-on-click-modal="false"
315
- :close-on-press-escape="false"
316
- :destroy-on-close="true"
317
- :append-to-body="true"
318
- :modal-append-to-body="true"
319
- >
320
- <div class="cont">
321
- <code-editor
322
- :mode="'javascript'"
323
- :readonly="false"
324
- v-model="editingScriptParam"
325
- ref="scriptParamEditor"
326
- ></code-editor>
327
- </div>
328
- <div slot="footer" class="dialog-footer">
329
- <el-button
330
- @click="scriptParamDialogVisible = false"
331
- class="button-sty"
332
- icon="el-icon-close"
333
- >
334
- {{ i18nt("designer.hint.cancel") }}
335
- </el-button>
336
- <el-button
337
- type="primary"
338
- @click="saveDynamicFieldScriptParam"
339
- class="button-sty"
340
- icon="el-icon-check"
341
- >
342
- {{ i18nt("designer.hint.confirm") }}
343
- </el-button>
344
- </div>
345
- </el-dialog>
346
- </div>
347
- </template>
348
-
349
- <script>
350
- import i18n from "../../../../components/xform/utils/i18n";
351
- import { buildWidgetTreeOptions } from "../../../../components/xform/form-render/container-item/dynamicFieldEngine";
352
-
353
- const NO_VALUE_OPERATORS = ["empty", "notEmpty"];
354
-
355
- export default {
356
- name: "form-dynamicField-setting",
357
- mixins: [i18n],
358
- props: {
359
- designer: Object,
360
- formConfig: Object,
361
- },
362
- data() {
363
- return {
364
- ruleDialogVisible: false,
365
- scriptParamDialogVisible: false,
366
- editingScriptParam: "",
367
- editingRules: [],
368
- operatorOptions: [
369
- { label: "等于", value: "eq" },
370
- { label: "不等于", value: "ne" },
371
- { label: "大于", value: "gt" },
372
- { label: "小于", value: "lt" },
373
- { label: "大于等于", value: "ge" },
374
- { label: "小于等于", value: "le" },
375
- { label: "属于(逗号分隔)", value: "in" },
376
- { label: "不属于(逗号分隔)", value: "nin" },
377
- { label: "包含", value: "includes" },
378
- { label: "为空", value: "empty" },
379
- { label: "不为空", value: "notEmpty" },
380
- ],
381
- };
382
- },
383
- computed: {
384
- // 带层级的字段/容器候选(整个表单)
385
- treeOptions() {
386
- return buildWidgetTreeOptions(
387
- (this.designer && this.designer.widgetList) || []
388
- );
389
- },
390
- },
391
- methods: {
392
- op_noValue(operator) {
393
- return NO_VALUE_OPERATORS.indexOf(operator) > -1;
394
- },
395
- // 选中态/过滤用的纯文本标签
396
- optPlainLabel(n) {
397
- if (n.isContainer) {
398
- return "[" + this.containerTypeLabel(n.type) + "] " + n.baseLabel;
399
- }
400
- return n.baseLabel;
401
- },
402
- containerTypeLabel(type) {
403
- const map = {
404
- grid: "栅格",
405
- table: "表格布局",
406
- "h5-table": "H5表格",
407
- tab: "标签页",
408
- "tab-pane": "标签项",
409
- card: "卡片",
410
- "vf-box": "区块",
411
- panel: "面板",
412
- detail: "单据详情",
413
- "detail-plain": "单据详情(简洁)",
414
- "detail-pane": "详情面板",
415
- "detail-h5": "H5详情",
416
- "h5-card": "H5卡片",
417
- "h5-card-pane": "H5卡片面板",
418
- "sub-form": "子表单",
419
- "grid-sub-form": "网格子表单",
420
- };
421
- return map[type] || type;
422
- },
423
- genId() {
424
- return "fdr_" + Date.now() + "_" + Math.floor(Math.random() * 10000);
425
- },
426
- editDynamicFieldScriptParam() {
427
- this.editingScriptParam = this.formConfig.dynamicFieldScriptParam || "";
428
- this.scriptParamDialogVisible = true;
429
- },
430
- saveDynamicFieldScriptParam() {
431
- const codeHints = this.$refs.scriptParamEditor.getEditorAnnotations();
432
- let syntaxErrorFlag = false;
433
- if (codeHints && codeHints.length > 0) {
434
- codeHints.forEach((chItem) => {
435
- if (chItem.type === "error") {
436
- syntaxErrorFlag = true;
437
- }
438
- });
439
- if (syntaxErrorFlag) {
440
- this.$message.error(
441
- this.i18nt("designer.setting.syntaxCheckWarning")
442
- );
443
- return;
444
- }
445
- }
446
- this.formConfig.dynamicFieldScriptParam = this.editingScriptParam;
447
- this.scriptParamDialogVisible = false;
448
- },
449
- openRuleDialog() {
450
- this.editingRules = JSON.parse(
451
- JSON.stringify(this.formConfig.dynamicFieldRules || [])
452
- );
453
- this.editingRules.forEach((rule) => {
454
- if (!rule.id) rule.id = this.genId();
455
- if (!rule.logic) rule.logic = "and";
456
- if (!rule.conditions) rule.conditions = [];
457
- if (!rule.targets) rule.targets = [];
458
- });
459
- this.ruleDialogVisible = true;
460
- },
461
- addRule() {
462
- this.editingRules.push({
463
- id: this.genId(),
464
- logic: "and",
465
- conditions: [{ field: "", operator: "eq", value: "" }],
466
- targets: [
467
- {
468
- field: "",
469
- visible: undefined,
470
- required: undefined,
471
- readonly: undefined,
472
- disabled: undefined,
473
- },
474
- ],
475
- });
476
- },
477
- removeRule(idx) {
478
- this.editingRules.splice(idx, 1);
479
- },
480
- addCondition(rule) {
481
- rule.conditions.push({ field: "", operator: "eq", value: "" });
482
- },
483
- removeCondition(rule, idx) {
484
- rule.conditions.splice(idx, 1);
485
- },
486
- addTarget(rule) {
487
- rule.targets.push({
488
- field: "",
489
- visible: undefined,
490
- required: undefined,
491
- readonly: undefined,
492
- disabled: undefined,
493
- });
494
- },
495
- removeTarget(rule, idx) {
496
- rule.targets.splice(idx, 1);
497
- },
498
- saveRules() {
499
- this.formConfig.dynamicFieldRules = JSON.parse(
500
- JSON.stringify(this.editingRules)
501
- );
502
- this.ruleDialogVisible = false;
503
- },
504
- },
505
- };
506
- </script>
507
-
508
- <style lang="scss" scoped>
509
- .tip-text {
510
- font-size: 12px;
511
- color: #909399;
512
- line-height: 1.4;
513
- margin-top: 2px;
514
- }
515
-
516
- .rule-list {
517
- max-height: 56vh;
518
- overflow-y: auto;
519
- }
520
-
521
- .rule-card {
522
- border: 1px solid #ebeef5;
523
- border-radius: 4px;
524
- padding: 10px 12px;
525
- margin-bottom: 12px;
526
- background: #fafafa;
527
- }
528
-
529
- .rule-card-header {
530
- display: flex;
531
- justify-content: space-between;
532
- align-items: center;
533
- margin-bottom: 6px;
534
-
535
- .rule-title {
536
- font-weight: bold;
537
- font-size: 13px;
538
- }
539
- }
540
-
541
- .rule-section {
542
- margin-top: 6px;
543
- }
544
-
545
- .rule-section-title {
546
- display: flex;
547
- align-items: center;
548
- gap: 8px;
549
- font-size: 13px;
550
- color: #606266;
551
- margin-bottom: 6px;
552
- }
553
-
554
- .rule-row {
555
- display: flex;
556
- align-items: center;
557
- gap: 6px;
558
- margin-bottom: 6px;
559
- flex-wrap: wrap;
560
- }
561
-
562
- .cell-field {
563
- width: 180px;
564
- }
565
-
566
- .cell-op {
567
- width: 120px;
568
- }
569
-
570
- .cell-value {
571
- width: 130px;
572
- }
573
-
574
- .cell-tri {
575
- width: 92px;
576
- }
577
-
578
- .target-attr {
579
- display: flex;
580
- align-items: center;
581
- gap: 2px;
582
-
583
- .attr-label {
584
- font-size: 12px;
585
- color: #909399;
586
- }
587
- }
588
-
589
- .danger-text {
590
- color: #f56c6c;
591
- }
592
-
593
- .opt-container {
594
- color: #409eff;
595
- }
596
-
597
- .opt-tag {
598
- color: #909399;
599
- font-size: 12px;
600
- margin-right: 4px;
601
- }
602
- </style>
1
+ <template>
2
+ <div class="form-dynamic-field-setting">
3
+ <el-form-item label="启用动态字段">
4
+ <el-switch v-model="formConfig.dynamicFieldEnabled"></el-switch>
5
+ <div class="tip-text">
6
+ 根据表单数据/后台脚本动态控制整个表单内任意字段、容器的显隐等状态
7
+ </div>
8
+ </el-form-item>
9
+
10
+ <template v-if="formConfig.dynamicFieldEnabled">
11
+ <el-form-item label="执行规则">
12
+ <el-select :value="formConfig.dynamicFieldRuleVersion || 1" size="mini"
13
+ @input="$set(formConfig, 'dynamicFieldRuleVersion', $event)">
14
+ <el-option label="新版:动态规则后执行流程规则" :value="2" />
15
+ <el-option label="历史兼容(待迁移)" :value="1" />
16
+ </el-select>
17
+ </el-form-item>
18
+ <div v-if="fieldControlTreeState.error" class="tip-text tree-options-error">{{ fieldControlTreeState.error }}</div>
19
+ <template v-if="formConfig.dynamicFieldRuleVersion === 2">
20
+ <el-form-item label="执行方式">
21
+ <el-select :value="formConfig.dynamicFieldRunMode || 'auto'" size="mini"
22
+ @input="$set(formConfig, 'dynamicFieldRunMode', $event)">
23
+ <el-option label="按场景默认" value="auto" />
24
+ <el-option label="仅初始化" value="initialOnly" />
25
+ <el-option label="持续生效" value="continuous" />
26
+ </el-select>
27
+ <div class="tip-text">默认:有流程仅初始化,无流程持续联动。仅初始化保留首次结果;持续模式每轮均由流程规则收尾。</div>
28
+ </el-form-item>
29
+ <el-form-item label="允许自动赋值" v-if="formConfig.dynamicFieldSourceType === 'script'">
30
+ <el-switch :value="!!formConfig.dynamicFieldAllowValue"
31
+ @input="$set(formConfig, 'dynamicFieldAllowValue', $event)" />
32
+ <div class="tip-text">开启前需确认后台 value 覆盖条件;规则未返回 value 时保留用户输入。</div>
33
+ </el-form-item>
34
+ </template>
35
+ <div v-else class="tip-text">历史模板保持原执行顺序。迁移前请核对流程放开、持续联动与后台状态快照。</div>
36
+ <el-form-item label="数据来源" class="form-item-label-top">
37
+ <el-radio-group v-model="formConfig.dynamicFieldSourceType" size="mini">
38
+ <el-radio-button label="local">本地规则</el-radio-button>
39
+ <el-radio-button label="script">后台脚本</el-radio-button>
40
+ </el-radio-group>
41
+ </el-form-item>
42
+
43
+ <template v-if="formConfig.dynamicFieldSourceType === 'local'">
44
+ <el-form-item label="字段规则" class="el-form-item-second">
45
+ <el-button
46
+ type="primary"
47
+ plain
48
+ size="mini"
49
+ icon="el-icon-setting"
50
+ @click="openRuleDialog"
51
+ >
52
+ 配置规则({{ (formConfig.dynamicFieldRules || []).length }})
53
+ </el-button>
54
+ </el-form-item>
55
+ </template>
56
+
57
+ <template v-if="formConfig.dynamicFieldSourceType === 'script'">
58
+ <el-form-item class="form-item-label-top el-form-item-second" label="脚本编码">
59
+ <el-input
60
+ v-model="formConfig.dynamicFieldScriptCode"
61
+ size="mini"
62
+ placeholder="scriptCode 或 formCode/scriptCode"
63
+ ></el-input>
64
+ </el-form-item>
65
+ <el-form-item label="额外参数" class="el-form-item-second">
66
+ <a
67
+ href="javascript:void(0);"
68
+ class="a-link link-oneLind"
69
+ @click="editDynamicFieldScriptParam"
70
+ >
71
+ <span>{{ formConfig.dynamicFieldScriptParam }}</span>
72
+ <i class="el-icon-edit"></i>
73
+ </a>
74
+ <div class="tip-text">
75
+ 格式: return { stage:
76
+ this.formModel.status }
77
+ </div>
78
+ </el-form-item>
79
+ <el-form-item class="form-item-label-top el-form-item-second" label="触发字段">
80
+ <el-select
81
+ v-model="formConfig.dynamicFieldTriggerFields"
82
+ multiple
83
+ filterable
84
+ size="mini"
85
+ style="width: 100%"
86
+ placeholder="字段值变化时重新请求后台"
87
+ >
88
+ <el-option
89
+ v-for="(n, idx) in treeOptions"
90
+ :key="n.value + '#' + idx"
91
+ :value="n.value"
92
+ :label="optPlainLabel(n)"
93
+ :disabled="n.isContainer"
94
+ >
95
+ <span
96
+ :style="{ paddingLeft: n.depth * 14 + 'px' }"
97
+ :class="{ 'opt-container': n.isContainer }"
98
+ >
99
+ <span v-if="n.isContainer" class="opt-tag"
100
+ >[{{ containerTypeLabel(n.type) }}]</span
101
+ >{{ n.baseLabel }}
102
+ </span>
103
+ </el-option>
104
+ </el-select>
105
+ <div class="tip-text">未配置数据触发字段时,编辑数据不会自动请求;新版持续模式在流程上下文刷新时仍会重算。</div>
106
+ </el-form-item>
107
+ <div class="tip-text">
108
+ 后台返回 fieldStates 的 key
109
+ 可为字段名,也可为「命名容器」名以控制其显隐(容器仅 visible 生效)
110
+ </div>
111
+ </template>
112
+ </template>
113
+
114
+ <el-dialog
115
+ title="表单动态字段规则"
116
+ :visible.sync="ruleDialogVisible"
117
+ :append-to-body="true"
118
+ width="760px"
119
+ custom-class="dynamic-field-rule-dialog"
120
+ >
121
+ <div class="rule-list">
122
+ <div
123
+ v-for="(rule, rIdx) in editingRules"
124
+ :key="rule.id"
125
+ class="rule-card"
126
+ >
127
+ <div class="rule-card-header">
128
+ <span class="rule-title">规则 {{ rIdx + 1 }}</span>
129
+ <el-button
130
+ type="text"
131
+ icon="el-icon-delete"
132
+ class="danger-text"
133
+ @click="removeRule(rIdx)"
134
+ >
135
+ 删除规则
136
+ </el-button>
137
+ </div>
138
+
139
+ <div class="rule-section">
140
+ <div class="rule-section-title">
141
+ <span>当满足</span>
142
+ <el-radio-group v-model="rule.logic" size="mini">
143
+ <el-radio-button label="and">全部条件</el-radio-button>
144
+ <el-radio-button label="or">任一条件</el-radio-button>
145
+ </el-radio-group>
146
+ </div>
147
+ <div
148
+ v-for="(cond, cIdx) in rule.conditions"
149
+ :key="cIdx"
150
+ class="rule-row"
151
+ >
152
+ <el-select
153
+ v-model="cond.field"
154
+ filterable
155
+ size="mini"
156
+ placeholder="字段"
157
+ class="cell-field"
158
+ >
159
+ <el-option
160
+ v-for="(n, idx) in treeOptions"
161
+ :key="n.value + '#' + idx"
162
+ :value="n.value"
163
+ :label="optPlainLabel(n)"
164
+ :disabled="n.isContainer"
165
+ >
166
+ <span
167
+ :style="{ paddingLeft: n.depth * 14 + 'px' }"
168
+ :class="{ 'opt-container': n.isContainer }"
169
+ >
170
+ <span v-if="n.isContainer" class="opt-tag"
171
+ >[{{ containerTypeLabel(n.type) }}]</span
172
+ >{{ n.baseLabel }}
173
+ </span>
174
+ </el-option>
175
+ </el-select>
176
+ <el-select
177
+ v-model="cond.operator"
178
+ size="mini"
179
+ placeholder="操作符"
180
+ class="cell-op"
181
+ >
182
+ <el-option
183
+ v-for="op in operatorOptions"
184
+ :key="op.value"
185
+ :label="op.label"
186
+ :value="op.value"
187
+ ></el-option>
188
+ </el-select>
189
+ <el-input
190
+ v-model="cond.value"
191
+ size="mini"
192
+ placeholder="值"
193
+ class="cell-value"
194
+ :disabled="op_noValue(cond.operator)"
195
+ ></el-input>
196
+ <el-button
197
+ type="text"
198
+ icon="el-icon-remove-outline"
199
+ class="danger-text"
200
+ @click="removeCondition(rule, cIdx)"
201
+ ></el-button>
202
+ </div>
203
+ <el-button
204
+ type="text"
205
+ icon="el-icon-plus"
206
+ @click="addCondition(rule)"
207
+ >添加条件</el-button
208
+ >
209
+ </div>
210
+
211
+ <div class="rule-section">
212
+ <div class="rule-section-title"><span>则设置字段/容器</span></div>
213
+ <div
214
+ v-for="(t, tIdx) in rule.targets"
215
+ :key="tIdx"
216
+ class="rule-row target-row"
217
+ >
218
+ <el-select
219
+ v-model="t.field"
220
+ filterable
221
+ size="mini"
222
+ placeholder="目标字段/容器"
223
+ class="cell-field"
224
+ >
225
+ <el-option
226
+ v-for="(n, idx) in treeOptions"
227
+ :key="n.value + '#' + idx"
228
+ :value="n.value"
229
+ :label="optPlainLabel(n)"
230
+ :disabled="n.isContainer && !n.hasName"
231
+ >
232
+ <span
233
+ :style="{ paddingLeft: n.depth * 14 + 'px' }"
234
+ :class="{ 'opt-container': n.isContainer }"
235
+ >
236
+ <span v-if="n.isContainer" class="opt-tag"
237
+ >[{{ containerTypeLabel(n.type) }}]</span
238
+ >{{ n.baseLabel }}
239
+ </span>
240
+ </el-option>
241
+ </el-select>
242
+ <span class="target-attr">
243
+ <span class="attr-label">显示</span>
244
+ <el-select
245
+ v-model="t.visible"
246
+ size="mini"
247
+ class="cell-tri"
248
+ clearable
249
+ placeholder="不变"
250
+ >
251
+ <el-option label="显示" :value="true"></el-option>
252
+ <el-option label="隐藏" :value="false"></el-option>
253
+ </el-select>
254
+ </span>
255
+ <span class="target-attr">
256
+ <span class="attr-label">必填</span>
257
+ <el-select
258
+ v-model="t.required"
259
+ size="mini"
260
+ class="cell-tri"
261
+ clearable
262
+ placeholder="不变"
263
+ >
264
+ <el-option label="必填" :value="true"></el-option>
265
+ <el-option label="非必填" :value="false"></el-option>
266
+ </el-select>
267
+ </span>
268
+ <span class="target-attr">
269
+ <span class="attr-label">只读</span>
270
+ <el-select
271
+ v-model="t.readonly"
272
+ size="mini"
273
+ class="cell-tri"
274
+ clearable
275
+ placeholder="不变"
276
+ >
277
+ <el-option label="只读" :value="true"></el-option>
278
+ <el-option label="可编辑" :value="false"></el-option>
279
+ </el-select>
280
+ </span>
281
+ <span class="target-attr">
282
+ <span class="attr-label">禁用</span>
283
+ <el-select
284
+ v-model="t.disabled"
285
+ size="mini"
286
+ class="cell-tri"
287
+ clearable
288
+ placeholder="不变"
289
+ >
290
+ <el-option label="禁用" :value="true"></el-option>
291
+ <el-option label="启用" :value="false"></el-option>
292
+ </el-select>
293
+ </span>
294
+ <el-button
295
+ type="text"
296
+ icon="el-icon-remove-outline"
297
+ class="danger-text"
298
+ @click="removeTarget(rule, tIdx)"
299
+ ></el-button>
300
+ </div>
301
+ <el-button type="text" icon="el-icon-plus" @click="addTarget(rule)"
302
+ >添加目标字段</el-button
303
+ >
304
+ <div class="tip-text">
305
+ 目标为容器时仅「显示」生效,必填/只读/禁用将被忽略
306
+ </div>
307
+ </div>
308
+ </div>
309
+
310
+ <el-button
311
+ type="primary"
312
+ plain
313
+ icon="el-icon-plus"
314
+ size="mini"
315
+ @click="addRule"
316
+ style="width: 100%"
317
+ >
318
+ 添加规则
319
+ </el-button>
320
+ </div>
321
+
322
+ <div slot="footer">
323
+ <el-button size="mini" @click="ruleDialogVisible = false"
324
+ >取消</el-button
325
+ >
326
+ <el-button size="mini" type="primary" @click="saveRules"
327
+ >确定</el-button
328
+ >
329
+ </div>
330
+ </el-dialog>
331
+
332
+ <el-dialog
333
+ title="额外参数"
334
+ :visible.sync="scriptParamDialogVisible"
335
+ v-if="scriptParamDialogVisible"
336
+ :show-close="true"
337
+ custom-class="dialog-style list-dialog"
338
+ v-dialog-drag
339
+ :close-on-click-modal="false"
340
+ :close-on-press-escape="false"
341
+ :destroy-on-close="true"
342
+ :append-to-body="true"
343
+ :modal-append-to-body="true"
344
+ >
345
+ <div class="cont">
346
+ <code-editor
347
+ :mode="'javascript'"
348
+ :readonly="false"
349
+ v-model="editingScriptParam"
350
+ ref="scriptParamEditor"
351
+ ></code-editor>
352
+ </div>
353
+ <div slot="footer" class="dialog-footer">
354
+ <el-button
355
+ @click="scriptParamDialogVisible = false"
356
+ class="button-sty"
357
+ icon="el-icon-close"
358
+ >
359
+ {{ i18nt("designer.hint.cancel") }}
360
+ </el-button>
361
+ <el-button
362
+ type="primary"
363
+ @click="saveDynamicFieldScriptParam"
364
+ class="button-sty"
365
+ icon="el-icon-check"
366
+ >
367
+ {{ i18nt("designer.hint.confirm") }}
368
+ </el-button>
369
+ </div>
370
+ </el-dialog>
371
+ </div>
372
+ </template>
373
+
374
+ <script>
375
+ import { fieldControlTreeOptions, normalizeLocalRules } from "@base/components/xform/form-render/fieldControlEngine";
376
+ import i18n from "../../../../components/xform/utils/i18n";
377
+ import { buildWidgetTreeOptions } from "../../../../components/xform/form-render/container-item/dynamicFieldEngine";
378
+
379
+ const NO_VALUE_OPERATORS = ["empty", "notEmpty"];
380
+
381
+ export default {
382
+ name: "form-dynamicField-setting",
383
+ mixins: [i18n],
384
+ props: {
385
+ designer: Object,
386
+ formConfig: Object,
387
+ },
388
+ data() {
389
+ return {
390
+ ruleDialogVisible: false,
391
+ scriptParamDialogVisible: false,
392
+ editingScriptParam: "",
393
+ editingRules: [],
394
+ operatorOptions: [
395
+ { label: "等于", value: "eq" },
396
+ { label: "不等于", value: "ne" },
397
+ { label: "大于", value: "gt" },
398
+ { label: "小于", value: "lt" },
399
+ { label: "大于等于", value: "ge" },
400
+ { label: "小于等于", value: "le" },
401
+ { label: "属于(逗号分隔)", value: "in" },
402
+ { label: "不属于(逗号分隔)", value: "nin" },
403
+ { label: "包含", value: "includes" },
404
+ { label: "为空", value: "empty" },
405
+ { label: "不为空", value: "notEmpty" },
406
+ ],
407
+ };
408
+ },
409
+ computed: {
410
+ fieldControlTreeState() {
411
+ const list = (this.designer && this.designer.widgetList) || [];
412
+ if (this.formConfig.dynamicFieldRuleVersion !== 2) {
413
+ return { options: buildWidgetTreeOptions(list), error: "" };
414
+ }
415
+ try {
416
+ return { options: fieldControlTreeOptions(list), error: "" };
417
+ } catch (error) {
418
+ return { options: [], error: error.message || "动态规则目标无效" };
419
+ }
420
+ },
421
+ // 带层级的字段/容器候选(整个表单)
422
+ treeOptions() {
423
+ return this.fieldControlTreeState.options;
424
+ },
425
+ },
426
+ methods: {
427
+ op_noValue(operator) {
428
+ return NO_VALUE_OPERATORS.indexOf(operator) > -1;
429
+ },
430
+ // 选中态/过滤用的纯文本标签
431
+ optPlainLabel(n) {
432
+ if (n.isContainer) {
433
+ return "[" + this.containerTypeLabel(n.type) + "] " + n.baseLabel;
434
+ }
435
+ return n.baseLabel;
436
+ },
437
+ containerTypeLabel(type) {
438
+ const map = {
439
+ grid: "栅格",
440
+ table: "表格布局",
441
+ "h5-table": "H5表格",
442
+ tab: "标签页",
443
+ "tab-pane": "标签项",
444
+ card: "卡片",
445
+ "vf-box": "区块",
446
+ panel: "面板",
447
+ detail: "单据详情",
448
+ "detail-plain": "单据详情(简洁)",
449
+ "detail-pane": "详情面板",
450
+ "detail-h5": "H5详情",
451
+ "h5-card": "H5卡片",
452
+ "h5-card-pane": "H5卡片面板",
453
+ "sub-form": "子表单",
454
+ "grid-sub-form": "网格子表单",
455
+ };
456
+ return map[type] || type;
457
+ },
458
+ genId() {
459
+ return "fdr_" + Date.now() + "_" + Math.floor(Math.random() * 10000);
460
+ },
461
+ editDynamicFieldScriptParam() {
462
+ this.editingScriptParam = this.formConfig.dynamicFieldScriptParam || "";
463
+ this.scriptParamDialogVisible = true;
464
+ },
465
+ saveDynamicFieldScriptParam() {
466
+ const codeHints = this.$refs.scriptParamEditor.getEditorAnnotations();
467
+ let syntaxErrorFlag = false;
468
+ if (codeHints && codeHints.length > 0) {
469
+ codeHints.forEach((chItem) => {
470
+ if (chItem.type === "error") {
471
+ syntaxErrorFlag = true;
472
+ }
473
+ });
474
+ if (syntaxErrorFlag) {
475
+ this.$message.error(
476
+ this.i18nt("designer.setting.syntaxCheckWarning")
477
+ );
478
+ return;
479
+ }
480
+ }
481
+ this.formConfig.dynamicFieldScriptParam = this.editingScriptParam;
482
+ this.scriptParamDialogVisible = false;
483
+ },
484
+ openRuleDialog() {
485
+ this.editingRules = JSON.parse(
486
+ JSON.stringify(this.formConfig.dynamicFieldRules || [])
487
+ );
488
+ this.editingRules.forEach((rule) => {
489
+ if (!rule.id) rule.id = this.genId();
490
+ if (!rule.logic) rule.logic = "and";
491
+ if (!rule.conditions) rule.conditions = [];
492
+ if (!rule.targets) rule.targets = [];
493
+ });
494
+ this.ruleDialogVisible = true;
495
+ },
496
+ addRule() {
497
+ this.editingRules.push({
498
+ id: this.genId(),
499
+ logic: "and",
500
+ conditions: [{ field: "", operator: "eq", value: "" }],
501
+ targets: [
502
+ {
503
+ field: "",
504
+ visible: undefined,
505
+ required: undefined,
506
+ readonly: undefined,
507
+ disabled: undefined,
508
+ },
509
+ ],
510
+ });
511
+ },
512
+ removeRule(idx) {
513
+ this.editingRules.splice(idx, 1);
514
+ },
515
+ addCondition(rule) {
516
+ rule.conditions.push({ field: "", operator: "eq", value: "" });
517
+ },
518
+ removeCondition(rule, idx) {
519
+ rule.conditions.splice(idx, 1);
520
+ },
521
+ addTarget(rule) {
522
+ rule.targets.push({
523
+ field: "",
524
+ visible: undefined,
525
+ required: undefined,
526
+ readonly: undefined,
527
+ disabled: undefined,
528
+ });
529
+ },
530
+ removeTarget(rule, idx) {
531
+ rule.targets.splice(idx, 1);
532
+ },
533
+ saveRules() {
534
+ if (this.formConfig.dynamicFieldRuleVersion === 2) {
535
+ try {
536
+ const options = fieldControlTreeOptions(this.designer?.widgetList || []);
537
+ const targets = new Set(options.map(item => item.value));
538
+ const fields = new Set(options.filter(item => !item.isContainer).map(item => item.value));
539
+ const normalized = normalizeLocalRules(this.editingRules);
540
+ normalized.forEach(rule => {
541
+ rule.conditions.forEach(condition => {
542
+ if (!fields.has(condition.field)) throw new Error("条件字段不存在或不支持:" + condition.field);
543
+ });
544
+ rule.targets.forEach(target => {
545
+ if (!targets.has(target.field)) throw new Error("目标不存在或不支持:" + target.field);
546
+ });
547
+ });
548
+ this.formConfig.dynamicFieldRules = normalized;
549
+ this.ruleDialogVisible = false;
550
+ } catch (error) { this.$message.error(error.message); }
551
+ return;
552
+ }
553
+ this.formConfig.dynamicFieldRules = JSON.parse(
554
+ JSON.stringify(this.editingRules)
555
+ );
556
+ this.ruleDialogVisible = false;
557
+ },
558
+ },
559
+ };
560
+ </script>
561
+
562
+ <style lang="scss" scoped>
563
+ .tip-text {
564
+ font-size: 12px;
565
+ color: #909399;
566
+ line-height: 1.4;
567
+ margin-top: 2px;
568
+ }
569
+
570
+ .tree-options-error {
571
+ color: #f56c6c;
572
+ }
573
+
574
+ .rule-list {
575
+ max-height: 56vh;
576
+ overflow-y: auto;
577
+ }
578
+
579
+ .rule-card {
580
+ border: 1px solid #ebeef5;
581
+ border-radius: 4px;
582
+ padding: 10px 12px;
583
+ margin-bottom: 12px;
584
+ background: #fafafa;
585
+ }
586
+
587
+ .rule-card-header {
588
+ display: flex;
589
+ justify-content: space-between;
590
+ align-items: center;
591
+ margin-bottom: 6px;
592
+
593
+ .rule-title {
594
+ font-weight: bold;
595
+ font-size: 13px;
596
+ }
597
+ }
598
+
599
+ .rule-section {
600
+ margin-top: 6px;
601
+ }
602
+
603
+ .rule-section-title {
604
+ display: flex;
605
+ align-items: center;
606
+ gap: 8px;
607
+ font-size: 13px;
608
+ color: #606266;
609
+ margin-bottom: 6px;
610
+ }
611
+
612
+ .rule-row {
613
+ display: flex;
614
+ align-items: center;
615
+ gap: 6px;
616
+ margin-bottom: 6px;
617
+ flex-wrap: wrap;
618
+ }
619
+
620
+ .cell-field {
621
+ width: 180px;
622
+ }
623
+
624
+ .cell-op {
625
+ width: 120px;
626
+ }
627
+
628
+ .cell-value {
629
+ width: 130px;
630
+ }
631
+
632
+ .cell-tri {
633
+ width: 92px;
634
+ }
635
+
636
+ .target-attr {
637
+ display: flex;
638
+ align-items: center;
639
+ gap: 2px;
640
+
641
+ .attr-label {
642
+ font-size: 12px;
643
+ color: #909399;
644
+ }
645
+ }
646
+
647
+ .danger-text {
648
+ color: #f56c6c;
649
+ }
650
+
651
+ .opt-container {
652
+ color: #409eff;
653
+ }
654
+
655
+ .opt-tag {
656
+ color: #909399;
657
+ font-size: 12px;
658
+ margin-right: 4px;
659
+ }
660
+ </style>