t20-common-lib 0.15.23 → 0.15.24
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,87 +1,97 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
v-bind="formAttrs"
|
|
13
|
-
>
|
|
14
|
-
<N20-anchor-item
|
|
15
|
-
v-for="group in formGroups"
|
|
16
|
-
:key="group.prop"
|
|
17
|
-
:id="group.prop"
|
|
18
|
-
:title="group.unitName"
|
|
19
|
-
v-show="group.isShow !== 0"
|
|
20
|
-
>
|
|
21
|
-
<template v-if="group.unitType === 'FORM'">
|
|
22
|
-
<el-form-item
|
|
23
|
-
v-for="item in group.elements"
|
|
24
|
-
:key="`${group.prop}.${item.prop}.${item.elementType}`"
|
|
25
|
-
:label="item.label"
|
|
26
|
-
:prop="`${group.prop}.${item.prop}`"
|
|
27
|
-
v-show="item.isShow !== 0"
|
|
28
|
-
:class="item.className"
|
|
29
|
-
>
|
|
30
|
-
<component
|
|
31
|
-
:is="elementTypeChange[item.elementType] || item.elementType"
|
|
32
|
-
v-if="elementTypeChange[item.elementType]"
|
|
33
|
-
v-model="formData[group.prop][item.prop]"
|
|
34
|
-
:formData="formData"
|
|
35
|
-
:groupProp="group.prop"
|
|
36
|
-
:items="item"
|
|
37
|
-
:disabled="item.isEditable === 0"
|
|
38
|
-
:label.sync="formData[group.prop][item.nameProp]"
|
|
39
|
-
v-bind="item.props"
|
|
40
|
-
@change="(val, option) => $emit('change', { val, option, groupProp: group.prop, item, group })"
|
|
41
|
-
@dialogChange="obj => $emit('dialog-change', { obj: obj || {}, groupProp: group.prop, item, group })"
|
|
42
|
-
/>
|
|
43
|
-
<slot
|
|
44
|
-
v-else-if="item.elementType === 'SLOT_ELEMENT'"
|
|
45
|
-
:name="item.prop"
|
|
46
|
-
:formData="formData"
|
|
47
|
-
:groupProp="group.prop"
|
|
48
|
-
:prop="item.prop"
|
|
49
|
-
:elementItem="item"
|
|
50
|
-
:value="formData[group.prop][item.prop]"
|
|
51
|
-
/>
|
|
52
|
-
</el-form-item>
|
|
53
|
-
</template>
|
|
54
|
-
</N20-anchor-item>
|
|
55
|
-
</el-form>
|
|
2
|
+
<N20-anchor v-model="action" position="right" nav-width="130">
|
|
3
|
+
<el-form
|
|
4
|
+
ref="dynamicForms"
|
|
5
|
+
class="label-width-16em"
|
|
6
|
+
:model="formData"
|
|
7
|
+
:rules="rules"
|
|
8
|
+
:validate-on-rule-change="false"
|
|
9
|
+
label-width="16em"
|
|
10
|
+
v-bind="formAttrs"
|
|
11
|
+
>
|
|
56
12
|
<N20-anchor-item
|
|
57
|
-
v-
|
|
58
|
-
key="
|
|
59
|
-
id="
|
|
60
|
-
:title="
|
|
13
|
+
v-for="group in formGroups"
|
|
14
|
+
:key="group.prop"
|
|
15
|
+
:id="group.prop"
|
|
16
|
+
:title="group.unitName"
|
|
17
|
+
v-show="group.isShow !== 0"
|
|
61
18
|
>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
19
|
+
<template v-if="group.unitType === 'FORM'">
|
|
20
|
+
<el-form-item
|
|
21
|
+
v-for="item in group.elements"
|
|
22
|
+
:key="`${group.prop}.${item.prop}.${item.elementType}`"
|
|
23
|
+
:label="item.label"
|
|
24
|
+
:prop="`${group.prop}.${item.prop}`"
|
|
25
|
+
v-show="item.isShow !== 0"
|
|
26
|
+
:class="item.className"
|
|
27
|
+
>
|
|
28
|
+
<component
|
|
29
|
+
:is="elementTypeChange[item.elementType] || item.elementType"
|
|
30
|
+
v-if="elementTypeChange[item.elementType]"
|
|
31
|
+
v-model="formData[group.prop][item.prop]"
|
|
32
|
+
:formData="formData"
|
|
33
|
+
:groupProp="group.prop"
|
|
34
|
+
:items="item"
|
|
35
|
+
:disabled="item.isEditable === 0"
|
|
36
|
+
:label.sync="formData[group.prop][item.nameProp]"
|
|
37
|
+
v-bind="item.props"
|
|
38
|
+
@change="
|
|
39
|
+
(val, option) =>
|
|
40
|
+
$emit('change', {
|
|
41
|
+
val,
|
|
42
|
+
option,
|
|
43
|
+
groupProp: group.prop,
|
|
44
|
+
item,
|
|
45
|
+
group,
|
|
46
|
+
})
|
|
47
|
+
"
|
|
48
|
+
@dialogChange="
|
|
49
|
+
(obj) =>
|
|
50
|
+
$emit('dialog-change', {
|
|
51
|
+
obj: obj || {},
|
|
52
|
+
groupProp: group.prop,
|
|
53
|
+
item,
|
|
54
|
+
group,
|
|
55
|
+
})
|
|
56
|
+
"
|
|
57
|
+
/>
|
|
58
|
+
<slot
|
|
59
|
+
v-else-if="item.elementType === 'SLOT_ELEMENT'"
|
|
60
|
+
:name="item.prop"
|
|
61
|
+
:formData="formData"
|
|
62
|
+
:groupProp="group.prop"
|
|
63
|
+
:prop="item.prop"
|
|
64
|
+
:elementItem="item"
|
|
65
|
+
:value="formData[group.prop][item.prop]"
|
|
66
|
+
/>
|
|
67
|
+
</el-form-item>
|
|
68
|
+
</template>
|
|
78
69
|
</N20-anchor-item>
|
|
79
|
-
</
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
</el-form>
|
|
71
|
+
<N20-anchor-item
|
|
72
|
+
v-if="needFile"
|
|
73
|
+
key="fileuploadtable"
|
|
74
|
+
id="fileuploadtable"
|
|
75
|
+
:title="$lc('附件信息')"
|
|
76
|
+
>
|
|
77
|
+
<N20-file-upload-table
|
|
78
|
+
v-if="!needFileDIY"
|
|
79
|
+
:action="fileAction"
|
|
80
|
+
:table-data="fileUploadTableData"
|
|
81
|
+
:see-types="seeTypes"
|
|
82
|
+
:show-batch-upload="false"
|
|
83
|
+
:data-porp="dataPorp"
|
|
84
|
+
:upload-http-request="uploadRequest"
|
|
85
|
+
:readonly="uploadReadOnly"
|
|
86
|
+
@on-success="onSuccess"
|
|
87
|
+
@on-remove="onRemove"
|
|
88
|
+
@add-row="addRow"
|
|
89
|
+
@down-rows="downRows"
|
|
90
|
+
@delete-rows="deleteRows"
|
|
91
|
+
/>
|
|
92
|
+
<slot v-else name="fileUploadTable"></slot>
|
|
93
|
+
</N20-anchor-item>
|
|
94
|
+
</N20-anchor>
|
|
85
95
|
</template>
|
|
86
96
|
|
|
87
97
|
<script>
|
|
@@ -89,73 +99,69 @@
|
|
|
89
99
|
* fieldControlList:与 dynamicData 同时传入,仅在收到模版时按 fieldNo 与 elements[].prop
|
|
90
100
|
* 合并一次 isShow、showName→label、isRequired;之后渲染均以合并后的 mergedTemplateBase 为唯一数据源。
|
|
91
101
|
*/
|
|
92
|
-
import { fileUploadTableMixin } from
|
|
102
|
+
import { fileUploadTableMixin } from "../mixins/fileUpload";
|
|
93
103
|
// 复用 Demo 中的字段组件,按模板的 elementType 动态渲染
|
|
94
|
-
const context = require.context(
|
|
95
|
-
const demoComponents = {}
|
|
96
|
-
context.keys().forEach(key => {
|
|
97
|
-
const component = context(key).default
|
|
98
|
-
if (!component || !component.name) return
|
|
99
|
-
demoComponents[component.name] = component
|
|
100
|
-
})
|
|
104
|
+
const context = require.context("./components/", true, /\.vue$/);
|
|
105
|
+
const demoComponents = {};
|
|
106
|
+
context.keys().forEach((key) => {
|
|
107
|
+
const component = context(key).default;
|
|
108
|
+
if (!component || !component.name) return;
|
|
109
|
+
demoComponents[component.name] = component;
|
|
110
|
+
});
|
|
101
111
|
|
|
102
112
|
export default {
|
|
103
|
-
name:
|
|
113
|
+
name: "DynamicForm",
|
|
104
114
|
components: {
|
|
105
|
-
...demoComponents
|
|
115
|
+
...demoComponents,
|
|
106
116
|
},
|
|
107
117
|
mixins: [fileUploadTableMixin],
|
|
108
118
|
props: {
|
|
109
|
-
title: {
|
|
110
|
-
type: String,
|
|
111
|
-
default: ''
|
|
112
|
-
},
|
|
113
119
|
dynamicData: {
|
|
114
120
|
type: Object,
|
|
115
|
-
default: () => ({ units: [] })
|
|
121
|
+
default: () => ({ units: [] }),
|
|
116
122
|
},
|
|
117
123
|
// 表单数据由页面持有,通过 v-model(value/input) 双向同步
|
|
118
124
|
value: {
|
|
119
125
|
type: Object,
|
|
120
|
-
default: () => ({})
|
|
126
|
+
default: () => ({}),
|
|
121
127
|
},
|
|
122
128
|
/** 字段控制配置,与 dynamicData 一并传入;仅整合一次 */
|
|
123
129
|
fieldControlList: {
|
|
124
130
|
type: Array,
|
|
125
|
-
default: () => []
|
|
131
|
+
default: () => [],
|
|
126
132
|
},
|
|
127
133
|
/** 是否展示附件锚点(默认关闭,不影响现有用法) */
|
|
128
134
|
needFile: {
|
|
129
135
|
type: Boolean,
|
|
130
|
-
default: true
|
|
136
|
+
default: true,
|
|
131
137
|
},
|
|
132
138
|
/** true 时使用 fileUploadTable 插槽自定义附件区 */
|
|
133
139
|
needFileDIY: {
|
|
134
140
|
type: Boolean,
|
|
135
|
-
default: false
|
|
141
|
+
default: false,
|
|
136
142
|
},
|
|
137
143
|
/** 电子档案 / 上传参数,与 Demo DynamicForm uploadParam 一致 */
|
|
138
144
|
uploadParam: {
|
|
139
145
|
type: Object,
|
|
140
146
|
default: () => ({
|
|
141
|
-
syscode:
|
|
142
|
-
appno:
|
|
143
|
-
bussValue:
|
|
144
|
-
attno:
|
|
145
|
-
bussId: null
|
|
146
|
-
})
|
|
147
|
+
syscode: "060000000",
|
|
148
|
+
appno: "060500000",
|
|
149
|
+
bussValue: "invest_041001001",
|
|
150
|
+
attno: "invest_002",
|
|
151
|
+
bussId: null,
|
|
152
|
+
}),
|
|
147
153
|
},
|
|
148
154
|
uploadReadOnly: {
|
|
149
155
|
type: Boolean,
|
|
150
|
-
default: false
|
|
151
|
-
}
|
|
156
|
+
default: false,
|
|
157
|
+
},
|
|
152
158
|
},
|
|
153
159
|
data() {
|
|
154
160
|
return {
|
|
155
161
|
// ElementUI 校验规则:key 形如 "groupProp.fieldProp"
|
|
156
162
|
rules: {},
|
|
157
163
|
// 右侧锚点当前定位项
|
|
158
|
-
action:
|
|
164
|
+
action: "",
|
|
159
165
|
formAttrs: {},
|
|
160
166
|
// dynamicData + fieldControlList 合并后的模版快照;applyFieldStates 均从此克隆,不再直接用原始 dynamicData
|
|
161
167
|
mergedTemplateBase: null,
|
|
@@ -167,41 +173,38 @@ export default {
|
|
|
167
173
|
isApplyingLinkage: false,
|
|
168
174
|
// 模板字段类型 -> 组件名映射;后续扩展新组件时在这里补
|
|
169
175
|
elementTypeChange: {
|
|
170
|
-
SELECT:
|
|
171
|
-
SELECT_LAZY:
|
|
172
|
-
SELECT_SCROLL_LOAD:
|
|
173
|
-
DIALOG:
|
|
174
|
-
INPUT:
|
|
175
|
-
AMOUNT:
|
|
176
|
-
RATE:
|
|
177
|
-
RADIO:
|
|
178
|
-
CHECKBOX:
|
|
179
|
-
TEXTAREA:
|
|
180
|
-
DATE:
|
|
181
|
-
DATE_RANGE:
|
|
182
|
-
DMY:
|
|
183
|
-
AMOUNT_RANGE:
|
|
184
|
-
RATE_RANGE:
|
|
185
|
-
UNIT_TREE_SELECT:
|
|
186
|
-
SELECT_TREE:
|
|
187
|
-
}
|
|
188
|
-
}
|
|
176
|
+
SELECT: "Select",
|
|
177
|
+
SELECT_LAZY: "LazySelect",
|
|
178
|
+
SELECT_SCROLL_LOAD: "ScrollLoadSelect",
|
|
179
|
+
DIALOG: "Dialog",
|
|
180
|
+
INPUT: "Input",
|
|
181
|
+
AMOUNT: "Amount",
|
|
182
|
+
RATE: "Rate",
|
|
183
|
+
RADIO: "RadioGroup",
|
|
184
|
+
CHECKBOX: "CheckboxGroup",
|
|
185
|
+
TEXTAREA: "Textarea",
|
|
186
|
+
DATE: "Date",
|
|
187
|
+
DATE_RANGE: "DateRange",
|
|
188
|
+
DMY: "DMY",
|
|
189
|
+
AMOUNT_RANGE: "AmountRange",
|
|
190
|
+
RATE_RANGE: "AmountRange",
|
|
191
|
+
UNIT_TREE_SELECT: "unitTreeSelect",
|
|
192
|
+
SELECT_TREE: "SelectTree",
|
|
193
|
+
},
|
|
194
|
+
};
|
|
189
195
|
},
|
|
190
196
|
computed: {
|
|
191
197
|
formData: {
|
|
192
198
|
get() {
|
|
193
|
-
return this.value || {}
|
|
199
|
+
return this.value || {};
|
|
194
200
|
},
|
|
195
201
|
set(val) {
|
|
196
|
-
this.$emit(
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
displayTitle() {
|
|
200
|
-
return this.title || this.$lc('动态表单')
|
|
202
|
+
this.$emit("input", val || {});
|
|
203
|
+
},
|
|
201
204
|
},
|
|
202
205
|
formGroups() {
|
|
203
|
-
return this.runtimeTemplate?.units || []
|
|
204
|
-
}
|
|
206
|
+
return this.runtimeTemplate?.units || [];
|
|
207
|
+
},
|
|
205
208
|
},
|
|
206
209
|
watch: {
|
|
207
210
|
// 模板变更时重建表单模型和规则,保证页面与模板一致
|
|
@@ -209,252 +212,310 @@ export default {
|
|
|
209
212
|
immediate: true,
|
|
210
213
|
deep: true,
|
|
211
214
|
handler() {
|
|
212
|
-
this.rules = {}
|
|
213
|
-
this.initializeFromTemplate()
|
|
214
|
-
}
|
|
215
|
+
this.rules = {};
|
|
216
|
+
this.initializeFromTemplate();
|
|
217
|
+
},
|
|
215
218
|
},
|
|
216
219
|
value: {
|
|
217
220
|
immediate: true,
|
|
218
221
|
deep: true,
|
|
219
222
|
handler() {
|
|
220
|
-
if (this.isApplyingLinkage) return
|
|
223
|
+
if (this.isApplyingLinkage) return;
|
|
221
224
|
// 编辑场景下 value 可能由页面先行组装,等模板就绪后仅补齐缺失字段,不覆盖已传数据。
|
|
222
|
-
if (!this.formGroups.length) return
|
|
223
|
-
this.ensureFormDataShape()
|
|
224
|
-
}
|
|
225
|
-
}
|
|
225
|
+
if (!this.formGroups.length) return;
|
|
226
|
+
this.ensureFormDataShape();
|
|
227
|
+
},
|
|
228
|
+
},
|
|
226
229
|
},
|
|
227
230
|
methods: {
|
|
228
231
|
ensureFormDataShape() {
|
|
229
|
-
let changed = false
|
|
230
|
-
if (
|
|
231
|
-
this.formData
|
|
232
|
-
|
|
232
|
+
let changed = false;
|
|
233
|
+
if (
|
|
234
|
+
!this.formData ||
|
|
235
|
+
typeof this.formData !== "object" ||
|
|
236
|
+
Array.isArray(this.formData)
|
|
237
|
+
) {
|
|
238
|
+
this.formData = {};
|
|
239
|
+
changed = true;
|
|
233
240
|
}
|
|
234
|
-
const formUnits = this.formGroups || []
|
|
235
|
-
formUnits.forEach(group => {
|
|
236
|
-
if (![
|
|
237
|
-
if (
|
|
238
|
-
this
|
|
239
|
-
|
|
241
|
+
const formUnits = this.formGroups || [];
|
|
242
|
+
formUnits.forEach((group) => {
|
|
243
|
+
if (!["FORM", "UNIT_FORM"].includes(group.unitType)) return;
|
|
244
|
+
if (
|
|
245
|
+
!this.formData[group.prop] ||
|
|
246
|
+
typeof this.formData[group.prop] !== "object" ||
|
|
247
|
+
Array.isArray(this.formData[group.prop])
|
|
248
|
+
) {
|
|
249
|
+
this.$set(this.formData, group.prop, {});
|
|
250
|
+
changed = true;
|
|
240
251
|
}
|
|
241
|
-
|
|
242
|
-
if (!item.prop) return
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
252
|
+
(group.elements || []).forEach((item) => {
|
|
253
|
+
if (!item.prop) return;
|
|
254
|
+
if (
|
|
255
|
+
!Object.prototype.hasOwnProperty.call(
|
|
256
|
+
this.formData[group.prop],
|
|
257
|
+
item.prop
|
|
258
|
+
)
|
|
259
|
+
) {
|
|
260
|
+
this.$set(
|
|
261
|
+
this.formData[group.prop],
|
|
262
|
+
item.prop,
|
|
263
|
+
item.defaultValue ?? ""
|
|
264
|
+
);
|
|
265
|
+
changed = true;
|
|
246
266
|
}
|
|
247
|
-
})
|
|
248
|
-
})
|
|
267
|
+
});
|
|
268
|
+
});
|
|
249
269
|
if (changed) {
|
|
250
|
-
this.$emit(
|
|
270
|
+
this.$emit("input", { ...this.formData });
|
|
251
271
|
}
|
|
252
272
|
},
|
|
253
273
|
/**
|
|
254
274
|
* 将 fieldControlList 合并进模版克隆(fieldNo 对应 elements[].prop)
|
|
255
275
|
*/
|
|
256
276
|
mergeFieldControlIntoTemplate(templateClone) {
|
|
257
|
-
const list = this.fieldControlList
|
|
258
|
-
if (!templateClone?.units || !Array.isArray(list) || list.length === 0)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!
|
|
277
|
+
const list = this.fieldControlList;
|
|
278
|
+
if (!templateClone?.units || !Array.isArray(list) || list.length === 0)
|
|
279
|
+
return templateClone;
|
|
280
|
+
const byFieldNo = Object.create(null);
|
|
281
|
+
list.forEach((row) => {
|
|
282
|
+
if (row && row.fieldNo != null && row.fieldNo !== "")
|
|
283
|
+
byFieldNo[row.fieldNo] = row;
|
|
284
|
+
});
|
|
285
|
+
(templateClone.units || []).forEach((group) => {
|
|
286
|
+
(group.elements || []).forEach((item) => {
|
|
287
|
+
if (!item.prop) return;
|
|
288
|
+
const cfg = byFieldNo[item.prop];
|
|
289
|
+
if (!cfg) return;
|
|
268
290
|
if (cfg.isShow !== undefined && cfg.isShow !== null) {
|
|
269
|
-
item.isShow = this.templateShowOrEditable(cfg.isShow)
|
|
291
|
+
item.isShow = this.templateShowOrEditable(cfg.isShow);
|
|
270
292
|
}
|
|
271
|
-
if (cfg.showName != null && String(cfg.showName).trim() !==
|
|
272
|
-
item.label = cfg.showName
|
|
293
|
+
if (cfg.showName != null && String(cfg.showName).trim() !== "") {
|
|
294
|
+
item.label = cfg.showName;
|
|
273
295
|
}
|
|
274
296
|
if (cfg.isRequired !== undefined && cfg.isRequired !== null) {
|
|
275
|
-
item.isRequired = this.templateRequired(cfg.isRequired)
|
|
297
|
+
item.isRequired = this.templateRequired(cfg.isRequired);
|
|
276
298
|
}
|
|
277
|
-
})
|
|
278
|
-
})
|
|
279
|
-
return templateClone
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
return templateClone;
|
|
280
302
|
},
|
|
281
303
|
// 基于模板初始化:分组锚点、默认值、必填规则
|
|
282
304
|
initializeFromTemplate() {
|
|
283
|
-
this.linkageFieldOverrides = {}
|
|
284
|
-
const raw = JSON.parse(JSON.stringify(this.dynamicData || { units: [] }))
|
|
285
|
-
this.mergedTemplateBase = this.mergeFieldControlIntoTemplate(raw)
|
|
286
|
-
this.runtimeTemplate = JSON.parse(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
this.formGroups.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
this.linkageFieldOverrides = {};
|
|
306
|
+
const raw = JSON.parse(JSON.stringify(this.dynamicData || { units: [] }));
|
|
307
|
+
this.mergedTemplateBase = this.mergeFieldControlIntoTemplate(raw);
|
|
308
|
+
this.runtimeTemplate = JSON.parse(
|
|
309
|
+
JSON.stringify(this.mergedTemplateBase)
|
|
310
|
+
);
|
|
311
|
+
this.action = this.formGroups.length > 0 ? this.formGroups[0].prop : "";
|
|
312
|
+
this.ensureFormDataShape();
|
|
313
|
+
this.formGroups.forEach((group) => {
|
|
314
|
+
(group.elements || []).forEach((item) => {
|
|
315
|
+
this.setFieldRule(group.prop, item);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
this.applyFieldStates();
|
|
295
319
|
},
|
|
296
320
|
setFieldRule(groupProp, item) {
|
|
297
321
|
this.$set(this.rules, `${groupProp}.${item.prop}`, [
|
|
298
322
|
{
|
|
299
323
|
required: item.isRequired === 1 && item.isShow !== 0,
|
|
300
|
-
message: `${
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
message: `${
|
|
325
|
+
item.elementType === "SELECT"
|
|
326
|
+
? this.$lc("请选择")
|
|
327
|
+
: this.$lc("请输入")
|
|
328
|
+
}${item.label}`,
|
|
329
|
+
trigger: ["blur", "change"],
|
|
330
|
+
},
|
|
331
|
+
]);
|
|
304
332
|
},
|
|
305
333
|
/**
|
|
306
334
|
* 仅接受 boolean,用于联动 API(setFieldShow 等)显式传入 true/false。
|
|
307
335
|
*/
|
|
308
336
|
toFlag(value) {
|
|
309
337
|
if (value !== true && value !== false) {
|
|
310
|
-
throw new TypeError(
|
|
338
|
+
throw new TypeError("[DynamicForm] toFlag 仅接受 true 或 false");
|
|
311
339
|
}
|
|
312
|
-
return value ? 1 : 0
|
|
340
|
+
return value ? 1 : 0;
|
|
313
341
|
},
|
|
314
342
|
/** 模板 JSON 中 0 | 1 转为运行时 isShow / isEditable(0 隐藏/禁用,非 0 为 1) */
|
|
315
343
|
templateShowOrEditable(v) {
|
|
316
|
-
return v === 0 ? 0 : 1
|
|
344
|
+
return v === 0 ? 0 : 1;
|
|
317
345
|
},
|
|
318
346
|
/** 模板 JSON 中 isRequired:仅 1 为必填 */
|
|
319
347
|
templateRequired(v) {
|
|
320
|
-
return v === 1 ? 1 : 0
|
|
348
|
+
return v === 1 ? 1 : 0;
|
|
321
349
|
},
|
|
322
350
|
getGroup(groupProp) {
|
|
323
|
-
return this.formGroups.find(group => group.prop === groupProp) || null
|
|
351
|
+
return this.formGroups.find((group) => group.prop === groupProp) || null;
|
|
324
352
|
},
|
|
325
353
|
getField(groupProp, fieldProp) {
|
|
326
|
-
const group = this.getGroup(groupProp)
|
|
327
|
-
if (!group) return null
|
|
328
|
-
return (
|
|
354
|
+
const group = this.getGroup(groupProp);
|
|
355
|
+
if (!group) return null;
|
|
356
|
+
return (
|
|
357
|
+
(group.elements || []).find((item) => item.prop === fieldProp) || null
|
|
358
|
+
);
|
|
329
359
|
},
|
|
330
360
|
setGroupShow(groupProp, visible) {
|
|
331
|
-
const group = this.getGroup(groupProp)
|
|
332
|
-
if (!group) return
|
|
333
|
-
this.$set(group,
|
|
361
|
+
const group = this.getGroup(groupProp);
|
|
362
|
+
if (!group) return;
|
|
363
|
+
this.$set(group, "isShow", this.toFlag(visible));
|
|
334
364
|
},
|
|
335
365
|
setFieldShow(groupProp, fieldProp, visible) {
|
|
336
|
-
const field = this.getField(groupProp, fieldProp)
|
|
337
|
-
if (!field) return
|
|
338
|
-
this.$set(field,
|
|
339
|
-
this.setFieldRule(groupProp, field)
|
|
366
|
+
const field = this.getField(groupProp, fieldProp);
|
|
367
|
+
if (!field) return;
|
|
368
|
+
this.$set(field, "isShow", this.toFlag(visible));
|
|
369
|
+
this.setFieldRule(groupProp, field);
|
|
340
370
|
},
|
|
341
371
|
setFieldRequired(groupProp, fieldProp, required) {
|
|
342
|
-
const field = this.getField(groupProp, fieldProp)
|
|
343
|
-
if (!field) return
|
|
344
|
-
this.$set(field,
|
|
345
|
-
this.setFieldRule(groupProp, field)
|
|
372
|
+
const field = this.getField(groupProp, fieldProp);
|
|
373
|
+
if (!field) return;
|
|
374
|
+
this.$set(field, "isRequired", this.toFlag(required));
|
|
375
|
+
this.setFieldRule(groupProp, field);
|
|
346
376
|
},
|
|
347
377
|
setFieldEditable(groupProp, fieldProp, editable) {
|
|
348
|
-
const field = this.getField(groupProp, fieldProp)
|
|
349
|
-
if (!field) return
|
|
350
|
-
this.$set(field,
|
|
378
|
+
const field = this.getField(groupProp, fieldProp);
|
|
379
|
+
if (!field) return;
|
|
380
|
+
this.$set(field, "isEditable", this.toFlag(editable));
|
|
351
381
|
},
|
|
352
382
|
setFieldValue(groupProp, fieldProp, value) {
|
|
353
|
-
const sourceFormData =
|
|
354
|
-
|
|
355
|
-
|
|
383
|
+
const sourceFormData =
|
|
384
|
+
this.formData && typeof this.formData === "object" ? this.formData : {};
|
|
385
|
+
if (
|
|
386
|
+
!sourceFormData[groupProp] ||
|
|
387
|
+
typeof sourceFormData[groupProp] !== "object" ||
|
|
388
|
+
Array.isArray(sourceFormData[groupProp])
|
|
389
|
+
) {
|
|
390
|
+
this.$set(sourceFormData, groupProp, {});
|
|
356
391
|
}
|
|
357
|
-
this.$set(sourceFormData[groupProp], fieldProp, value)
|
|
358
|
-
this.$emit(
|
|
392
|
+
this.$set(sourceFormData[groupProp], fieldProp, value);
|
|
393
|
+
this.$emit("input", sourceFormData);
|
|
359
394
|
},
|
|
360
395
|
/**
|
|
361
396
|
* 场景4:与 value 配套的展示字段(模板里的 nameProp),用于下拉等回显名称。
|
|
362
397
|
*/
|
|
363
398
|
setFieldLabelValue(groupProp, fieldProp, labelText) {
|
|
364
|
-
const field = this.getField(groupProp, fieldProp)
|
|
365
|
-
if (!field || !field.nameProp) return
|
|
366
|
-
this.setFieldValue(groupProp, field.nameProp, labelText)
|
|
399
|
+
const field = this.getField(groupProp, fieldProp);
|
|
400
|
+
if (!field || !field.nameProp) return;
|
|
401
|
+
this.setFieldValue(groupProp, field.nameProp, labelText);
|
|
367
402
|
},
|
|
368
403
|
/**
|
|
369
404
|
* 场景4:覆盖字段的 options(如下拉数据源随 A 变化)。
|
|
370
405
|
*/
|
|
371
406
|
setFieldOptions(groupProp, fieldProp, options) {
|
|
372
407
|
if (!Array.isArray(options)) {
|
|
373
|
-
throw new TypeError(
|
|
408
|
+
throw new TypeError("[DynamicForm] setFieldOptions 第三个参数须为数组");
|
|
374
409
|
}
|
|
375
|
-
const field = this.getField(groupProp, fieldProp)
|
|
376
|
-
if (!field) return
|
|
377
|
-
this.$set(field,
|
|
378
|
-
this.patchLinkageOverride(groupProp, fieldProp, { options })
|
|
410
|
+
const field = this.getField(groupProp, fieldProp);
|
|
411
|
+
if (!field) return;
|
|
412
|
+
this.$set(field, "options", options);
|
|
413
|
+
this.patchLinkageOverride(groupProp, fieldProp, { options });
|
|
379
414
|
},
|
|
380
415
|
/**
|
|
381
416
|
* 场景5:切换字段渲染的 elementType(如 INPUT / SELECT),须在 elementTypeChange 中已注册。
|
|
382
417
|
*/
|
|
383
418
|
setFieldElementType(groupProp, fieldProp, elementType) {
|
|
384
|
-
if (typeof elementType !==
|
|
385
|
-
throw new TypeError(
|
|
419
|
+
if (typeof elementType !== "string" || !elementType) {
|
|
420
|
+
throw new TypeError(
|
|
421
|
+
"[DynamicForm] setFieldElementType 须传入非空字符串"
|
|
422
|
+
);
|
|
386
423
|
}
|
|
387
424
|
if (!this.elementTypeChange[elementType]) {
|
|
388
|
-
throw new TypeError(
|
|
425
|
+
throw new TypeError(
|
|
426
|
+
`[DynamicForm] 未注册的 elementType: ${elementType}`
|
|
427
|
+
);
|
|
389
428
|
}
|
|
390
|
-
const field = this.getField(groupProp, fieldProp)
|
|
391
|
-
if (!field) return
|
|
392
|
-
this.$set(field,
|
|
393
|
-
this.patchLinkageOverride(groupProp, fieldProp, { elementType })
|
|
394
|
-
this.setFieldRule(groupProp, field)
|
|
429
|
+
const field = this.getField(groupProp, fieldProp);
|
|
430
|
+
if (!field) return;
|
|
431
|
+
this.$set(field, "elementType", elementType);
|
|
432
|
+
this.patchLinkageOverride(groupProp, fieldProp, { elementType });
|
|
433
|
+
this.setFieldRule(groupProp, field);
|
|
395
434
|
},
|
|
396
435
|
patchLinkageOverride(groupProp, fieldProp, patch) {
|
|
397
436
|
if (!this.linkageFieldOverrides[groupProp]) {
|
|
398
|
-
this.$set(this.linkageFieldOverrides, groupProp, {})
|
|
437
|
+
this.$set(this.linkageFieldOverrides, groupProp, {});
|
|
399
438
|
}
|
|
400
|
-
const prev = this.linkageFieldOverrides[groupProp][fieldProp] || {}
|
|
401
|
-
this.$set(this.linkageFieldOverrides[groupProp], fieldProp, {
|
|
439
|
+
const prev = this.linkageFieldOverrides[groupProp][fieldProp] || {};
|
|
440
|
+
this.$set(this.linkageFieldOverrides[groupProp], fieldProp, {
|
|
441
|
+
...prev,
|
|
442
|
+
...patch,
|
|
443
|
+
});
|
|
402
444
|
},
|
|
403
445
|
/** 模板每次从 mergedTemplateBase 克隆后,把联动里改过的 options / elementType 写回对应字段 */
|
|
404
446
|
applyLinkageOverrides() {
|
|
405
|
-
Object.keys(this.linkageFieldOverrides || {}).forEach(groupProp => {
|
|
406
|
-
const fields = this.linkageFieldOverrides[groupProp]
|
|
407
|
-
Object.keys(fields || {}).forEach(fieldProp => {
|
|
408
|
-
const patch = fields[fieldProp]
|
|
409
|
-
const item = this.getField(groupProp, fieldProp)
|
|
410
|
-
if (!item || !patch) return
|
|
447
|
+
Object.keys(this.linkageFieldOverrides || {}).forEach((groupProp) => {
|
|
448
|
+
const fields = this.linkageFieldOverrides[groupProp];
|
|
449
|
+
Object.keys(fields || {}).forEach((fieldProp) => {
|
|
450
|
+
const patch = fields[fieldProp];
|
|
451
|
+
const item = this.getField(groupProp, fieldProp);
|
|
452
|
+
if (!item || !patch) return;
|
|
411
453
|
if (patch.options !== undefined) {
|
|
412
|
-
this.$set(item,
|
|
454
|
+
this.$set(item, "options", patch.options);
|
|
413
455
|
}
|
|
414
456
|
if (patch.elementType !== undefined) {
|
|
415
|
-
this.$set(item,
|
|
457
|
+
this.$set(item, "elementType", patch.elementType);
|
|
416
458
|
}
|
|
417
|
-
})
|
|
418
|
-
})
|
|
459
|
+
});
|
|
460
|
+
});
|
|
419
461
|
},
|
|
420
462
|
applyFieldStates() {
|
|
421
|
-
if (!this.mergedTemplateBase?.units) return
|
|
422
|
-
this.isApplyingLinkage = true
|
|
423
|
-
const nextTemplate = JSON.parse(JSON.stringify(this.mergedTemplateBase))
|
|
424
|
-
this.rules = {}
|
|
425
|
-
|
|
426
|
-
group.isShow = this.templateShowOrEditable(group.isShow)
|
|
427
|
-
|
|
428
|
-
item.isShow = this.templateShowOrEditable(item.isShow)
|
|
429
|
-
item.isRequired = this.templateRequired(item.isRequired)
|
|
430
|
-
item.isEditable = this.templateShowOrEditable(item.isEditable)
|
|
431
|
-
})
|
|
432
|
-
})
|
|
433
|
-
this.runtimeTemplate = nextTemplate
|
|
434
|
-
this.applyLinkageOverrides()
|
|
435
|
-
this.formGroups.forEach(group => {
|
|
436
|
-
|
|
437
|
-
this.setFieldRule(group.prop, item)
|
|
438
|
-
})
|
|
439
|
-
})
|
|
463
|
+
if (!this.mergedTemplateBase?.units) return;
|
|
464
|
+
this.isApplyingLinkage = true;
|
|
465
|
+
const nextTemplate = JSON.parse(JSON.stringify(this.mergedTemplateBase));
|
|
466
|
+
this.rules = {};
|
|
467
|
+
(nextTemplate.units || []).forEach((group) => {
|
|
468
|
+
group.isShow = this.templateShowOrEditable(group.isShow);
|
|
469
|
+
(group.elements || []).forEach((item) => {
|
|
470
|
+
item.isShow = this.templateShowOrEditable(item.isShow);
|
|
471
|
+
item.isRequired = this.templateRequired(item.isRequired);
|
|
472
|
+
item.isEditable = this.templateShowOrEditable(item.isEditable);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
this.runtimeTemplate = nextTemplate;
|
|
476
|
+
this.applyLinkageOverrides();
|
|
477
|
+
this.formGroups.forEach((group) => {
|
|
478
|
+
(group.elements || []).forEach((item) => {
|
|
479
|
+
this.setFieldRule(group.prop, item);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
440
482
|
this.$nextTick(() => {
|
|
441
483
|
// 模板重建后统一清理校验态,避免规则变化导致页面出现历史校验提示。
|
|
442
|
-
if (
|
|
443
|
-
this.$refs.dynamicForms
|
|
484
|
+
if (
|
|
485
|
+
this.$refs.dynamicForms &&
|
|
486
|
+
typeof this.$refs.dynamicForms.clearValidate === "function"
|
|
487
|
+
) {
|
|
488
|
+
this.$refs.dynamicForms.clearValidate();
|
|
444
489
|
}
|
|
445
|
-
this.isApplyingLinkage = false
|
|
446
|
-
})
|
|
447
|
-
},
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
490
|
+
this.isApplyingLinkage = false;
|
|
491
|
+
});
|
|
492
|
+
},
|
|
493
|
+
/**
|
|
494
|
+
* 触发表单校验;通过后由返回值携带表单数据与附件(与 needFile 一致时含已落档行)。
|
|
495
|
+
* @returns {Promise<{ valid: false, formData: object } | { valid: true, formData: object, operate?: string, fileUploadTableData?: array }>}
|
|
496
|
+
*/
|
|
497
|
+
async validate() {
|
|
498
|
+
const formRef = this.$refs.dynamicForms;
|
|
499
|
+
if (!formRef || typeof formRef.validate !== "function") {
|
|
500
|
+
return { valid: false, formData: this.formData };
|
|
501
|
+
}
|
|
502
|
+
const pass = await new Promise((resolve) => {
|
|
503
|
+
formRef.validate((valid) => resolve(valid));
|
|
504
|
+
});
|
|
505
|
+
if (!pass) {
|
|
506
|
+
return { valid: false, formData: this.formData };
|
|
507
|
+
}
|
|
508
|
+
const result = {
|
|
509
|
+
valid: true,
|
|
510
|
+
formData: this.formData,
|
|
511
|
+
};
|
|
453
512
|
if (this.needFile) {
|
|
454
|
-
|
|
513
|
+
result.fileUploadTableData = (this.fileUploadTableData || []).filter(
|
|
514
|
+
(t) => t.beid
|
|
515
|
+
);
|
|
455
516
|
}
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
517
|
+
return result;
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
};
|
|
460
521
|
</script>
|
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<!--
|
|
4
|
-
使用规范(与 DynamicForm 同一套 dynamicData):
|
|
5
|
-
1. value 与 DynamicForm 提交的 formData 结构一致:{ [groupProp]: { [fieldProp]: 值 } }。
|
|
6
|
-
2. 下拉、弹窗等需要展示中文时:须在提交数据中同时写入模板里的 nameProp 对应字段;展示页优先显示 nameProp。
|
|
7
|
-
3. 未配置 nameProp 且无法从 options 反查时,直接展示存证值(代码/ id)。
|
|
8
|
-
4. SLOT_ELEMENT 由具名插槽 item.prop 自行渲染。
|
|
9
|
-
5. 附件:needFile / uploadParam 等与 DynamicForm 一致;详情默认 uploadReadOnly=true。
|
|
10
|
-
6. 子表、审批流等业务区由外层页面编排,或用 append 插槽扩展。
|
|
11
|
-
-->
|
|
12
|
-
<T20-page-header
|
|
13
|
-
v-if="showPageHeader"
|
|
14
|
-
slot="header"
|
|
15
|
-
@back="onBack"
|
|
16
|
-
:content="title"
|
|
17
|
-
>
|
|
18
|
-
<template slot="headerButtons">
|
|
19
|
-
<slot name="headerButtons" />
|
|
20
|
-
</template>
|
|
21
|
-
</T20-page-header>
|
|
2
|
+
<div class="dynamic-form-detail">
|
|
22
3
|
<N20-expandable-pane
|
|
23
4
|
v-for="group in formGroups"
|
|
24
5
|
:key="group.prop"
|
|
@@ -70,29 +51,13 @@
|
|
|
70
51
|
/>
|
|
71
52
|
<slot v-else name="fileUploadTable"></slot>
|
|
72
53
|
</N20-expandable-pane>
|
|
73
|
-
|
|
74
|
-
<el-button slot="tips" size="mini" plain @click="imgV = true">{{ $l('流程图查看') }}</el-button>
|
|
75
|
-
<N20-approve-card :procInstId="processInstanceId" />
|
|
76
|
-
<el-dialog
|
|
77
|
-
v-drag
|
|
78
|
-
:title="$l('查看流程')"
|
|
79
|
-
:visible.sync="imgV"
|
|
80
|
-
width="65%"
|
|
81
|
-
class="p-a-0"
|
|
82
|
-
append-to-body
|
|
83
|
-
top="10vh"
|
|
84
|
-
>
|
|
85
|
-
<N20-approval-img class="text-c p-a" style="height: 70vh; overflow: auto" />
|
|
86
|
-
</el-dialog>
|
|
87
|
-
</N20-expandable-pane>
|
|
88
|
-
<N20-approval-buttons v-if="showApprovalFooter" slot="footer" />
|
|
89
|
-
<slot name="append" />
|
|
90
|
-
</N20-page>
|
|
54
|
+
</div>
|
|
91
55
|
</template>
|
|
92
56
|
|
|
93
57
|
<script>
|
|
94
58
|
/**
|
|
95
59
|
* 只读展示:与 DynamicForm 共用 dynamicData / fieldControlList 合并规则。
|
|
60
|
+
* 页面壳(N20-page、顶栏、审批区等)由业务页自行编排。
|
|
96
61
|
*/
|
|
97
62
|
import { fileUploadTableMixin } from '../../dynamic-form/mixins/fileUpload'
|
|
98
63
|
|
|
@@ -112,21 +77,6 @@ export default {
|
|
|
112
77
|
type: Object,
|
|
113
78
|
default: () => ({})
|
|
114
79
|
},
|
|
115
|
-
/** 顶栏标题 */
|
|
116
|
-
title: {
|
|
117
|
-
type: String,
|
|
118
|
-
default: ''
|
|
119
|
-
},
|
|
120
|
-
/** 是否显示页面头 */
|
|
121
|
-
showPageHeader: {
|
|
122
|
-
type: Boolean,
|
|
123
|
-
default: true
|
|
124
|
-
},
|
|
125
|
-
/** 返回事件:默认调用 $goBackCommon;传 false 则仅抛出 back 事件 */
|
|
126
|
-
autoBack: {
|
|
127
|
-
type: Boolean,
|
|
128
|
-
default: true
|
|
129
|
-
},
|
|
130
80
|
/** 是否展示附件区(与 DynamicForm 一致) */
|
|
131
81
|
needFile: {
|
|
132
82
|
type: Boolean,
|
|
@@ -152,27 +102,12 @@ export default {
|
|
|
152
102
|
uploadReadOnly: {
|
|
153
103
|
type: Boolean,
|
|
154
104
|
default: true
|
|
155
|
-
},
|
|
156
|
-
/** 是否展示审批进度 */
|
|
157
|
-
showApprovalProgress: {
|
|
158
|
-
type: Boolean,
|
|
159
|
-
default: false
|
|
160
|
-
},
|
|
161
|
-
/** 是否展示审批按钮(同时需 orderId + taskId) */
|
|
162
|
-
showApprovalFooter: {
|
|
163
|
-
type: Boolean,
|
|
164
|
-
default: false
|
|
165
|
-
},
|
|
166
|
-
processInstanceId: {
|
|
167
|
-
type: [String, Number],
|
|
168
|
-
default: ''
|
|
169
105
|
}
|
|
170
106
|
},
|
|
171
107
|
data() {
|
|
172
108
|
return {
|
|
173
109
|
mergedTemplateBase: null,
|
|
174
|
-
runtimeTemplate: { units: [] }
|
|
175
|
-
imgV: false
|
|
110
|
+
runtimeTemplate: { units: [] }
|
|
176
111
|
}
|
|
177
112
|
},
|
|
178
113
|
computed: {
|
|
@@ -199,12 +134,6 @@ export default {
|
|
|
199
134
|
}
|
|
200
135
|
},
|
|
201
136
|
methods: {
|
|
202
|
-
onBack() {
|
|
203
|
-
this.$emit('back')
|
|
204
|
-
if (this.autoBack !== false && typeof this.$goBackCommon === 'function') {
|
|
205
|
-
this.$goBackCommon()
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
137
|
rowValue(groupProp, fieldProp) {
|
|
209
138
|
const g = this.formData[groupProp]
|
|
210
139
|
if (!g || typeof g !== 'object') return undefined
|