cloud-web-corejs 1.1.0-dev.18 → 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.
- package/package.json +1 -1
- package/src/components/baseInputExport/mixins.js +0 -6
- package/src/components/errorMsg/index.vue +2 -2
- 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
- package/src/components/xform/form-designer/designer.js +2101 -2084
- package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +34 -0
- package/src/components/xform/form-designer/form-widget/field-widget/fieldMixin.js +13 -1
- package/src/components/xform/form-designer/setting-panel/form-dynamicField-setting.vue +660 -602
- package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +8 -0
- package/src/components/xform/form-render/container-item/data-table-mixin.js +6306 -6285
- package/src/components/xform/form-render/container-item/dynamicFieldMixin.js +54 -0
- package/src/components/xform/form-render/container-item/tab-item.vue +138 -129
- package/src/components/xform/form-render/fieldControlEngine.js +200 -0
- package/src/components/xform/form-render/fieldControlMixin.js +524 -0
- package/src/components/xform/form-render/formDynamicFieldMixin.js +2 -0
- package/src/components/xform/form-render/index.vue +161 -153
- package/src/components/xform/form-render/indexMixin.js +5404 -5331
- package/src/components/xform/mixins/defaultHandle.js +713 -707
- package/src/components/xform/styles/h5.scss +3 -0
- package/src/components/xform/utils/util.js +1711 -1708
|
@@ -108,6 +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
112
|
return Promise.resolve(false);
|
|
112
113
|
}
|
|
113
114
|
},
|
|
@@ -116,6 +117,12 @@ const dynamicFieldMixin = {
|
|
|
116
117
|
if (this.designState || !this.widget) return;
|
|
117
118
|
if (!this.supportsRowLayout()) return;
|
|
118
119
|
if (this.getDynamicSchemaSourceType() === "none") return;
|
|
120
|
+
const formRef = this.getFormRef();
|
|
121
|
+
if (formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2()) {
|
|
122
|
+
const task = this.$nextTick().then(() => this.resolveShouldInitDynamicSchema())
|
|
123
|
+
.then(shouldInit => shouldInit ? this.loadControlledDynamicSchema() : undefined);
|
|
124
|
+
return formRef.trackFieldControlSchema(task, () => this.loadDynamicSchema(), this);
|
|
125
|
+
}
|
|
119
126
|
this.$nextTick(() => {
|
|
120
127
|
this.resolveShouldInitDynamicSchema().then((shouldInit) => {
|
|
121
128
|
if (shouldInit) {
|
|
@@ -125,7 +132,54 @@ const dynamicFieldMixin = {
|
|
|
125
132
|
});
|
|
126
133
|
},
|
|
127
134
|
|
|
135
|
+
loadControlledDynamicSchema() {
|
|
136
|
+
const formRef = this.getFormRef();
|
|
137
|
+
const runtime = formRef.getFieldControlRuntime();
|
|
138
|
+
const sequence = (this._dynamicSchemaRun || 0) + 1;
|
|
139
|
+
this._dynamicSchemaRun = sequence;
|
|
140
|
+
const current = () => this._dynamicSchemaRun === sequence &&
|
|
141
|
+
formRef.getFieldControlRuntime() === runtime && !runtime.destroyed && !this._isDestroyed;
|
|
142
|
+
const build = result => {
|
|
143
|
+
if (current()) this.buildDynamicRows(result);
|
|
144
|
+
return this.$nextTick();
|
|
145
|
+
};
|
|
146
|
+
const options = this.widget.options;
|
|
147
|
+
if (this.getDynamicSchemaSourceType() === "script") {
|
|
148
|
+
const code = getDynamicSchemaOption(options, "frontendScript");
|
|
149
|
+
if (!code) return Promise.reject(new Error("请配置动态字段脚本"));
|
|
150
|
+
return Promise.resolve().then(() => this.handleCustomEvent(code))
|
|
151
|
+
.then(result => build(this.normalizeSchemaScriptResult(result)));
|
|
152
|
+
}
|
|
153
|
+
const scriptCode = getDynamicSchemaOption(options, "scriptCode");
|
|
154
|
+
if (!scriptCode) return Promise.reject(new Error("请配置动态字段接口编码"));
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
try {
|
|
157
|
+
const task = this.formHttp({
|
|
158
|
+
scriptCode,
|
|
159
|
+
isLoading: false,
|
|
160
|
+
data: buildDynamicSchemaRequestData(this.formModel,
|
|
161
|
+
getDynamicSchemaOption(options, "scriptParam"), this.handleCustomEvent.bind(this)),
|
|
162
|
+
success: response => {
|
|
163
|
+
if (!current()) { resolve(); return; }
|
|
164
|
+
try { Promise.resolve(build(this.resolveSchemaDefs(response))).then(resolve, reject); }
|
|
165
|
+
catch (error) { reject(error); }
|
|
166
|
+
},
|
|
167
|
+
fail: () => current() ? reject(new Error("动态字段加载失败,请重试")) : resolve(),
|
|
168
|
+
error: error => current() ? reject(error) : resolve(),
|
|
169
|
+
});
|
|
170
|
+
if (task && task.catch) task.catch(reject);
|
|
171
|
+
} catch (error) { reject(error); }
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
128
175
|
loadDynamicSchema() {
|
|
176
|
+
const formRef = this.getFormRef && this.getFormRef();
|
|
177
|
+
if (!this.designState && this.supportsRowLayout() &&
|
|
178
|
+
this.getDynamicSchemaSourceType() !== "none" &&
|
|
179
|
+
formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2()) {
|
|
180
|
+
return formRef.trackFieldControlSchema(this.loadControlledDynamicSchema(),
|
|
181
|
+
() => this.loadDynamicSchema(), this);
|
|
182
|
+
}
|
|
129
183
|
if (this.designState || !this.supportsRowLayout()) {
|
|
130
184
|
return Promise.resolve();
|
|
131
185
|
}
|
|
@@ -1,129 +1,138 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<container-item-wrapper
|
|
3
|
-
:widget="widget"
|
|
4
|
-
:class="widget.options.isFullscreen ? 'full-height' : ''"
|
|
5
|
-
>
|
|
6
|
-
<div :key="widget.id" class="tab-container mHeight" v-show="!widget.options.hidden">
|
|
7
|
-
<!-- 高度默认 auto(跟随内容):没在属性面板配 height 就不设。
|
|
8
|
-
原来兜底成固定 300px,内容多了内部滚动、少了留白,装详情内容模块时尤其别扭 -->
|
|
9
|
-
<el-tabs
|
|
10
|
-
v-model="activeTabName"
|
|
11
|
-
:type="widget.displayType"
|
|
12
|
-
:ref="widget.id"
|
|
13
|
-
:class="[customClass, widget.options.tabClass]"
|
|
14
|
-
:style="{
|
|
15
|
-
height: widget.options.isFullscreen ? '' : widget.options.height,
|
|
16
|
-
}"
|
|
17
|
-
>
|
|
18
|
-
<el-tab-pane
|
|
19
|
-
v-for="tab in visibleTabs"
|
|
20
|
-
:key="tab.options.name"
|
|
21
|
-
:label="getI18nLabel(tab.options.label)"
|
|
22
|
-
:disabled="tab.options.disabled"
|
|
23
|
-
:name="tab.options.name"
|
|
24
|
-
>
|
|
25
|
-
<template v-for="(subWidget, swIdx) in tab.widgetList">
|
|
26
|
-
<template v-if="'container' === subWidget.category">
|
|
27
|
-
<component
|
|
28
|
-
:is="subWidget.type + '-item'"
|
|
29
|
-
:widget="subWidget"
|
|
30
|
-
:key="subWidget.id || swIdx"
|
|
31
|
-
:parent-list="tab.widgetList"
|
|
32
|
-
:index-of-parent-list="swIdx"
|
|
33
|
-
:parent-widget="widget"
|
|
34
|
-
:tableParam="tableParam"
|
|
35
|
-
:formItemProp="formItemProp"
|
|
36
|
-
>
|
|
37
|
-
<!-- 递归传递插槽!!! -->
|
|
38
|
-
<template v-for="slot in Object.keys($scopedSlots)" v-slot:[slot]="scope">
|
|
39
|
-
<slot :name="slot" v-bind="scope" />
|
|
40
|
-
</template>
|
|
41
|
-
</component>
|
|
42
|
-
</template>
|
|
43
|
-
<template v-else>
|
|
44
|
-
<component
|
|
45
|
-
:is="subWidget.type + '-widget'"
|
|
46
|
-
:field="subWidget"
|
|
47
|
-
:key="subWidget.id || swIdx"
|
|
48
|
-
:parent-list="tab.widgetList"
|
|
49
|
-
:index-of-parent-list="swIdx"
|
|
50
|
-
:parent-widget="widget"
|
|
51
|
-
:tableParam="tableParam"
|
|
52
|
-
:formItemProp="formItemProp"
|
|
53
|
-
>
|
|
54
|
-
<!-- 递归传递插槽!!! -->
|
|
55
|
-
<template v-for="slot in Object.keys($scopedSlots)" v-slot:[slot]="scope">
|
|
56
|
-
<slot :name="slot" v-bind="scope" />
|
|
57
|
-
</template>
|
|
58
|
-
</component>
|
|
59
|
-
</template>
|
|
60
|
-
</template>
|
|
61
|
-
</el-tab-pane>
|
|
62
|
-
</el-tabs>
|
|
63
|
-
</div>
|
|
64
|
-
</container-item-wrapper>
|
|
65
|
-
</template>
|
|
66
|
-
|
|
67
|
-
<script>
|
|
68
|
-
import emitter from "../../../../components/xform/utils/emitter";
|
|
69
|
-
import i18n from "../../../../components/xform/utils/i18n";
|
|
70
|
-
import refMixin from "../../../../components/xform/form-render/refMixin";
|
|
71
|
-
import ContainerItemWrapper from "./container-item-wrapper";
|
|
72
|
-
import containerItemMixin from "./containerItemMixin";
|
|
73
|
-
import FieldComponents from "../../../../components/xform/form-designer/form-widget/field-widget/index";
|
|
74
|
-
|
|
75
|
-
export default {
|
|
76
|
-
name: "tab-item",
|
|
77
|
-
componentName: "ContainerItem",
|
|
78
|
-
mixins: [emitter, i18n, refMixin, containerItemMixin],
|
|
79
|
-
components: {
|
|
80
|
-
ContainerItemWrapper,
|
|
81
|
-
...FieldComponents,
|
|
82
|
-
},
|
|
83
|
-
props: {
|
|
84
|
-
widget: Object,
|
|
85
|
-
},
|
|
86
|
-
inject: ["refList", "sfRefList", "globalModel"],
|
|
87
|
-
data() {
|
|
88
|
-
return {
|
|
89
|
-
activeTabName: "",
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
computed: {
|
|
93
|
-
visibleTabs() {
|
|
94
|
-
return this.widget.tabs.filter((tp) => {
|
|
95
|
-
return !tp.options.hidden;
|
|
96
|
-
});
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<container-item-wrapper
|
|
3
|
+
:widget="widget"
|
|
4
|
+
:class="widget.options.isFullscreen ? 'full-height' : ''"
|
|
5
|
+
>
|
|
6
|
+
<div :key="widget.id" class="tab-container mHeight" v-show="!widget.options.hidden">
|
|
7
|
+
<!-- 高度默认 auto(跟随内容):没在属性面板配 height 就不设。
|
|
8
|
+
原来兜底成固定 300px,内容多了内部滚动、少了留白,装详情内容模块时尤其别扭 -->
|
|
9
|
+
<el-tabs
|
|
10
|
+
v-model="activeTabName"
|
|
11
|
+
:type="widget.displayType"
|
|
12
|
+
:ref="widget.id"
|
|
13
|
+
:class="[customClass, widget.options.tabClass]"
|
|
14
|
+
:style="{
|
|
15
|
+
height: widget.options.isFullscreen ? '' : widget.options.height,
|
|
16
|
+
}"
|
|
17
|
+
>
|
|
18
|
+
<el-tab-pane
|
|
19
|
+
v-for="tab in visibleTabs"
|
|
20
|
+
:key="tab.options.name"
|
|
21
|
+
:label="getI18nLabel(tab.options.label)"
|
|
22
|
+
:disabled="tab.options.disabled"
|
|
23
|
+
:name="tab.options.name"
|
|
24
|
+
>
|
|
25
|
+
<template v-for="(subWidget, swIdx) in tab.widgetList">
|
|
26
|
+
<template v-if="'container' === subWidget.category">
|
|
27
|
+
<component
|
|
28
|
+
:is="subWidget.type + '-item'"
|
|
29
|
+
:widget="subWidget"
|
|
30
|
+
:key="subWidget.id || swIdx"
|
|
31
|
+
:parent-list="tab.widgetList"
|
|
32
|
+
:index-of-parent-list="swIdx"
|
|
33
|
+
:parent-widget="widget"
|
|
34
|
+
:tableParam="tableParam"
|
|
35
|
+
:formItemProp="formItemProp"
|
|
36
|
+
>
|
|
37
|
+
<!-- 递归传递插槽!!! -->
|
|
38
|
+
<template v-for="slot in Object.keys($scopedSlots)" v-slot:[slot]="scope">
|
|
39
|
+
<slot :name="slot" v-bind="scope" />
|
|
40
|
+
</template>
|
|
41
|
+
</component>
|
|
42
|
+
</template>
|
|
43
|
+
<template v-else>
|
|
44
|
+
<component
|
|
45
|
+
:is="subWidget.type + '-widget'"
|
|
46
|
+
:field="subWidget"
|
|
47
|
+
:key="subWidget.id || swIdx"
|
|
48
|
+
:parent-list="tab.widgetList"
|
|
49
|
+
:index-of-parent-list="swIdx"
|
|
50
|
+
:parent-widget="widget"
|
|
51
|
+
:tableParam="tableParam"
|
|
52
|
+
:formItemProp="formItemProp"
|
|
53
|
+
>
|
|
54
|
+
<!-- 递归传递插槽!!! -->
|
|
55
|
+
<template v-for="slot in Object.keys($scopedSlots)" v-slot:[slot]="scope">
|
|
56
|
+
<slot :name="slot" v-bind="scope" />
|
|
57
|
+
</template>
|
|
58
|
+
</component>
|
|
59
|
+
</template>
|
|
60
|
+
</template>
|
|
61
|
+
</el-tab-pane>
|
|
62
|
+
</el-tabs>
|
|
63
|
+
</div>
|
|
64
|
+
</container-item-wrapper>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script>
|
|
68
|
+
import emitter from "../../../../components/xform/utils/emitter";
|
|
69
|
+
import i18n from "../../../../components/xform/utils/i18n";
|
|
70
|
+
import refMixin from "../../../../components/xform/form-render/refMixin";
|
|
71
|
+
import ContainerItemWrapper from "./container-item-wrapper";
|
|
72
|
+
import containerItemMixin from "./containerItemMixin";
|
|
73
|
+
import FieldComponents from "../../../../components/xform/form-designer/form-widget/field-widget/index";
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
name: "tab-item",
|
|
77
|
+
componentName: "ContainerItem",
|
|
78
|
+
mixins: [emitter, i18n, refMixin, containerItemMixin],
|
|
79
|
+
components: {
|
|
80
|
+
ContainerItemWrapper,
|
|
81
|
+
...FieldComponents,
|
|
82
|
+
},
|
|
83
|
+
props: {
|
|
84
|
+
widget: Object,
|
|
85
|
+
},
|
|
86
|
+
inject: ["refList", "sfRefList", "globalModel"],
|
|
87
|
+
data() {
|
|
88
|
+
return {
|
|
89
|
+
activeTabName: "",
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
computed: {
|
|
93
|
+
visibleTabs() {
|
|
94
|
+
return this.widget.tabs.filter((tp) => {
|
|
95
|
+
return !tp.options.hidden;
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
watch: {
|
|
100
|
+
visibleTabs(tabs) {
|
|
101
|
+
const formRef = this.getFormRef();
|
|
102
|
+
if (formRef && formRef.isFieldControlV2 && formRef.isFieldControlV2() &&
|
|
103
|
+
!tabs.some(tab => tab.options.name === this.activeTabName)) {
|
|
104
|
+
this.activeTabName = tabs.length ? tabs[0].options.name : "";
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
created() {
|
|
109
|
+
this.initRefList();
|
|
110
|
+
},
|
|
111
|
+
mounted() {
|
|
112
|
+
this.initActiveTab();
|
|
113
|
+
},
|
|
114
|
+
beforeDestroy() {
|
|
115
|
+
this.unregisterFromRefList();
|
|
116
|
+
},
|
|
117
|
+
methods: {
|
|
118
|
+
initActiveTab() {
|
|
119
|
+
if (this.widget.type === "tab" && this.widget.tabs.length > 0) {
|
|
120
|
+
let activePanes = this.widget.tabs.filter((tp) => {
|
|
121
|
+
return tp.options.active === true;
|
|
122
|
+
});
|
|
123
|
+
if (activePanes.length > 0) {
|
|
124
|
+
this.activeTabName = activePanes[0].options.name;
|
|
125
|
+
} else {
|
|
126
|
+
this.activeTabName = this.widget.tabs[0].options.name;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<style lang="scss" scoped>
|
|
135
|
+
/* 内容区高度跟随内容。原来写死 min-height:312px,会把上面 height:auto 的效果整个抵消掉
|
|
136
|
+
(无论内容多少都至少 312px)。这里只留一个小兜底,保证空标签页不塌成一条线。 */
|
|
137
|
+
::v-deep .mHeight .el-tabs .el-tabs__content{min-height:60px}
|
|
138
|
+
</style>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import isEqual from "lodash/isEqual";
|
|
2
|
+
|
|
3
|
+
export const STATE_KEYS = ["visible", "required", "readonly", "disabled"];
|
|
4
|
+
const TRANSPARENT = ["grid-col", "table-cell", "h5-table-cell"];
|
|
5
|
+
const ISOLATED = ["vf-dialog", "vf-drawer"];
|
|
6
|
+
const ROW_CONTAINERS = ["data-table", "sub-form", "grid-sub-form", "list-h5"];
|
|
7
|
+
|
|
8
|
+
export function readFieldState(widget) {
|
|
9
|
+
const options = widget.options || {};
|
|
10
|
+
const state = { visible: !options.hidden };
|
|
11
|
+
if (widget.formItemFlag) {
|
|
12
|
+
state.required = !!options.required;
|
|
13
|
+
state.readonly = !!options.readonly;
|
|
14
|
+
state.disabled = !!options.disabled;
|
|
15
|
+
}
|
|
16
|
+
return state;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function writeStateOptions(options, state) {
|
|
20
|
+
STATE_KEYS.forEach(key => {
|
|
21
|
+
if (typeof state[key] === "boolean") {
|
|
22
|
+
options[key === "visible" ? "hidden" : key] = key === "visible" ? !state[key] : state[key];
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 设计器和运行时共用同一范围;明细行由独立作用域处理,不混入主表 key。
|
|
28
|
+
export function collectFieldControlTargets(list) {
|
|
29
|
+
const entries = [];
|
|
30
|
+
const keys = new Set();
|
|
31
|
+
const walk = (widgets, parent, depth) => {
|
|
32
|
+
(widgets || []).forEach(widget => {
|
|
33
|
+
if (!widget || !widget.options || ISOLATED.includes(widget.type)) return;
|
|
34
|
+
const container = widget.category === "container";
|
|
35
|
+
let entry = parent;
|
|
36
|
+
if ((container && !TRANSPARENT.includes(widget.type)) || (!container && widget.formItemFlag)) {
|
|
37
|
+
const key = container ? widget.options.name
|
|
38
|
+
: ((widget.options.keyNameEnabled && widget.options.keyName) || widget.options.name);
|
|
39
|
+
if (key) {
|
|
40
|
+
if (keys.has(key)) throw new Error("动态规则目标名称重复:" + key);
|
|
41
|
+
keys.add(key);
|
|
42
|
+
entry = { key, widget, parent, depth, isContainer: container, refKey: widget.options.name };
|
|
43
|
+
entries.push(entry);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!container || ROW_CONTAINERS.includes(widget.type)) return;
|
|
47
|
+
const nextDepth = entry === parent ? depth : depth + 1;
|
|
48
|
+
const children = [];
|
|
49
|
+
(widget.rows || []).forEach(row => children.push(...(row.cols || [])));
|
|
50
|
+
["cols", "tabs", "panes", "widgetList", "buttonWidgetList"].forEach(key => {
|
|
51
|
+
if (Array.isArray(widget[key])) children.push(...widget[key]);
|
|
52
|
+
});
|
|
53
|
+
walk(children, entry, nextDepth);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
walk(list, null, 0);
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function fieldControlTreeOptions(list) {
|
|
61
|
+
return collectFieldControlTargets(list).map(entry => ({
|
|
62
|
+
value: entry.key,
|
|
63
|
+
baseLabel: entry.widget.options.label || entry.key,
|
|
64
|
+
isContainer: entry.isContainer,
|
|
65
|
+
hasName: true,
|
|
66
|
+
type: entry.widget.type,
|
|
67
|
+
depth: entry.depth,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function normalizeState(state, label = "") {
|
|
72
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) {
|
|
73
|
+
throw new Error("动态规则状态格式错误:" + label);
|
|
74
|
+
}
|
|
75
|
+
const result = {};
|
|
76
|
+
STATE_KEYS.forEach(key => {
|
|
77
|
+
const value = state[key];
|
|
78
|
+
if (value === undefined || value === null || value === "") return;
|
|
79
|
+
if (typeof value !== "boolean") throw new Error("动态规则状态必须为布尔值:" + label + "." + key);
|
|
80
|
+
result[key] = value;
|
|
81
|
+
});
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function normalizeLocalRules(rules) {
|
|
86
|
+
if (!Array.isArray(rules)) throw new Error("动态规则必须为数组");
|
|
87
|
+
return rules.map(rule => {
|
|
88
|
+
if (!rule || !Array.isArray(rule.conditions) || !Array.isArray(rule.targets)) {
|
|
89
|
+
throw new Error("动态规则缺少条件或目标");
|
|
90
|
+
}
|
|
91
|
+
if (rule.logic && !["and", "or"].includes(rule.logic)) throw new Error("动态规则条件逻辑无效");
|
|
92
|
+
return {
|
|
93
|
+
...rule,
|
|
94
|
+
targets: rule.targets.map(target => {
|
|
95
|
+
if (!target.field) throw new Error("请选择动态规则目标");
|
|
96
|
+
return { field: target.field, ...normalizeState(target, target.field) };
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function evaluateCondition(condition, model) {
|
|
103
|
+
if (!condition || !condition.field) throw new Error("请选择动态规则条件字段");
|
|
104
|
+
const actual = model[condition.field];
|
|
105
|
+
const expected = condition.value;
|
|
106
|
+
const empty = value => value === undefined || value === null || value === ""
|
|
107
|
+
|| (Array.isArray(value) && value.length === 0);
|
|
108
|
+
const operators = {
|
|
109
|
+
eq: () => String(actual) === String(expected),
|
|
110
|
+
ne: () => String(actual) !== String(expected),
|
|
111
|
+
empty: () => empty(actual),
|
|
112
|
+
notEmpty: () => !empty(actual),
|
|
113
|
+
in: () => String(expected == null ? "" : expected).split(",").map(x => x.trim()).filter(Boolean).includes(String(actual)),
|
|
114
|
+
nin: () => !operators.in(),
|
|
115
|
+
includes: () => Array.isArray(actual) ? actual.map(String).includes(String(expected))
|
|
116
|
+
: String(actual == null ? "" : actual).includes(String(expected)),
|
|
117
|
+
};
|
|
118
|
+
if (["gt", "ge", "lt", "le"].includes(condition.operator)) {
|
|
119
|
+
if (empty(actual) || empty(expected) || typeof actual === "boolean" || Array.isArray(actual)) return false;
|
|
120
|
+
const a = Number(actual);
|
|
121
|
+
const b = Number(expected);
|
|
122
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) return false;
|
|
123
|
+
return { gt: a > b, ge: a >= b, lt: a < b, le: a <= b }[condition.operator];
|
|
124
|
+
}
|
|
125
|
+
if (!operators[condition.operator]) throw new Error("动态规则操作符无效:" + condition.operator);
|
|
126
|
+
return operators[condition.operator]();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function computeFieldStates(rules, baseline, model) {
|
|
130
|
+
const states = Object.create(null);
|
|
131
|
+
Object.keys(baseline).forEach(key => { states[key] = { ...baseline[key] }; });
|
|
132
|
+
normalizeLocalRules(rules).forEach(rule => {
|
|
133
|
+
rule.targets.forEach(target => {
|
|
134
|
+
if (!Object.prototype.hasOwnProperty.call(states, target.field)) {
|
|
135
|
+
throw new Error("动态规则目标不存在或不支持:" + target.field);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// 先检查所有条件,避免短路计算掩盖配置错误。
|
|
139
|
+
const matches = rule.conditions.map(condition => evaluateCondition(condition, model));
|
|
140
|
+
const match = !matches.length || (rule.logic === "or" ? matches.some(Boolean) : matches.every(Boolean));
|
|
141
|
+
if (match) rule.targets.forEach(target => Object.assign(states[target.field], normalizeState(target)));
|
|
142
|
+
});
|
|
143
|
+
return states;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function resolveScriptStates(fieldStates, baseline, allowValue) {
|
|
147
|
+
if (!fieldStates || typeof fieldStates !== "object" || Array.isArray(fieldStates)) {
|
|
148
|
+
throw new Error("后台动态规则缺少有效的 fieldStates");
|
|
149
|
+
}
|
|
150
|
+
const states = Object.create(null);
|
|
151
|
+
const effects = Object.create(null);
|
|
152
|
+
Object.keys(baseline).forEach(key => { states[key] = { ...baseline[key] }; });
|
|
153
|
+
Object.keys(fieldStates).forEach(key => {
|
|
154
|
+
if (!Object.prototype.hasOwnProperty.call(states, key)) throw new Error("动态规则目标不存在或不支持:" + key);
|
|
155
|
+
const state = fieldStates[key];
|
|
156
|
+
Object.assign(states[key], normalizeState(state, key));
|
|
157
|
+
if (Object.prototype.hasOwnProperty.call(state, "value")) {
|
|
158
|
+
if (!allowValue) throw new Error("动态规则返回了 value,请先明确启用自动赋值:" + key);
|
|
159
|
+
effects[key] = { value: state.value };
|
|
160
|
+
}
|
|
161
|
+
if (state.options !== undefined) {
|
|
162
|
+
if (!Array.isArray(state.options)) throw new Error("动态选项必须为数组:" + key);
|
|
163
|
+
effects[key] = { ...effects[key], options: state.options };
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return { states, effects };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isEntryVisible(entry) {
|
|
170
|
+
for (let current = entry; current; current = current.parent) {
|
|
171
|
+
if (current.widget.options.hidden) return false;
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function applyFieldState(entry, state, getRef) {
|
|
177
|
+
const widget = entry.widget;
|
|
178
|
+
const ref = getRef(entry.refKey);
|
|
179
|
+
const target = ref && (ref.field === widget || ref.widget === widget) ? ref : null;
|
|
180
|
+
const setters = { visible: "setHidden", required: "setRequired", readonly: "setReadonly", disabled: "setDisabled" };
|
|
181
|
+
STATE_KEYS.forEach(key => {
|
|
182
|
+
if (typeof state[key] !== "boolean" || (entry.isContainer && key !== "visible")) return;
|
|
183
|
+
const option = key === "visible" ? "hidden" : key;
|
|
184
|
+
const value = key === "visible" ? !state[key] : state[key];
|
|
185
|
+
if (widget.options[option] !== value || (target && ["hidden", "disabled", "readonly"].includes(option) && target[option] !== value)) {
|
|
186
|
+
if (target && typeof target[setters[key]] === "function") target[setters[key]](value);
|
|
187
|
+
else widget.options[option] = value;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function sameValue(a, b) {
|
|
193
|
+
return isEqual(a, b);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function resolveRunMode(config, hasWf) {
|
|
197
|
+
const mode = config.dynamicFieldRunMode || "auto";
|
|
198
|
+
if (!["auto", "initialOnly", "continuous"].includes(mode)) throw new Error("动态规则执行方式无效");
|
|
199
|
+
return mode === "auto" ? (hasWf ? "initialOnly" : "continuous") : mode;
|
|
200
|
+
}
|