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

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cloud-web-corejs",
3
3
  "private": false,
4
- "version": "1.1.0-dev.20",
4
+ "version": "1.1.0-dev.21",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
7
  "dev:micro": "vue-cli-service serve --port 17527",
@@ -106,7 +106,6 @@ let chartContainers = [],
106
106
  globalConfig: null,
107
107
  // 表单级动态字段规则
108
108
  dynamicFieldEnabled: false,
109
- dynamicFieldRuleVersion: 2,
110
109
  dynamicFieldRunMode: "auto",
111
110
  dynamicFieldAllowValue: false,
112
111
  dynamicFieldSourceType: "local",
@@ -282,13 +281,6 @@ let chartContainers = [],
282
281
  modifiedFlag = true;
283
282
  }
284
283
  if (!!formJson && !!formJson.formConfig) {
285
- // 旧模板缺少版本时不能继承新建模板的 V2 默认值。
286
- this.vueInstance.$set(this.formConfig, "dynamicFieldRuleVersion",
287
- formJson.formConfig.dynamicFieldRuleVersion || 1);
288
- this.vueInstance.$set(this.formConfig, "dynamicFieldRunMode",
289
- formJson.formConfig.dynamicFieldRunMode || "auto");
290
- this.vueInstance.$set(this.formConfig, "dynamicFieldAllowValue",
291
- !!formJson.formConfig.dynamicFieldAllowValue);
292
284
  //this.formConfig = importObj.formConfig
293
285
  baseRefUtil.overwriteObj(
294
286
  this.formConfig,
@@ -1970,13 +1962,6 @@ let chartContainers = [],
1970
1962
  "form__config__backup"
1971
1963
  );
1972
1964
  if (!!formConfigBackup) {
1973
- const restoredConfig = JSON.parse(formConfigBackup);
1974
- this.vueInstance.$set(this.formConfig, "dynamicFieldRuleVersion",
1975
- restoredConfig.dynamicFieldRuleVersion || 1);
1976
- this.vueInstance.$set(this.formConfig, "dynamicFieldRunMode",
1977
- restoredConfig.dynamicFieldRunMode || "auto");
1978
- this.vueInstance.$set(this.formConfig, "dynamicFieldAllowValue",
1979
- !!restoredConfig.dynamicFieldAllowValue);
1980
1965
  //this.formConfig = JSON.parse(formConfigBackup)
1981
1966
  baseRefUtil.overwriteObj(
1982
1967
  this.formConfig,
@@ -120,7 +120,7 @@ modules = {
120
120
  },
121
121
  mounted() {
122
122
  const formRef = this.getFormRef && this.getFormRef();
123
- if (formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2()) {
123
+ if (formRef && formRef.isFieldControlEnabled()) {
124
124
  this.initFieldAttrs();
125
125
  formRef.syncFieldControlValidation();
126
126
  }
@@ -1047,7 +1047,7 @@ modules = {
1047
1047
  },
1048
1048
  buildFieldRules() {
1049
1049
  const formRef = this.getFormRef && this.getFormRef();
1050
- if (formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2() &&
1050
+ if (formRef && formRef.isFieldControlEnabled() &&
1051
1051
  !formRef.isFieldControlFieldVisible(this.field)) {
1052
1052
  this.clearFieldRules();
1053
1053
  return;
@@ -2,123 +2,48 @@
2
2
  <div class="form-dynamic-field-setting">
3
3
  <el-form-item label="启用动态字段">
4
4
  <el-switch v-model="formConfig.dynamicFieldEnabled"></el-switch>
5
- <div class="tip-text">
6
- 根据表单数据/后台脚本动态控制整个表单内任意字段、容器的显隐等状态
7
- </div>
5
+ <el-tooltip content="根据表单数据动态控制字段、容器的显隐、必填、只读和禁用状态。" placement="left" popper-class="xform-setting-help">
6
+ <i class="el-icon-info setting-help-icon" tabindex="0" aria-label="动态字段说明"></i>
7
+ </el-tooltip>
8
8
  </el-form-item>
9
9
 
10
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" />
11
+ <div v-if="fieldControlTreeState.error" class="tip-text tree-options-error">{{ fieldControlTreeState.error }}</div>
12
+ <el-form-item label="执行方式">
13
+ <el-select :value="formConfig.dynamicFieldRunMode || 'auto'" size="mini"
14
+ @input="$set(formConfig, 'dynamicFieldRunMode', $event)">
15
+ <el-option label="按场景默认" value="auto" />
16
+ <el-option label="仅初始化" value="initialOnly" />
17
+ <el-option label="持续生效" value="continuous" />
16
18
  </el-select>
19
+ <el-tooltip content="默认:有流程仅初始化,无流程持续联动。仅初始化保留首次结果;持续模式每轮均由流程规则收尾。" placement="left" popper-class="xform-setting-help">
20
+ <i class="el-icon-info setting-help-icon" tabindex="0" aria-label="执行方式说明"></i>
21
+ </el-tooltip>
17
22
  </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>
23
+ <el-form-item label="字段规则" class="el-form-item-second">
24
+ <el-button
25
+ type="primary"
26
+ plain
27
+ size="mini"
28
+ icon="el-icon-setting"
29
+ @click="openRuleDialog"
30
+ >
31
+ 配置规则({{ (formConfig.dynamicFieldRules || []).length }})
32
+ </el-button>
41
33
  </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
34
  </template>
113
35
 
114
36
  <el-dialog
115
37
  title="表单动态字段规则"
116
38
  :visible.sync="ruleDialogVisible"
117
39
  :append-to-body="true"
118
- width="760px"
40
+ width="880px"
41
+ top="6vh"
119
42
  custom-class="dynamic-field-rule-dialog"
120
43
  >
44
+ <div class="rule-dialog-summary">配置触发条件与字段状态,规则按排列顺序执行。</div>
121
45
  <div class="rule-list">
46
+ <div v-if="!editingRules.length" class="rule-empty">暂无规则,点击下方「添加规则」开始配置</div>
122
47
  <div
123
48
  v-for="(rule, rIdx) in editingRules"
124
49
  :key="rule.id"
@@ -147,13 +72,13 @@
147
72
  <div
148
73
  v-for="(cond, cIdx) in rule.conditions"
149
74
  :key="cIdx"
150
- class="rule-row"
75
+ class="rule-row condition-row"
151
76
  >
152
77
  <el-select
153
78
  v-model="cond.field"
154
79
  filterable
155
80
  size="mini"
156
- placeholder="字段"
81
+ placeholder="选择条件字段"
157
82
  class="cell-field"
158
83
  >
159
84
  <el-option
@@ -189,14 +114,15 @@
189
114
  <el-input
190
115
  v-model="cond.value"
191
116
  size="mini"
192
- placeholder=""
117
+ placeholder="输入比较值"
193
118
  class="cell-value"
194
119
  :disabled="op_noValue(cond.operator)"
195
120
  ></el-input>
196
121
  <el-button
197
122
  type="text"
198
123
  icon="el-icon-remove-outline"
199
- class="danger-text"
124
+ class="danger-text row-remove"
125
+ aria-label="删除条件"
200
126
  @click="removeCondition(rule, cIdx)"
201
127
  ></el-button>
202
128
  </div>
@@ -294,7 +220,8 @@
294
220
  <el-button
295
221
  type="text"
296
222
  icon="el-icon-remove-outline"
297
- class="danger-text"
223
+ class="danger-text row-remove"
224
+ aria-label="删除目标字段"
298
225
  @click="removeTarget(rule, tIdx)"
299
226
  ></el-button>
300
227
  </div>
@@ -319,7 +246,8 @@
319
246
  </el-button>
320
247
  </div>
321
248
 
322
- <div slot="footer">
249
+ <div slot="footer" class="rule-dialog-footer">
250
+ <span class="rule-count">共 {{ editingRules.length }} 条规则</span>
323
251
  <el-button size="mini" @click="ruleDialogVisible = false"
324
252
  >取消</el-button
325
253
  >
@@ -372,9 +300,8 @@
372
300
  </template>
373
301
 
374
302
  <script>
375
- import { fieldControlTreeOptions, normalizeLocalRules } from "@base/components/xform/form-render/fieldControlEngine";
303
+ import { fieldControlTreeOptions, commitLocalFieldRules } from "@base/components/xform/form-render/fieldControlEngine";
376
304
  import i18n from "../../../../components/xform/utils/i18n";
377
- import { buildWidgetTreeOptions } from "../../../../components/xform/form-render/container-item/dynamicFieldEngine";
378
305
 
379
306
  const NO_VALUE_OPERATORS = ["empty", "notEmpty"];
380
307
 
@@ -409,9 +336,6 @@ export default {
409
336
  computed: {
410
337
  fieldControlTreeState() {
411
338
  const list = (this.designer && this.designer.widgetList) || [];
412
- if (this.formConfig.dynamicFieldRuleVersion !== 2) {
413
- return { options: buildWidgetTreeOptions(list), error: "" };
414
- }
415
339
  try {
416
340
  return { options: fieldControlTreeOptions(list), error: "" };
417
341
  } catch (error) {
@@ -531,29 +455,11 @@ export default {
531
455
  rule.targets.splice(idx, 1);
532
456
  },
533
457
  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;
458
+ try {
459
+ this.formConfig.dynamicFieldRules = commitLocalFieldRules(
460
+ this.editingRules, this.designer?.widgetList || []);
461
+ this.ruleDialogVisible = false;
462
+ } catch (error) { this.$message.error(error.message); }
557
463
  },
558
464
  },
559
465
  };
@@ -658,3 +564,124 @@ export default {
658
564
  margin-right: 4px;
659
565
  }
660
566
  </style>
567
+
568
+ <!-- 弹框挂载到 body,以独立类名隔离样式,并覆盖设置面板的控件宽度。 -->
569
+ <style lang="scss">
570
+ .el-dialog.dynamic-field-rule-dialog {
571
+ display: flex;
572
+ flex-direction: column;
573
+ max-width: calc(100vw - 32px);
574
+ max-height: 88vh;
575
+ margin-bottom: 0;
576
+ border-radius: 8px;
577
+
578
+ > .el-dialog__header {
579
+ flex: none;
580
+ padding: 20px 24px;
581
+ border-bottom: 1px solid #ebeef5;
582
+ }
583
+
584
+ > .el-dialog__body {
585
+ display: flex;
586
+ flex-direction: column;
587
+ min-height: 0;
588
+ padding: 16px 24px;
589
+ overflow: hidden;
590
+ }
591
+
592
+ > .el-dialog__footer {
593
+ flex: none;
594
+ padding: 14px 24px;
595
+ border-top: 1px solid #ebeef5;
596
+ }
597
+
598
+ .rule-dialog-summary {
599
+ flex: none;
600
+ margin-bottom: 14px;
601
+ color: #909399;
602
+ font-size: 13px;
603
+ line-height: 20px;
604
+ }
605
+
606
+ .rule-list {
607
+ min-height: 0;
608
+ max-height: none;
609
+ overflow-y: auto;
610
+ overflow-x: hidden;
611
+ padding-right: 4px;
612
+ }
613
+
614
+ .rule-empty {
615
+ padding: 40px 12px;
616
+ text-align: center;
617
+ color: #909399;
618
+ }
619
+
620
+ .rule-card {
621
+ padding: 0 16px 16px;
622
+ margin-bottom: 16px;
623
+ border: 1px solid #e4e7ed;
624
+ border-radius: 6px;
625
+ background: #fff;
626
+ }
627
+
628
+ .rule-card-header {
629
+ min-height: 44px;
630
+ margin: 0 -16px 14px;
631
+ padding: 0 16px;
632
+ background: #f5f7fa;
633
+ border-bottom: 1px solid #ebeef5;
634
+ border-radius: 6px 6px 0 0;
635
+ }
636
+
637
+ .rule-title { font-size: 14px; color: #303133; }
638
+ .rule-section + .rule-section { margin-top: 14px; padding-top: 14px; border-top: 1px dashed #e4e7ed; }
639
+ .rule-section-title { margin-bottom: 12px; gap: 12px; }
640
+
641
+ .rule-row {
642
+ display: grid;
643
+ gap: 10px;
644
+ margin-bottom: 10px;
645
+ min-width: 0;
646
+ }
647
+
648
+ .condition-row { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) 28px; }
649
+ .target-row {
650
+ grid-template-columns: repeat(4, minmax(0, 1fr)) 28px;
651
+ padding: 12px;
652
+ background: #fafbfd;
653
+ border: 1px solid #ebeef5;
654
+ border-radius: 4px;
655
+ }
656
+
657
+ .target-row > .cell-field { grid-column: 1 / 5; }
658
+ .target-row > .row-remove { grid-column: 5; grid-row: 1; }
659
+ .target-attr { display: flex; flex-direction: column; align-items: stretch; gap: 6px; min-width: 0; }
660
+ .target-attr .attr-label { color: #606266; font-size: 12px; line-height: 18px; }
661
+
662
+ .rule-row .el-select,
663
+ .rule-row .el-input,
664
+ .rule-row .cell-field,
665
+ .rule-row .cell-op,
666
+ .rule-row .cell-value,
667
+ .rule-row .cell-tri { width: 100% !important; min-width: 0; margin: 0; }
668
+
669
+ .row-remove { width: 28px; margin: 0; padding: 8px 0; }
670
+ .rule-dialog-footer { display: flex; align-items: center; gap: 10px; }
671
+ .rule-dialog-footer .el-button { margin: 0; }
672
+ .rule-count { margin-right: auto; font-size: 13px; color: #909399; }
673
+
674
+ @media (max-width: 600px) {
675
+ > .el-dialog__body { padding: 12px; }
676
+ .condition-row { grid-template-columns: minmax(0, 1fr) 28px; }
677
+ .condition-row > .cell-field { grid-column: 1; }
678
+ .condition-row > .row-remove { grid-column: 2; grid-row: 1; }
679
+ .condition-row > .cell-op, .condition-row > .cell-value { grid-column: 1; }
680
+ .target-row { grid-template-columns: repeat(2, minmax(0, 1fr)) 28px; }
681
+ .target-row > .cell-field { grid-column: 1 / 3; }
682
+ .target-row > .row-remove { grid-column: 3; }
683
+ .target-attr:nth-of-type(odd) { grid-column: 1; }
684
+ .target-attr:nth-of-type(even) { grid-column: 2; }
685
+ }
686
+ }
687
+ </style>
@@ -5,7 +5,7 @@
5
5
  size="mini"
6
6
  label-position="left"
7
7
  label-width="120px"
8
- class="setting-form"
8
+ class="setting-form form-settings-panel"
9
9
  @submit.native.prevent
10
10
  >
11
11
  <el-collapse v-model="formActiveCollapseNames" class="setting-collapse">
@@ -359,9 +359,12 @@
359
359
  </el-collapse-item>
360
360
  <el-collapse-item name="4" title="列表标签页信息(仅列表表单生效)">
361
361
  <el-form-item label-width="0">
362
- <span style="color:rgba(0,0,0,0.45); font-size: 12px; line-height: 1.6;display:block;">
362
+ <el-tooltip placement="left" popper-class="xform-setting-help">
363
+ <span slot="content">
363
364
  本组配置只从「查询列表页」所用的表单读取,在详情/编辑表单里设置不会生效
364
365
  </span>
366
+ <span>适用范围<i class="el-icon-info setting-help-icon" tabindex="0"></i></span>
367
+ </el-tooltip>
365
368
  </el-form-item>
366
369
  <el-form-item :label="i18nt('列表启用详情多标签')">
367
370
  <el-switch v-model="formConfig.multiTabEnabled"></el-switch>
@@ -379,17 +382,21 @@
379
382
  >
380
383
  <el-switch v-model="formConfig.addTabEnabled"></el-switch>
381
384
  <span style="margin-left: 8px; color: #999; font-size: 12px">
382
- 开启后新增打开独立可关闭页签,需配置「标签名称字段」
385
+ <el-tooltip content="开启后新增打开独立可关闭页签,需配置标签名称字段。" placement="left" popper-class="xform-setting-help">
386
+ <i class="el-icon-info setting-help-icon" tabindex="0" aria-label="新增页签说明"></i>
387
+ </el-tooltip>
383
388
  </span>
384
389
  </el-form-item>
385
- <el-form-item :label="i18nt('流程启动后行为')" class="form-item-label-top">
390
+ <el-form-item class="stacked-setting submit-behavior-setting">
391
+ <span slot="label">{{ i18nt('流程启动后行为') }}
392
+ <el-tooltip content="详情页保存并提交、直接启动流程成功后的行为,不填默认刷新当前页签。" placement="left" popper-class="xform-setting-help">
393
+ <i class="el-icon-info setting-help-icon" tabindex="0" aria-label="流程启动后行为说明"></i>
394
+ </el-tooltip>
395
+ </span>
386
396
  <el-radio-group v-model="formConfig.submitBehavior">
387
397
  <el-radio label="closeToList">关闭页签并回列表刷新</el-radio>
388
398
  <el-radio label="refreshCurrent">刷新当前页签</el-radio>
389
399
  </el-radio-group>
390
- <div style="color: #999; font-size: 12px; line-height: 1.6">
391
- 详情页「保存并提交」「直接启动流程」成功后的行为,不填默认「刷新当前页签」
392
- </div>
393
400
  </el-form-item>
394
401
  </el-collapse-item>
395
402
  <el-collapse-item name="5" title="其他标签页信息">
@@ -406,11 +413,11 @@
406
413
  <el-form-item :label="i18nt('隐藏常规标签')">
407
414
  <el-switch v-model="formConfig.hideNormalTab"></el-switch>
408
415
  </el-form-item>
409
- <el-form-item :label="i18nt('其他标签页配置')" class="form-item-label-top" label-width="0">
416
+ <el-form-item :label="i18nt('其他标签页配置')" class="stacked-setting other-tabs-setting">
410
417
  <div>
411
418
  <draggable
412
419
  tag="ul"
413
- class="draggable-box"
420
+ class="other-tabs-list"
414
421
  :list="formConfig.otherTabList"
415
422
  v-bind="{
416
423
  group: 'optionsGroup',
@@ -1792,3 +1799,61 @@ $\{wf.starterName\}提交的$\{pm_product_project.product_code\}$\{pm_product_pr
1792
1799
  word-break: break-all;
1793
1800
  }
1794
1801
  </style>
1802
+
1803
+ <style lang="scss">
1804
+ .form-settings-panel.setting-form {
1805
+ .el-form-item {
1806
+ min-width: 0;
1807
+ }
1808
+ .el-form-item > .el-form-item__label {
1809
+ flex-shrink: 0;
1810
+ white-space: normal !important;
1811
+ overflow-wrap: anywhere;
1812
+ line-height: 20px;
1813
+ text-align: left;
1814
+ }
1815
+ .el-form-item > .el-form-item__content {
1816
+ min-width: 0;
1817
+ line-height: 28px;
1818
+ }
1819
+ .el-form-item__content > .el-input,
1820
+ .el-form-item__content > .el-select,
1821
+ .el-form-item__content > .el-textarea { width: 100% !important; min-width: 0; }
1822
+ .el-form-item__content > .el-select { width: calc(100% - 28px) !important; }
1823
+ .el-form-item__content > a { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
1824
+ .el-radio { margin: 0 12px 4px 0; white-space: normal; line-height: 22px; }
1825
+ .el-collapse-item__header { height: auto; min-height: 42px; line-height: 22px; padding: 8px 0; }
1826
+ .el-collapse-item__arrow { flex-shrink: 0; }
1827
+ .el-form-item.stacked-setting {
1828
+ display: block !important;
1829
+ > .el-form-item__label {
1830
+ float: none;
1831
+ display: block !important;
1832
+ width: 100% !important;
1833
+ padding: 0 0 8px;
1834
+ }
1835
+ > .el-form-item__content { margin-left: 0 !important; width: 100%; }
1836
+ }
1837
+ .submit-behavior-setting .el-radio-group { display: flex; flex-direction: column; gap: 8px; }
1838
+ .submit-behavior-setting .el-radio { display: flex; align-items: baseline; margin: 0; }
1839
+ .other-tabs-list { padding: 0; margin: 0; width: 100%; }
1840
+ .other-tabs-list > li {
1841
+ display: grid;
1842
+ grid-template-columns: 18px minmax(0, 1fr) 28px;
1843
+ align-items: center;
1844
+ gap: 8px;
1845
+ padding: 10px 8px;
1846
+ margin-bottom: 10px;
1847
+ list-style: none;
1848
+ background: #f5f7fa;
1849
+ border-radius: 4px;
1850
+ }
1851
+ .other-tabs-list > li > .el-input { grid-column: 2; width: 100% !important; min-width: 0; margin: 0; }
1852
+ .other-tabs-list .drag-option { grid-column: 1; grid-row: 1 / 3; cursor: move; color: #909399; }
1853
+ .other-tabs-list .col-delete-button { grid-column: 3; grid-row: 1 / 3; margin: 0; padding: 6px; }
1854
+ .tree-options-error { overflow-wrap: anywhere; margin-bottom: 8px; }
1855
+ }
1856
+ .setting-help-icon { margin-left: 8px; color: #909399; cursor: help; font-size: 14px; }
1857
+ .setting-help-icon:hover, .setting-help-icon:focus { color: #409eff; }
1858
+ .el-tooltip__popper.xform-setting-help { max-width: 300px; line-height: 1.7; overflow-wrap: anywhere; }
1859
+ </style>
@@ -1442,10 +1442,8 @@ modules = {
1442
1442
  const formRef = this.getFormRef();
1443
1443
  if (!formRef || formRef.designState) return;
1444
1444
  this.$nextTick(() => {
1445
- if (formRef.syncFieldControlAfterStructure && formRef.syncFieldControlAfterStructure()) return;
1446
- if (formRef.hanldeCommonWidgetForTableColumns) {
1447
- formRef.hanldeCommonWidgetForTableColumns(tableColumns);
1448
- }
1445
+ if (formRef.syncFieldControlAfterStructure()) return;
1446
+ formRef.hanldeCommonWidgetForTableColumns(tableColumns);
1449
1447
  });
1450
1448
  },
1451
1449
  /** @returns {Boolean} 首次初始化是否跳过动态列加载。 */
@@ -4539,7 +4537,7 @@ modules = {
4539
4537
  try {
4540
4538
  // 行内保存只提交当前行,校验范围同步收敛到当前行:其他行与表外字段
4541
4539
  // 的必填不该拦住单行保存(整体保存仍走 formRef.validate 的全量校验)
4542
- const controlled = formRef.isFieldControlV2 && formRef.isFieldControlV2();
4540
+ const controlled = formRef.isFieldControlEnabled();
4543
4541
  const rowValid = controlled ? await this.validateEditRowAsync(obj.row) : this.validateEditRow(obj.row);
4544
4542
  if (!rowValid) return false;
4545
4543
  const fieldControlSnapshot = controlled ? formRef.captureFieldControlSubmission() : null;
@@ -10,7 +10,7 @@ import {
10
10
  /**
11
11
  * 表格布局容器(table/h5-table)专用混入。
12
12
  *
13
- * 提供两类「布局表格」特有能力(动态字段状态控制已统一收敛到表单设置,见 formDynamicFieldMixin):
13
+ * 提供两类「布局表格」特有能力(动态字段状态控制由表单级 fieldControlMixin 统一处理):
14
14
  * 1. 动态字段(dynamicSchemaSourceType):字段定义由脚本或接口下发,运行时生成并插入到行布局中。
15
15
  * 2. 行折叠(默认行为):整行字段均隐藏时折叠该行(响应式,随字段显隐自动更新)。
16
16
  *
@@ -108,7 +108,7 @@ const dynamicFieldMixin = {
108
108
  }
109
109
  return Promise.resolve(true);
110
110
  } catch (e) {
111
- if (formRef.isFieldControlV2 && formRef.isFieldControlV2()) return Promise.reject(e);
111
+ if (formRef.isFieldControlEnabled()) return Promise.reject(e);
112
112
  return Promise.resolve(false);
113
113
  }
114
114
  },
@@ -118,7 +118,7 @@ const dynamicFieldMixin = {
118
118
  if (!this.supportsRowLayout()) return;
119
119
  if (this.getDynamicSchemaSourceType() === "none") return;
120
120
  const formRef = this.getFormRef();
121
- if (formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2()) {
121
+ if (formRef && formRef.isFieldControlEnabled()) {
122
122
  const task = this.$nextTick().then(() => this.resolveShouldInitDynamicSchema())
123
123
  .then(shouldInit => shouldInit ? this.loadControlledDynamicSchema() : undefined);
124
124
  return formRef.trackFieldControlSchema(task, () => this.loadDynamicSchema(), this);
@@ -176,7 +176,7 @@ const dynamicFieldMixin = {
176
176
  const formRef = this.getFormRef && this.getFormRef();
177
177
  if (!this.designState && this.supportsRowLayout() &&
178
178
  this.getDynamicSchemaSourceType() !== "none" &&
179
- formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2()) {
179
+ formRef && formRef.isFieldControlEnabled()) {
180
180
  return formRef.trackFieldControlSchema(this.loadControlledDynamicSchema(),
181
181
  () => this.loadDynamicSchema(), this);
182
182
  }
@@ -99,7 +99,7 @@ export default {
99
99
  watch: {
100
100
  visibleTabs(tabs) {
101
101
  const formRef = this.getFormRef();
102
- if (formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2() &&
102
+ if (formRef && formRef.isFieldControlEnabled() &&
103
103
  !tabs.some(tab => tab.options.name === this.activeTabName)) {
104
104
  this.activeTabName = tabs.length ? tabs[0].options.name : "";
105
105
  }
@@ -99,6 +99,24 @@ export function normalizeLocalRules(rules) {
99
99
  });
100
100
  }
101
101
 
102
+ export function commitLocalFieldRules(rules, widgetList) {
103
+ const list = Array.isArray(rules) ? rules : [];
104
+ if (!list.length) return [];
105
+ const options = fieldControlTreeOptions(widgetList || []);
106
+ const targets = new Set(options.map(item => item.value));
107
+ const fields = new Set(options.filter(item => !item.isContainer).map(item => item.value));
108
+ const normalized = normalizeLocalRules(list);
109
+ normalized.forEach(rule => {
110
+ rule.conditions.forEach(condition => {
111
+ if (!fields.has(condition.field)) throw new Error("条件字段不存在或不支持:" + condition.field);
112
+ });
113
+ rule.targets.forEach(target => {
114
+ if (!targets.has(target.field)) throw new Error("目标不存在或不支持:" + target.field);
115
+ });
116
+ });
117
+ return normalized;
118
+ }
119
+
102
120
  export function evaluateCondition(condition, model) {
103
121
  if (!condition || !condition.field) throw new Error("请选择动态规则条件字段");
104
122
  const actual = model[condition.field];