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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "t20-common-lib",
3
- "version": "0.15.22",
3
+ "version": "0.15.23",
4
4
  "description": "T20",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
@@ -0,0 +1,7 @@
1
+ import DynamicFormDetail from './src/main.vue'
2
+
3
+ DynamicFormDetail.install = function (Vue) {
4
+ Vue.component(DynamicFormDetail.name, DynamicFormDetail)
5
+ }
6
+
7
+ export default DynamicFormDetail
@@ -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,