t20-common-lib 0.15.22 → 0.15.23
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
|
@@ -0,0 +1,358 @@
|
|
|
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>
|
|
22
|
+
<N20-expandable-pane
|
|
23
|
+
v-for="group in formGroups"
|
|
24
|
+
:key="group.prop"
|
|
25
|
+
:title="group.unitName"
|
|
26
|
+
v-show="group.isShow !== 0"
|
|
27
|
+
>
|
|
28
|
+
<template v-if="group.unitType === 'FORM' || group.unitType === 'UNIT_FORM'">
|
|
29
|
+
<N20-descriptions>
|
|
30
|
+
<el-descriptions-item
|
|
31
|
+
v-for="item in group.elements"
|
|
32
|
+
:key="`${group.prop}.${item.prop}.${item.elementType}`"
|
|
33
|
+
:label="item.label"
|
|
34
|
+
v-show="item.isShow !== 0"
|
|
35
|
+
>
|
|
36
|
+
<template v-if="item.elementType === 'SLOT_ELEMENT'">
|
|
37
|
+
<slot
|
|
38
|
+
:name="item.prop"
|
|
39
|
+
:form-data="formData"
|
|
40
|
+
:group-prop="group.prop"
|
|
41
|
+
:prop="item.prop"
|
|
42
|
+
:element-item="item"
|
|
43
|
+
:value="rowValue(group.prop, item.prop)"
|
|
44
|
+
/>
|
|
45
|
+
</template>
|
|
46
|
+
<span v-else class="dynamic-form-detail__text">{{ displayText(group.prop, item) }}</span>
|
|
47
|
+
</el-descriptions-item>
|
|
48
|
+
</N20-descriptions>
|
|
49
|
+
</template>
|
|
50
|
+
</N20-expandable-pane>
|
|
51
|
+
<N20-expandable-pane
|
|
52
|
+
v-if="needFile"
|
|
53
|
+
key="fileuploadtable"
|
|
54
|
+
:title="$lc('附件信息')"
|
|
55
|
+
>
|
|
56
|
+
<N20-file-upload-table
|
|
57
|
+
v-if="!needFileDIY"
|
|
58
|
+
:action="fileAction"
|
|
59
|
+
:table-data="fileUploadTableData"
|
|
60
|
+
:see-types="seeTypes"
|
|
61
|
+
:show-batch-upload="false"
|
|
62
|
+
:data-porp="dataPorp"
|
|
63
|
+
:upload-http-request="uploadRequest"
|
|
64
|
+
:readonly="uploadReadOnly"
|
|
65
|
+
@on-success="onSuccess"
|
|
66
|
+
@on-remove="onRemove"
|
|
67
|
+
@add-row="addRow"
|
|
68
|
+
@down-rows="downRows"
|
|
69
|
+
@delete-rows="deleteRows"
|
|
70
|
+
/>
|
|
71
|
+
<slot v-else name="fileUploadTable"></slot>
|
|
72
|
+
</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>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<script>
|
|
94
|
+
/**
|
|
95
|
+
* 只读展示:与 DynamicForm 共用 dynamicData / fieldControlList 合并规则。
|
|
96
|
+
*/
|
|
97
|
+
import { fileUploadTableMixin } from '../../dynamic-form/mixins/fileUpload'
|
|
98
|
+
|
|
99
|
+
export default {
|
|
100
|
+
name: 'DynamicFormDetail',
|
|
101
|
+
mixins: [fileUploadTableMixin],
|
|
102
|
+
props: {
|
|
103
|
+
dynamicData: {
|
|
104
|
+
type: Object,
|
|
105
|
+
default: () => ({ units: [] })
|
|
106
|
+
},
|
|
107
|
+
fieldControlList: {
|
|
108
|
+
type: Array,
|
|
109
|
+
default: () => []
|
|
110
|
+
},
|
|
111
|
+
value: {
|
|
112
|
+
type: Object,
|
|
113
|
+
default: () => ({})
|
|
114
|
+
},
|
|
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
|
+
/** 是否展示附件区(与 DynamicForm 一致) */
|
|
131
|
+
needFile: {
|
|
132
|
+
type: Boolean,
|
|
133
|
+
default: false
|
|
134
|
+
},
|
|
135
|
+
/** true 时使用 fileUploadTable 插槽自定义附件区 */
|
|
136
|
+
needFileDIY: {
|
|
137
|
+
type: Boolean,
|
|
138
|
+
default: false
|
|
139
|
+
},
|
|
140
|
+
/** 电子档案 / 上传参数,与 DynamicForm uploadParam 一致 */
|
|
141
|
+
uploadParam: {
|
|
142
|
+
type: Object,
|
|
143
|
+
default: () => ({
|
|
144
|
+
syscode: '060000000',
|
|
145
|
+
appno: '060500000',
|
|
146
|
+
bussValue: 'invest_041001001',
|
|
147
|
+
attno: 'invest_002',
|
|
148
|
+
bussId: null
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
/** 详情页默认只读;需可上传时再置为 false */
|
|
152
|
+
uploadReadOnly: {
|
|
153
|
+
type: Boolean,
|
|
154
|
+
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
|
+
}
|
|
170
|
+
},
|
|
171
|
+
data() {
|
|
172
|
+
return {
|
|
173
|
+
mergedTemplateBase: null,
|
|
174
|
+
runtimeTemplate: { units: [] },
|
|
175
|
+
imgV: false
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
computed: {
|
|
179
|
+
formData() {
|
|
180
|
+
return this.value && typeof this.value === 'object' && !Array.isArray(this.value) ? this.value : {}
|
|
181
|
+
},
|
|
182
|
+
formGroups() {
|
|
183
|
+
return this.runtimeTemplate?.units || []
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
watch: {
|
|
187
|
+
dynamicData: {
|
|
188
|
+
immediate: true,
|
|
189
|
+
deep: true,
|
|
190
|
+
handler() {
|
|
191
|
+
this.buildRuntimeTemplate()
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
fieldControlList: {
|
|
195
|
+
deep: true,
|
|
196
|
+
handler() {
|
|
197
|
+
this.buildRuntimeTemplate()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
methods: {
|
|
202
|
+
onBack() {
|
|
203
|
+
this.$emit('back')
|
|
204
|
+
if (this.autoBack !== false && typeof this.$goBackCommon === 'function') {
|
|
205
|
+
this.$goBackCommon()
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
rowValue(groupProp, fieldProp) {
|
|
209
|
+
const g = this.formData[groupProp]
|
|
210
|
+
if (!g || typeof g !== 'object') return undefined
|
|
211
|
+
return g[fieldProp]
|
|
212
|
+
},
|
|
213
|
+
templateShowOrEditable(v) {
|
|
214
|
+
return v === 0 ? 0 : 1
|
|
215
|
+
},
|
|
216
|
+
templateRequired(v) {
|
|
217
|
+
return v === 1 ? 1 : 0
|
|
218
|
+
},
|
|
219
|
+
mergeFieldControlIntoTemplate(templateClone) {
|
|
220
|
+
const list = this.fieldControlList
|
|
221
|
+
if (!templateClone?.units || !Array.isArray(list) || list.length === 0) return templateClone
|
|
222
|
+
const byFieldNo = Object.create(null)
|
|
223
|
+
list.forEach(row => {
|
|
224
|
+
if (row && row.fieldNo != null && row.fieldNo !== '') byFieldNo[row.fieldNo] = row
|
|
225
|
+
})
|
|
226
|
+
;(templateClone.units || []).forEach(group => {
|
|
227
|
+
;(group.elements || []).forEach(item => {
|
|
228
|
+
if (!item.prop) return
|
|
229
|
+
const cfg = byFieldNo[item.prop]
|
|
230
|
+
if (!cfg) return
|
|
231
|
+
if (cfg.isShow !== undefined && cfg.isShow !== null) {
|
|
232
|
+
item.isShow = this.templateShowOrEditable(cfg.isShow)
|
|
233
|
+
}
|
|
234
|
+
if (cfg.showName != null && String(cfg.showName).trim() !== '') {
|
|
235
|
+
item.label = cfg.showName
|
|
236
|
+
}
|
|
237
|
+
if (cfg.isRequired !== undefined && cfg.isRequired !== null) {
|
|
238
|
+
item.isRequired = this.templateRequired(cfg.isRequired)
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
return templateClone
|
|
243
|
+
},
|
|
244
|
+
buildRuntimeTemplate() {
|
|
245
|
+
const raw = JSON.parse(JSON.stringify(this.dynamicData || { units: [] }))
|
|
246
|
+
this.mergedTemplateBase = this.mergeFieldControlIntoTemplate(raw)
|
|
247
|
+
const next = JSON.parse(JSON.stringify(this.mergedTemplateBase))
|
|
248
|
+
;(next.units || []).forEach(group => {
|
|
249
|
+
group.isShow = this.templateShowOrEditable(group.isShow)
|
|
250
|
+
;(group.elements || []).forEach(item => {
|
|
251
|
+
item.isShow = this.templateShowOrEditable(item.isShow)
|
|
252
|
+
item.isRequired = this.templateRequired(item.isRequired)
|
|
253
|
+
item.isEditable = this.templateShowOrEditable(item.isEditable)
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
this.runtimeTemplate = next
|
|
257
|
+
},
|
|
258
|
+
emptyMark() {
|
|
259
|
+
return '--'
|
|
260
|
+
},
|
|
261
|
+
formatMoney(val) {
|
|
262
|
+
const n = Number(val)
|
|
263
|
+
if (Number.isNaN(n)) return val
|
|
264
|
+
return n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
265
|
+
},
|
|
266
|
+
formatRate(val) {
|
|
267
|
+
const n = Number(val)
|
|
268
|
+
if (Number.isNaN(n)) return val
|
|
269
|
+
return String(n)
|
|
270
|
+
},
|
|
271
|
+
labelFromOptions(options, raw, multiple) {
|
|
272
|
+
const opts = options || []
|
|
273
|
+
if (multiple && Array.isArray(raw)) {
|
|
274
|
+
const parts = raw.map(v => {
|
|
275
|
+
const o = opts.find(x => x.value === v || x.value == v)
|
|
276
|
+
return o ? o.label : v
|
|
277
|
+
})
|
|
278
|
+
return parts.filter(Boolean).join(', ')
|
|
279
|
+
}
|
|
280
|
+
const o = opts.find(x => x.value === raw || x.value == raw)
|
|
281
|
+
return o ? o.label : ''
|
|
282
|
+
},
|
|
283
|
+
labelsFromCheckbox(options, raw) {
|
|
284
|
+
const opts = options || []
|
|
285
|
+
const arr = Array.isArray(raw) ? raw : raw === '' || raw == null ? [] : [raw]
|
|
286
|
+
return arr
|
|
287
|
+
.map(v => {
|
|
288
|
+
const o = opts.find(x => x.value === v || x.value == v)
|
|
289
|
+
return o ? o.label : v
|
|
290
|
+
})
|
|
291
|
+
.filter(x => x !== '' && x != null)
|
|
292
|
+
.join(', ')
|
|
293
|
+
},
|
|
294
|
+
displayText(groupProp, item) {
|
|
295
|
+
const row = this.formData[groupProp]
|
|
296
|
+
if (!row || typeof row !== 'object') return this.emptyMark()
|
|
297
|
+
|
|
298
|
+
if (item.nameProp) {
|
|
299
|
+
const np = row[item.nameProp]
|
|
300
|
+
if (np !== undefined && np !== null && String(np).trim() !== '') {
|
|
301
|
+
return np
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const raw = row[item.prop]
|
|
306
|
+
if (raw === null || raw === undefined || raw === '') return this.emptyMark()
|
|
307
|
+
|
|
308
|
+
const et = item.elementType
|
|
309
|
+
const opts = item.options || []
|
|
310
|
+
|
|
311
|
+
switch (et) {
|
|
312
|
+
case 'SELECT':
|
|
313
|
+
case 'SELECT_LAZY':
|
|
314
|
+
case 'SELECT_SCROLL_LOAD':
|
|
315
|
+
return this.labelFromOptions(opts, raw, item.multiple) || raw
|
|
316
|
+
case 'RADIO':
|
|
317
|
+
return this.labelFromOptions(opts, raw, false) || raw
|
|
318
|
+
case 'CHECKBOX':
|
|
319
|
+
return this.labelsFromCheckbox(opts, raw) || raw
|
|
320
|
+
case 'AMOUNT':
|
|
321
|
+
return this.formatMoney(raw)
|
|
322
|
+
case 'RATE':
|
|
323
|
+
return this.formatRate(raw)
|
|
324
|
+
case 'DATE_RANGE': {
|
|
325
|
+
const s = String(raw)
|
|
326
|
+
const parts = s.split(',')
|
|
327
|
+
if (parts.length >= 2) return `${parts[0]} ~ ${parts[1]}`
|
|
328
|
+
return raw
|
|
329
|
+
}
|
|
330
|
+
case 'AMOUNT_RANGE':
|
|
331
|
+
case 'RATE_RANGE': {
|
|
332
|
+
const parts = String(raw).split(',')
|
|
333
|
+
if (parts.length < 2) return raw
|
|
334
|
+
const isRate = et === 'RATE_RANGE'
|
|
335
|
+
const a = isRate ? this.formatRate(parts[0]) : this.formatMoney(parts[0])
|
|
336
|
+
const b = isRate ? this.formatRate(parts[1]) : this.formatMoney(parts[1])
|
|
337
|
+
return `${a} ~ ${b}`
|
|
338
|
+
}
|
|
339
|
+
case 'INPUT':
|
|
340
|
+
case 'TEXTAREA':
|
|
341
|
+
case 'DATE':
|
|
342
|
+
case 'DMY':
|
|
343
|
+
case 'DIALOG':
|
|
344
|
+
case 'UNIT_TREE_SELECT':
|
|
345
|
+
case 'SELECT_TREE':
|
|
346
|
+
default:
|
|
347
|
+
return raw
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
</script>
|
|
353
|
+
|
|
354
|
+
<style scoped>
|
|
355
|
+
.dynamic-form-detail__text {
|
|
356
|
+
word-break: break-word;
|
|
357
|
+
}
|
|
358
|
+
</style>
|
package/src/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import SelectTreeUnitForm from '../packages/select-tree-unit-form/index.js'
|
|
|
22
22
|
import ScrollLoadSelect from '../packages/scroll-load-select/index.js'
|
|
23
23
|
import FileImport from '../packages/file-import/index.js'
|
|
24
24
|
import DynamicForm from '../packages/dynamic-form/index.js'
|
|
25
|
+
import DynamicFormDetail from '../packages/dynamic-form-detail/index.js'
|
|
25
26
|
|
|
26
27
|
// 存储组件列表
|
|
27
28
|
const components = [
|
|
@@ -42,7 +43,8 @@ const components = [
|
|
|
42
43
|
SelectTreeUnit,
|
|
43
44
|
SelectTreeUnitForm,
|
|
44
45
|
FileImport,
|
|
45
|
-
DynamicForm
|
|
46
|
+
DynamicForm,
|
|
47
|
+
DynamicFormDetail
|
|
46
48
|
]
|
|
47
49
|
|
|
48
50
|
// 定义 install 方法,接收 Vue 作为参数
|
|
@@ -89,6 +91,7 @@ export {
|
|
|
89
91
|
SelectTreeUnitForm,
|
|
90
92
|
FileImport,
|
|
91
93
|
DynamicForm,
|
|
94
|
+
DynamicFormDetail,
|
|
92
95
|
// 工具方法
|
|
93
96
|
repairEl,
|
|
94
97
|
getColumnWidth,
|