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,6 +1,6 @@
1
1
  {
2
2
  "name": "t20-common-lib",
3
- "version": "0.15.23",
3
+ "version": "0.15.24",
4
4
  "description": "T20",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
@@ -1,87 +1,97 @@
1
1
  <template>
2
- <T20-Form-Page>
3
- <T20-page-header slot="header" :content="displayTitle" @back="$goBackCommon"></T20-page-header>
4
- <N20-anchor v-model="action" position="right" nav-width="130">
5
- <el-form
6
- ref="dynamicForms"
7
- class="label-width-16em"
8
- :model="formData"
9
- :rules="rules"
10
- :validate-on-rule-change="false"
11
- label-width="16em"
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-if="needFile"
58
- key="fileuploadtable"
59
- id="fileuploadtable"
60
- :title="$lc('附件信息')"
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
- <N20-file-upload-table
63
- v-if="!needFileDIY"
64
- :action="fileAction"
65
- :table-data="fileUploadTableData"
66
- :see-types="seeTypes"
67
- :show-batch-upload="false"
68
- :data-porp="dataPorp"
69
- :upload-http-request="uploadRequest"
70
- :readonly="uploadReadOnly"
71
- @on-success="onSuccess"
72
- @on-remove="onRemove"
73
- @add-row="addRow"
74
- @down-rows="downRows"
75
- @delete-rows="deleteRows"
76
- />
77
- <slot v-else name="fileUploadTable"></slot>
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
- </N20-anchor>
80
- <div slot="footer">
81
- <el-button type="primary" @click="submit('SUBMIT')">{{ $lc('提交') }}</el-button>
82
- <el-button @click="submit('SAVE')">{{ $lc('保存') }}</el-button>
83
- </div>
84
- </T20-Form-Page>
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 '../mixins/fileUpload'
102
+ import { fileUploadTableMixin } from "../mixins/fileUpload";
93
103
  // 复用 Demo 中的字段组件,按模板的 elementType 动态渲染
94
- const context = require.context('./components/', true, /\.vue$/)
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: 'DynamicForm',
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: '060000000',
142
- appno: '060500000',
143
- bussValue: 'invest_041001001',
144
- attno: 'invest_002',
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: 'Select',
171
- SELECT_LAZY: 'LazySelect',
172
- SELECT_SCROLL_LOAD: 'ScrollLoadSelect',
173
- DIALOG: 'Dialog',
174
- INPUT: 'Input',
175
- AMOUNT: 'Amount',
176
- RATE: 'Rate',
177
- RADIO: 'RadioGroup',
178
- CHECKBOX: 'CheckboxGroup',
179
- TEXTAREA: 'Textarea',
180
- DATE: 'Date',
181
- DATE_RANGE: 'DateRange',
182
- DMY: 'DMY',
183
- AMOUNT_RANGE: 'AmountRange',
184
- RATE_RANGE: 'AmountRange',
185
- UNIT_TREE_SELECT: 'unitTreeSelect',
186
- SELECT_TREE: 'SelectTree'
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('input', val || {})
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 (!this.formData || typeof this.formData !== 'object' || Array.isArray(this.formData)) {
231
- this.formData = {}
232
- changed = true
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 (!['FORM', 'UNIT_FORM'].includes(group.unitType)) return
237
- if (!this.formData[group.prop] || typeof this.formData[group.prop] !== 'object' || Array.isArray(this.formData[group.prop])) {
238
- this.$set(this.formData, group.prop, {})
239
- changed = true
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
- ;(group.elements || []).forEach(item => {
242
- if (!item.prop) return
243
- if (!Object.prototype.hasOwnProperty.call(this.formData[group.prop], item.prop)) {
244
- this.$set(this.formData[group.prop], item.prop, item.defaultValue ?? '')
245
- changed = true
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('input', { ...this.formData })
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) return templateClone
259
- const byFieldNo = Object.create(null)
260
- list.forEach(row => {
261
- if (row && row.fieldNo != null && row.fieldNo !== '') byFieldNo[row.fieldNo] = row
262
- })
263
- ;(templateClone.units || []).forEach(group => {
264
- ;(group.elements || []).forEach(item => {
265
- if (!item.prop) return
266
- const cfg = byFieldNo[item.prop]
267
- if (!cfg) return
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(JSON.stringify(this.mergedTemplateBase))
287
- this.action = this.formGroups.length > 0 ? this.formGroups[0].prop : ''
288
- this.ensureFormDataShape()
289
- this.formGroups.forEach(group => {
290
- ;(group.elements || []).forEach(item => {
291
- this.setFieldRule(group.prop, item)
292
- })
293
- })
294
- this.applyFieldStates()
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: `${item.elementType === 'SELECT' ? this.$lc('请选择') : this.$lc('请输入')}${item.label}`,
301
- trigger: ['blur', 'change']
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('[DynamicForm] toFlag 仅接受 true 或 false')
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 (group.elements || []).find(item => item.prop === fieldProp) || null
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, 'isShow', this.toFlag(visible))
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, 'isShow', this.toFlag(visible))
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, 'isRequired', this.toFlag(required))
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, 'isEditable', this.toFlag(editable))
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 = this.formData && typeof this.formData === 'object' ? this.formData : {}
354
- if (!sourceFormData[groupProp] || typeof sourceFormData[groupProp] !== 'object' || Array.isArray(sourceFormData[groupProp])) {
355
- this.$set(sourceFormData, groupProp, {})
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('input', sourceFormData)
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('[DynamicForm] setFieldOptions 第三个参数须为数组')
408
+ throw new TypeError("[DynamicForm] setFieldOptions 第三个参数须为数组");
374
409
  }
375
- const field = this.getField(groupProp, fieldProp)
376
- if (!field) return
377
- this.$set(field, 'options', options)
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 !== 'string' || !elementType) {
385
- throw new TypeError('[DynamicForm] setFieldElementType 须传入非空字符串')
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(`[DynamicForm] 未注册的 elementType: ${elementType}`)
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, 'elementType', elementType)
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, { ...prev, ...patch })
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, 'options', patch.options)
454
+ this.$set(item, "options", patch.options);
413
455
  }
414
456
  if (patch.elementType !== undefined) {
415
- this.$set(item, 'elementType', patch.elementType)
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
- ;(nextTemplate.units || []).forEach(group => {
426
- group.isShow = this.templateShowOrEditable(group.isShow)
427
- ;(group.elements || []).forEach(item => {
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
- ;(group.elements || []).forEach(item => {
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 (this.$refs.dynamicForms && typeof this.$refs.dynamicForms.clearValidate === 'function') {
443
- this.$refs.dynamicForms.clearValidate()
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
- async submit(operate) {
450
- const valid = await this.$refs.dynamicForms.validate()
451
- if (!valid) return
452
- const payload = { formData: this.formData, operate }
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
- payload.fileUploadTableData = (this.fileUploadTableData || []).filter(t => t.beid)
513
+ result.fileUploadTableData = (this.fileUploadTableData || []).filter(
514
+ (t) => t.beid
515
+ );
455
516
  }
456
- this.$emit('submit', payload)
457
- }
458
- }
459
- }
517
+ return result;
518
+ },
519
+ },
520
+ };
460
521
  </script>
@@ -1,24 +1,5 @@
1
1
  <template>
2
- <N20-page class="dynamic-form-detail">
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
- <N20-expandable-pane v-if="showApprovalProgress" :title="$l('审批进度')">
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