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.
@@ -30,7 +30,12 @@ export default {
30
30
  computed: {
31
31
  fieldControlBlocked() {
32
32
  // 仅准备期隐藏,避免用户在流程限制生效前操作。error 要保留已填内容,提交由 ensure 拦截。
33
- return this.isFieldControlV2() && this.fieldControlStatus === "preparing";
33
+ return this.isFieldControlEnabled() && this.fieldControlStatus === "preparing";
34
+ },
35
+ fieldControlInteractionBlocked() {
36
+ // 错误时保留表单内容可见,但不允许继续编辑或触发字段/按钮副作用。
37
+ return this.isFieldControlEnabled()
38
+ && ["preparing", "error"].includes(this.fieldControlStatus);
34
39
  },
35
40
  fieldControlContextKey() {
36
41
  const info = this.wfParam?.wfInfo || {};
@@ -43,7 +48,7 @@ export default {
43
48
  watch: {
44
49
  fieldControlContextKey() {
45
50
  const runtime = this._fieldControl;
46
- if (!runtime || !this.isFieldControlV2()) return;
51
+ if (!runtime || !this.isFieldControlEnabled()) return;
47
52
  if (!this.fieldControlInitialized) {
48
53
  if (runtime.mounting || runtime.task) runtime.revision++;
49
54
  return;
@@ -52,7 +57,8 @@ export default {
52
57
  runtime.mode = resolveRunMode(this.formConfig, !!this.wfParam?.hasWf);
53
58
  this.stopFieldControlWatcher();
54
59
  if (previousMode !== runtime.mode || runtime.mode === "continuous") {
55
- this.invalidateFieldControl();
60
+ // 流程节点是权限边界,不能在防抖/后台请求期间继续使用旧节点状态。
61
+ this.invalidateFieldControl({ immediate: true, blockInteraction: true });
56
62
  } else {
57
63
  runtime.revision++;
58
64
  this.applyFieldControlStates(runtime.dynamicStates);
@@ -64,8 +70,8 @@ export default {
64
70
  this.disposeFieldControl();
65
71
  },
66
72
  methods: {
67
- isFieldControlV2() {
68
- return !!this.formConfig?.dynamicFieldEnabled && Number(this.formConfig.dynamicFieldRuleVersion) === 2;
73
+ isFieldControlEnabled() {
74
+ return !!this.formConfig?.dynamicFieldEnabled;
69
75
  },
70
76
  getFieldControlRuntime() {
71
77
  if (!this._fieldControl) this._fieldControl = createRuntime();
@@ -74,7 +80,7 @@ export default {
74
80
  prepareFieldControlTargets() {
75
81
  const runtime = this.getFieldControlRuntime();
76
82
  const entries = collectFieldControlTargets(this.widgetList);
77
- const widgets = this.getFieldControlWidgets ? this.getFieldControlWidgets() : entries.map(e => e.widget);
83
+ const widgets = this.getFieldControlWidgets();
78
84
  runtime.entries = entries;
79
85
  runtime.byWidget = new WeakMap();
80
86
  entries.forEach(entry => runtime.byWidget.set(entry.widget, entry));
@@ -86,9 +92,9 @@ export default {
86
92
  runtime.definitions.set(widget, state);
87
93
  });
88
94
  },
89
- // 动态列/结构晚于首次采集时,先登记未改写的定义,再走动态→流程,避免 V1 旁路污染基线。
95
+ // 动态列/结构晚于首次采集时,先登记未改写的定义,再走动态→流程。
90
96
  syncFieldControlAfterStructure() {
91
- if (!this.isFieldControlV2()) return false;
97
+ if (!this.isFieldControlEnabled()) return false;
92
98
  this.prepareFieldControlTargets();
93
99
  const runtime = this.getFieldControlRuntime();
94
100
  if (this.fieldControlInitialized) {
@@ -107,7 +113,7 @@ export default {
107
113
  const result = Object.create(null);
108
114
  this.getFieldControlRuntime().entries.forEach(entry => {
109
115
  const projected = this.createFieldControlProjection(entry.widget);
110
- if (this.handleWidgetShowRule) this.handleWidgetShowRule(projected);
116
+ this.handleWidgetShowRule(projected);
111
117
  result[entry.key] = readFieldState(projected);
112
118
  });
113
119
  return result;
@@ -192,7 +198,7 @@ export default {
192
198
  if (runtime.schemaErrors.size) throw Array.from(runtime.schemaErrors.values())[0].error;
193
199
  },
194
200
  async initializeFieldControl() {
195
- if (!this.isFieldControlV2()) return;
201
+ if (!this.isFieldControlEnabled()) return;
196
202
  const runtime = this.getFieldControlRuntime();
197
203
  if (runtime.destroyed) throw new Error("表单已关闭");
198
204
  this.fieldControlStatus = "preparing";
@@ -247,18 +253,25 @@ export default {
247
253
  if (!sameValue(runtime.lastDependency, this.fieldControlDependency())) this.invalidateFieldControl();
248
254
  }, { deep: true, sync: true });
249
255
  },
250
- invalidateFieldControl() {
256
+ invalidateFieldControl(options = {}) {
251
257
  const runtime = this.getFieldControlRuntime();
258
+ const immediate = options.immediate === true;
259
+ if (options.blockInteraction) {
260
+ this.fieldControlStatus = "preparing";
261
+ this.fieldControlError = "";
262
+ }
252
263
  runtime.revision++;
253
264
  runtime.dirty = true;
254
265
  if (runtime.timer) clearTimeout(runtime.timer);
255
- runtime.timer = setTimeout(() => {
266
+ const refresh = () => {
256
267
  runtime.timer = null;
257
268
  this.refreshFieldControl().catch(error => this.failFieldControl(error, runtime));
258
- }, 120);
269
+ };
270
+ if (immediate) refresh();
271
+ else runtime.timer = setTimeout(refresh, 120);
259
272
  },
260
273
  async refreshFieldControl() {
261
- if (!this.isFieldControlV2()) return;
274
+ if (!this.isFieldControlEnabled()) return;
262
275
  const runtime = this.getFieldControlRuntime();
263
276
  if (runtime.timer) clearTimeout(runtime.timer);
264
277
  runtime.timer = null;
@@ -389,14 +402,14 @@ export default {
389
402
  },
390
403
  applyFieldControlStates(states) {
391
404
  const runtime = this.getFieldControlRuntime();
392
- this.widgetEditOnWf = this.hanldeWfWidgetNew0 ? this.hanldeWfWidgetNew0() : false;
405
+ this.widgetEditOnWf = this.hanldeWfWidgetNew0();
393
406
  let editable = false;
394
407
  runtime.flowWidgets.forEach(widget => {
395
408
  const entry = runtime.byWidget.get(widget);
396
409
  const projected = this.createFieldControlProjection(widget);
397
- if (this.handleWidgetShowRule) this.handleWidgetShowRule(projected);
410
+ this.handleWidgetShowRule(projected);
398
411
  if (entry && states?.[entry.key]) writeStateOptions(projected.options, states[entry.key]);
399
- if (this.applyWorkflowFieldControl) this.applyWorkflowFieldControl(projected);
412
+ this.applyWorkflowFieldControl(projected);
400
413
  if (widget.formItemFlag && this.disabledMode) projected.options.disabled = true;
401
414
  const effective = readFieldState(projected);
402
415
  // 非输入按钮不在动态目标范围,但仍应用流程自身的 disabled。
@@ -407,7 +420,7 @@ export default {
407
420
  applyFieldState(target, effective, name => this.getWidgetRef(name));
408
421
  if (widget.formItemFlag && !projected.options.hidden && !projected.options.disabled && !projected.options.readonly) editable = true;
409
422
  });
410
- this.wfModifyEnabled = !!this.wfParam?.wfInfo?.toModify && editable && !this.getReadMode?.();
423
+ this.wfModifyEnabled = !!this.wfParam?.wfInfo?.toModify && editable && !this.getReadMode();
411
424
  this.syncFieldControlValidation();
412
425
  },
413
426
  syncFieldControlValidation() {
@@ -429,11 +442,14 @@ export default {
429
442
  if (runtime.destroyed || runtime !== this.getFieldControlRuntime()) return;
430
443
  this.fieldControlStatus = "error";
431
444
  this.fieldControlError = error?.message || "字段规则执行失败,请重试";
432
- // 无成功结果时至少应用已知流程限制。错误状态独立阻挡编辑/提交。
433
- if (!runtime.dynamicStates && runtime.flowWidgets.length) this.applyFieldControlStates(null);
445
+ // 即使动态重算失败,也要用最后一份可信动态状态重新收口当前流程限制。
446
+ // 交互由 fieldControlInteractionBlocked 独立阻断,不污染定义基线。
447
+ if (runtime.flowWidgets.length) {
448
+ try { this.applyFieldControlStates(runtime.dynamicStates); } catch (applyError) { /* 保留原始失败 */ }
449
+ }
434
450
  },
435
451
  async ensureFieldControlReady() {
436
- if (!this.isFieldControlV2()) return;
452
+ if (!this.isFieldControlEnabled()) return;
437
453
  const runtime = this.getFieldControlRuntime();
438
454
  if (runtime.mountTask) await runtime.mountTask;
439
455
  if (runtime.destroyed) throw new Error("表单已关闭");
@@ -457,7 +473,7 @@ export default {
457
473
  }
458
474
  },
459
475
  guardFieldControlWorkflow(option) {
460
- if (!this.isFieldControlV2()) return option;
476
+ if (!this.isFieldControlEnabled()) return option;
461
477
  const snapshots = {};
462
478
  const wrapped = { ...option };
463
479
  [["onStart", "onBeforeStartSubmit", "start"], ["onClickAgree", "onBeforeAgree", "agree"]]
@@ -485,7 +501,7 @@ export default {
485
501
  return wrapped;
486
502
  },
487
503
  captureFieldControlSubmission() {
488
- if (!this.isFieldControlV2()) return null;
504
+ if (!this.isFieldControlEnabled()) return null;
489
505
  return {
490
506
  data: cloneDeep(this.formModel), context: this.fieldControlContextKey,
491
507
  revision: this.getFieldControlRuntime().revision,
@@ -513,12 +529,6 @@ export default {
513
529
  this.fieldControlInitialized = false;
514
530
  this.fieldControlStatus = "idle";
515
531
  this.fieldControlError = "";
516
- // 同时清理 V1,支持 setFormJson 在两个版本之间切换。
517
- if (this._fdfWatcher) this._fdfWatcher();
518
- if (this._fdfTimer) clearTimeout(this._fdfTimer);
519
- this._fdfWatcher = null;
520
- this._fdfTimer = null;
521
- this._fdfInited = false;
522
532
  },
523
533
  },
524
534
  };
@@ -10,7 +10,7 @@
10
10
 
11
11
  <template>
12
12
  <div>
13
- <el-alert v-if="isFieldControlV2() && fieldControlError" :title="fieldControlError" type="error" :closable="false" show-icon>
13
+ <el-alert v-if="fieldControlError" :title="fieldControlError" type="error" :closable="false" show-icon>
14
14
  <el-button type="text" @click="retryFieldControl">重新计算字段规则</el-button>
15
15
  </el-alert>
16
16
  <div v-if="fieldControlBlocked" role="status">正在准备表单,请稍候…</div>
@@ -19,8 +19,12 @@
19
19
  :size="size"
20
20
  :class="[customClass, h5RootClass]"
21
21
  class="render-form"
22
- :style="fieldControlBlocked ? { visibility: 'hidden' } : null"
22
+ :style="fieldControlBlocked
23
+ ? { visibility: 'hidden' }
24
+ : (fieldControlInteractionBlocked ? { pointerEvents: 'none' } : null)"
25
+ :inert="fieldControlInteractionBlocked ? '' : null"
23
26
  :aria-busy="fieldControlBlocked ? 'true' : 'false'"
27
+ :aria-disabled="fieldControlInteractionBlocked ? 'true' : 'false'"
24
28
  :label-width="labelWidth"
25
29
  :validate-on-rule-change="false"
26
30
  :model="formDataModel"
@@ -119,7 +123,6 @@
119
123
  import "./container-item/index";
120
124
  import FieldComponents from "../../../components/xform/form-designer/form-widget/field-widget/index";
121
125
  import indexMixin from "../../../components/xform/form-render/indexMixin";
122
- import formDynamicFieldMixin from "../../../components/xform/form-render/formDynamicFieldMixin";
123
126
 
124
127
  import fieldControlMixin from "@base/components/xform/form-render/fieldControlMixin";
125
128
 
@@ -144,7 +147,7 @@ export default {
144
147
  compareDelList: () =>
145
148
  import("../../../components/xform/form-render/compareDelList.vue"),
146
149
  },
147
- mixins: [indexMixin, formDynamicFieldMixin, fieldControlMixin],
150
+ mixins: [indexMixin, fieldControlMixin],
148
151
  };
149
152
  </script>
150
153
 
@@ -386,7 +386,7 @@ modules = {
386
386
  let formConfig = this.formConfig;
387
387
  if (formConfig.isLoadEntity) {
388
388
  this.loadDefaultEntityData(() => {
389
- if (!(this.isFieldControlV2 && this.isFieldControlV2())) {
389
+ if (!this.isFieldControlEnabled()) {
390
390
  this.handleCustomEvent(formConfig.formScriptSuccess);
391
391
  }
392
392
  });
@@ -574,7 +574,7 @@ modules = {
574
574
  : null;
575
575
  },
576
576
  handleShowContent(callback) {
577
- if (this.isFieldControlV2 && this.isFieldControlV2()) return this.showControlledForm(callback);
577
+ if (this.isFieldControlEnabled()) return this.showControlledForm(callback);
578
578
  // this.buildFormModel(!this.formJsonObj ? null : this.formJsonObj.widgetList);
579
579
  this.handleBeforeMounted().then(() => {
580
580
  this.showFormContent = true;
@@ -742,8 +742,14 @@ modules = {
742
742
  })*/
743
743
  },
744
744
  hanldeCommonWidget2() {
745
- if (this.isFieldControlV2 && this.isFieldControlV2()) {
746
- this.prepareFieldControlTargets();
745
+ if (this.isFieldControlEnabled()) {
746
+ // 渲染前仍先补齐列 widget,但配置异常不能跳出受控初始化:
747
+ // showControlledForm 会再次采集并把错误落到 error/重试界面。
748
+ try {
749
+ this.prepareFieldControlTargets();
750
+ } catch (error) {
751
+ this.failFieldControl(error);
752
+ }
747
753
  return;
748
754
  }
749
755
  let wfParam = this.wfParam || {};
@@ -1561,7 +1567,7 @@ modules = {
1561
1567
  if (wfConfig1) {
1562
1568
  resOption = extendDeeply(resOption, wfConfig1);
1563
1569
  }
1564
- return this.guardFieldControlWorkflow ? this.guardFieldControlWorkflow(resOption) : resOption;
1570
+ return this.guardFieldControlWorkflow(resOption);
1565
1571
  };
1566
1572
  }
1567
1573
  return this.loadFormData({
@@ -2093,7 +2099,7 @@ modules = {
2093
2099
  !i.formConfig || !i.widgetList)
2094
2100
  )
2095
2101
  return void this.$message.error("Invalid format of form json.");
2096
- if (this.resetFieldControl) this.resetFieldControl();
2102
+ this.resetFieldControl();
2097
2103
  this.clearFormDataModel(),
2098
2104
  this.buildFormModel(i.widgetList),
2099
2105
  this.$set(this.formJsonObj, "formConfig", i.formConfig),
@@ -2103,11 +2109,8 @@ modules = {
2103
2109
  this.insertCustomStyleAndScriptNode(),
2104
2110
  this.$nextTick(function () {
2105
2111
  t.initFormObject(!1);
2106
- if (t.isFieldControlV2 && t.isFieldControlV2()) t.showControlledForm();
2107
- else {
2108
- t.handleOnMounted();
2109
- if (t.initFormDynamicFields) t.initFormDynamicFields();
2110
- }
2112
+ if (t.isFieldControlEnabled()) t.showControlledForm();
2113
+ else t.handleOnMounted();
2111
2114
  });
2112
2115
  } else this.$message.error("Set form json failed.");
2113
2116
  },
@@ -2120,7 +2123,7 @@ modules = {
2120
2123
  },
2121
2124
  getFormData: function () {
2122
2125
  const validateData = arguments.length === 0 || arguments[0] === undefined || arguments[0];
2123
- if (validateData && this.isFieldControlV2 && this.isFieldControlV2()) {
2126
+ if (validateData && this.isFieldControlEnabled()) {
2124
2127
  return this.ensureFieldControlReady().then(() => new Promise((resolve, reject) => {
2125
2128
  const snapshot = this.captureFieldControlSubmission();
2126
2129
  this.validateCurrentForm(async valid => {
@@ -2275,7 +2278,7 @@ modules = {
2275
2278
  this.$refs.renderForm.clearValidate(e);
2276
2279
  },
2277
2280
  validateForm: function (e) {
2278
- if (this.isFieldControlV2 && this.isFieldControlV2()) return this.validate(e);
2281
+ if (this.isFieldControlEnabled()) return this.validate(e);
2279
2282
 
2280
2283
  this.$refs["renderForm"].$baseValidate((valid) => {
2281
2284
  if (!valid) {
@@ -4662,6 +4665,8 @@ modules = {
4662
4665
  saveForm(callback) {
4663
4666
  this.validateForm((valid) => {
4664
4667
  if (!valid) return;
4668
+ // 从校验通过起锁定数据/规则版本;getSubData 和确认框都是异步窗口。
4669
+ const fieldControlSnapshot = this.captureFieldControlSubmission();
4665
4670
  this.getSubData().then((formData) => {
4666
4671
  let reportTemplate = this.reportTemplate || {};
4667
4672
  let formConfig = this.formConfig;
@@ -4676,7 +4681,13 @@ modules = {
4676
4681
  data: realFormData,
4677
4682
  };
4678
4683
 
4679
- this.$baseConfirm("您确定要保存吗?").then(() => {
4684
+ this.$baseConfirm("您确定要保存吗?").then(async () => {
4685
+ try {
4686
+ await this.assertFieldControlSubmission(fieldControlSnapshot);
4687
+ } catch (error) {
4688
+ this.$message.error(error.message);
4689
+ return;
4690
+ }
4680
4691
  let saveConfig = formConfig.saveConfig || {};
4681
4692
  this.formHttp({
4682
4693
  options: saveConfig,
@@ -4932,7 +4943,7 @@ modules = {
4932
4943
  }
4933
4944
  },
4934
4945
  validate(callback) {
4935
- if (this.isFieldControlV2 && this.isFieldControlV2()) {
4946
+ if (this.isFieldControlEnabled()) {
4936
4947
  return this.ensureFieldControlReady().then(() => new Promise(resolve => {
4937
4948
  const snapshot = this.captureFieldControlSubmission();
4938
4949
  this.validateCurrentForm(async valid => {
@@ -1135,7 +1135,6 @@ export function getDefaultFormConfig() {
1135
1135
  globalConfig: null,
1136
1136
  // 表单级动态字段规则:按本地规则/后台脚本动态控制整个表单内任意字段/容器的显隐/必填/只读/禁用/取值
1137
1137
  dynamicFieldEnabled: false,
1138
- dynamicFieldRuleVersion: 2,
1139
1138
  dynamicFieldRunMode: "auto",
1140
1139
  dynamicFieldAllowValue: false,
1141
1140
  dynamicFieldSourceType: "local",
@@ -737,15 +737,18 @@ body #app .index-home {
737
737
  .time {
738
738
  color: #b9b9b9;
739
739
  font-size: 12px;
740
- right: 12px;
740
+ right: 12px;
741
+ background: #FFF;
742
+ padding-left: 8px;
741
743
  }
742
744
 
743
745
  &:hover {
744
746
  color: $baseColor;
745
- background-color: transparentize($baseColor, 0.97);
747
+ background-color: #F8FAFC;
746
748
 
747
749
  .time {
748
- color: $baseColor;
750
+ color: $baseColor;
751
+ background-color: #F8FAFC;
749
752
  }
750
753
  }
751
754
 
@@ -483,7 +483,8 @@ export default {
483
483
  let userInfo = res.objx;
484
484
  this.headPhotoUrl = userInfo.headPhotoUrl
485
485
  ? userInfo.headPhotoUrl
486
- : (require("@/resources/images/default-header.png").default || require("@/resources/images/default-header.png"));
486
+ : require("@/resources/images/default-header.png").default ||
487
+ require("@/resources/images/default-header.png");
487
488
 
488
489
  this.userInfo = userInfo;
489
490
  this.initServiceProviderRole();
@@ -709,7 +710,7 @@ export default {
709
710
  this.showDealDialog = true;
710
711
  },
711
712
  openOrderView(row) {
712
- let dataId = row.id;
713
+ let dataId = row.order_id;
713
714
  if (dataId == null) {
714
715
  this.$message.warning(this.$t1("未找到工单"));
715
716
  return;
@@ -1,285 +0,0 @@
1
- /**
2
- * 动态字段规则引擎(纯逻辑,无 Vue 上下文)。
3
- *
4
- * 供「表单级动态规则(form-setting)」与「容器级动态字段(container-item)」共用:
5
- * - 目标收集:字段 + 命名容器,建立 目标key -> { widget, refKey, isContainer } 映射与设计基线
6
- * - 规则计算:本地条件/逻辑求值,输出每个目标的最终状态
7
- * - 状态落地:通过 getWidgetRef 应用到运行时组件,无 ref 时回退改 schema
8
- */
9
- import {
10
- traverseFieldWidgets,
11
- traverseContainerWidgets,
12
- traverseFieldWidgetsOfContainer,
13
- } from "../../../../components/xform/utils/util";
14
-
15
- // 布局内格容器,不作为可控目标
16
- const EXCLUDED_CONTAINER_TYPES = ["grid-col", "table-cell", "h5-table-cell"];
17
-
18
- export function getModelKey(w) {
19
- return (w.options.keyNameEnabled && w.options.keyName) || w.options.name;
20
- }
21
-
22
- function addFieldTo(map, baseline, w) {
23
- if (!w || !w.options) return;
24
- const key = getModelKey(w);
25
- if (!key) return;
26
- map[key] = { widget: w, refKey: w.options.name, isContainer: false };
27
- baseline[key] = {
28
- visible: !w.options.hidden,
29
- required: !!w.options.required,
30
- readonly: !!w.options.readonly,
31
- disabled: !!w.options.disabled,
32
- };
33
- }
34
-
35
- function addContainerTo(map, baseline, w) {
36
- if (!w || !w.options || !w.options.name) return;
37
- if (EXCLUDED_CONTAINER_TYPES.indexOf(w.type) > -1) return;
38
- const key = w.options.name;
39
- if (map[key]) return;
40
- map[key] = { widget: w, refKey: key, isContainer: true };
41
- baseline[key] = { visible: !w.options.hidden };
42
- }
43
-
44
- // 整个表单范围:字段 + 命名容器(跳过弹窗/抽屉内部)
45
- export function buildFormTargetMap(widgetList) {
46
- const map = {};
47
- const baseline = {};
48
- traverseFieldWidgets(widgetList || [], (w) => addFieldTo(map, baseline, w));
49
- traverseContainerWidgets(
50
- widgetList || [],
51
- (w) => addContainerTo(map, baseline, w),
52
- true
53
- );
54
- return { map, baseline };
55
- }
56
-
57
- // 单个容器范围:容器内字段 + 命名子容器
58
- export function buildContainerTargetMap(container) {
59
- const map = {};
60
- const baseline = {};
61
- traverseFieldWidgetsOfContainer(container, (w) => addFieldTo(map, baseline, w));
62
- collectNamedContainersInContainer(container).forEach((w) =>
63
- addContainerTo(map, baseline, w)
64
- );
65
- return { map, baseline };
66
- }
67
-
68
- // 递归收集指定容器内「命名的子容器」
69
- export function collectNamedContainersInContainer(root) {
70
- const out = [];
71
- const walk = (w, isRoot) => {
72
- if (!w || w.category !== "container") return;
73
- if (
74
- !isRoot
75
- && w.options
76
- && w.options.name
77
- && EXCLUDED_CONTAINER_TYPES.indexOf(w.type) === -1
78
- ) {
79
- out.push(w);
80
- }
81
- if (Array.isArray(w.rows)) {
82
- w.rows.forEach((row) => {
83
- (row.cols || []).forEach((cell) => walk(cell, false));
84
- });
85
- }
86
- ["cols", "tabs", "widgetList", "panes", "buttonWidgetList"].forEach((k) => {
87
- if (Array.isArray(w[k])) w[k].forEach((c) => walk(c, false));
88
- });
89
- };
90
- walk(root, true);
91
- return out;
92
- }
93
-
94
- /* ------------------- 层级下拉选项(供设计器) ------------------- */
95
- // 构建带层级的目标候选(字段 + 容器),按表单树 DFS 顺序输出,附带 depth。
96
- // 布局内格(grid-col/table-cell 等)不单独成节点,仅作为透明层向下展开。
97
- function makeTreeBuilder() {
98
- const nodes = [];
99
- const visitList = (list, depth) => {
100
- (list || []).forEach((w) => visit(w, depth));
101
- };
102
- const visitChildren = (w, depth) => {
103
- if (Array.isArray(w.rows)) {
104
- w.rows.forEach((row) => visitList((row && row.cols) || [], depth));
105
- }
106
- ["cols", "tabs", "widgetList", "panes", "buttonWidgetList"].forEach((k) => {
107
- if (Array.isArray(w[k])) visitList(w[k], depth);
108
- });
109
- };
110
- const visit = (w, depth) => {
111
- if (!w) return;
112
- if (w.category === "container") {
113
- // 布局内格:透明展开,深度不变
114
- if (EXCLUDED_CONTAINER_TYPES.indexOf(w.type) > -1) {
115
- visitChildren(w, depth);
116
- return;
117
- }
118
- const name = w.options && w.options.name;
119
- nodes.push({
120
- value: name || "__c_" + w.id,
121
- baseLabel: (w.options && (w.options.label || name)) || w.type,
122
- depth,
123
- isContainer: true,
124
- hasName: !!name,
125
- type: w.type,
126
- });
127
- visitChildren(w, depth + 1);
128
- } else if (w.formItemFlag) {
129
- const key = getModelKey(w);
130
- if (!key) return;
131
- nodes.push({
132
- value: key,
133
- baseLabel: (w.options.label || key) + " (" + key + ")",
134
- depth,
135
- isContainer: false,
136
- hasName: true,
137
- type: w.type,
138
- });
139
- }
140
- };
141
- return { nodes, visitList, visitChildren };
142
- }
143
-
144
- // 整个表单范围的层级候选
145
- export function buildWidgetTreeOptions(rootList) {
146
- const b = makeTreeBuilder();
147
- b.visitList(rootList || [], 0);
148
- return b.nodes;
149
- }
150
-
151
- // 单个容器范围的层级候选(不含容器自身,从其子级开始)
152
- export function buildContainerTreeOptions(container) {
153
- const b = makeTreeBuilder();
154
- if (container) b.visitChildren(container, 0);
155
- return b.nodes;
156
- }
157
-
158
- /* ------------------- 规则计算 ------------------- */
159
- export function computeLocalStates(rules, baseline, formModel) {
160
- const list = rules || [];
161
- const states = {};
162
- const ensure = (key) => {
163
- if (!states[key]) {
164
- const base = baseline[key] || { visible: true };
165
- states[key] = Object.assign({}, base);
166
- }
167
- return states[key];
168
- };
169
- // 先把所有规则涉及的目标置为基线,未命中时即还原
170
- list.forEach((rule) => {
171
- (rule.targets || []).forEach((t) => t.field && ensure(t.field));
172
- });
173
- list.forEach((rule) => {
174
- if (!evalRule(rule, formModel)) return;
175
- (rule.targets || []).forEach((t) => {
176
- if (!t.field) return;
177
- const s = ensure(t.field);
178
- if (t.visible !== undefined && t.visible !== null) s.visible = !!t.visible;
179
- if (t.required !== undefined && t.required !== null) {
180
- s.required = !!t.required;
181
- }
182
- if (t.readonly !== undefined && t.readonly !== null) {
183
- s.readonly = !!t.readonly;
184
- }
185
- if (t.disabled !== undefined && t.disabled !== null) {
186
- s.disabled = !!t.disabled;
187
- }
188
- });
189
- });
190
- return states;
191
- }
192
-
193
- export function evalRule(rule, model) {
194
- const conds = (rule && rule.conditions) || [];
195
- if (!conds.length) return true;
196
- if (rule.logic === "or") {
197
- return conds.some((c) => evalCondition(c, model));
198
- }
199
- return conds.every((c) => evalCondition(c, model));
200
- }
201
-
202
- export function evalCondition(cond, model) {
203
- if (!cond || !cond.field) return false;
204
- const actual = model ? model[cond.field] : undefined;
205
- const expected = cond.value;
206
- const isEmpty = (v) =>
207
- v === undefined
208
- || v === null
209
- || v === ""
210
- || (Array.isArray(v) && v.length === 0);
211
- const toList = (v) =>
212
- String(v == null ? "" : v)
213
- .split(",")
214
- .map((s) => s.trim())
215
- .filter((s) => s !== "");
216
- switch (cond.operator) {
217
- case "eq":
218
- return String(actual) === String(expected);
219
- case "ne":
220
- return String(actual) !== String(expected);
221
- case "gt":
222
- return Number(actual) > Number(expected);
223
- case "lt":
224
- return Number(actual) < Number(expected);
225
- case "ge":
226
- return Number(actual) >= Number(expected);
227
- case "le":
228
- return Number(actual) <= Number(expected);
229
- case "in":
230
- return toList(expected).includes(String(actual));
231
- case "nin":
232
- return !toList(expected).includes(String(actual));
233
- case "includes":
234
- return Array.isArray(actual)
235
- ? actual.map(String).includes(String(expected))
236
- : String(actual == null ? "" : actual).indexOf(String(expected)) > -1;
237
- case "empty":
238
- return isEmpty(actual);
239
- case "notEmpty":
240
- return !isEmpty(actual);
241
- default:
242
- return false;
243
- }
244
- }
245
-
246
- /* ------------------- 状态落地 ------------------- */
247
- // entry: { widget, refKey, isContainer };getWidgetRef: (name) => 运行时组件ref
248
- export function applyTargetState(entry, state, getWidgetRef) {
249
- if (!entry || !state) return;
250
- const widget = entry.widget;
251
- const isContainer = entry.isContainer;
252
- const ref = getWidgetRef ? getWidgetRef(entry.refKey) : null;
253
- const hasVisible = state.visible !== undefined && state.visible !== null;
254
- if (ref) {
255
- if (hasVisible && ref.setHidden) ref.setHidden(!state.visible);
256
- if (isContainer) return;
257
- if (state.required !== undefined && state.required !== null && ref.setRequired) {
258
- ref.setRequired(!!state.required);
259
- }
260
- if (state.readonly !== undefined && state.readonly !== null && ref.setReadonly) {
261
- ref.setReadonly(!!state.readonly);
262
- }
263
- if (state.disabled !== undefined && state.disabled !== null && ref.setDisabled) {
264
- ref.setDisabled(!!state.disabled);
265
- }
266
- if (state.options !== undefined && state.options !== null && ref.setOptions) {
267
- ref.setOptions(state.options);
268
- }
269
- if (state.value !== undefined && ref.setValue) {
270
- ref.setValue(state.value);
271
- }
272
- } else {
273
- if (hasVisible) widget.options.hidden = !state.visible;
274
- if (isContainer) return;
275
- if (state.required !== undefined && state.required !== null) {
276
- widget.options.required = !!state.required;
277
- }
278
- if (state.readonly !== undefined && state.readonly !== null) {
279
- widget.options.readonly = !!state.readonly;
280
- }
281
- if (state.disabled !== undefined && state.disabled !== null) {
282
- widget.options.disabled = !!state.disabled;
283
- }
284
- }
285
- }