n20-common-lib 3.0.31 → 3.0.32

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.
@@ -1,225 +1,76 @@
1
1
  <template>
2
2
  <div class="file-upload-table-v3">
3
- <!-- <div class="flex-box flex-v flex-lr m-b-s">
4
- <div>
5
- <slot name="title">附件上传</slot>
6
- <span v-if="fileText" v-html="fileText"></span>
7
- </div>
8
- <div>
9
- <slot v-if="!readonly" name="add-btn">
10
- <el-button type="primary" size="mini" @click="addRow">{{ '新增' | $lc }}</el-button>
11
- </slot>
12
- <slot v-if="!readonly && showBatchUpload" name="batch-btn">
13
- <el-button size="mini" plain @click="handleBathUpload">{{ '批量上传' | $lc }}</el-button>
14
- </slot>
15
- <slot v-if="!readonly && showBatchPrint" name="batch-btn">
16
- <el-button size="mini" plain @click="batchPrint">{{ '批量打印' | $lc }}</el-button>
17
- </slot>
18
- <slot name="down-btn">
19
- <el-button size="mini" plain @click="downRows">{{ '下载' | $lc }}</el-button>
20
- </slot>
21
- <slot v-if="!readonly" name="delete-btn">
22
- <el-button type="danger" plain size="mini" @click="deleteRows">{{ '删除' | $lc }}</el-button>
23
- </slot>
3
+ <div class="panel">
4
+ <div class="panel-top">
5
+ <div class="summary">
6
+ <i class="v3-icon-link"></i>
7
+ <span>{{ uploadedCount }} 个附件</span>
8
+ <el-button type="text" class="summary-action v3-icon-download" @click="downRows">全部下载</el-button>
9
+ </div>
10
+ <el-button type="text" class="toggle-btn" @click="toggleCollapse">
11
+ {{ showAllTypes ? '收起未传类型' : '展开全部' }}
12
+ <i :class="['el-icon-arrow-down', { 'is-reverse': showAllTypes }]"></i>
13
+ </el-button>
24
14
  </div>
25
- </div>
26
- <el-table
27
- :data="tableData"
28
- :row-key="keys.rowKey"
29
- :height="height"
30
- border
31
- @selection-change="(selection) => (selectionList = selection)"
32
- >
33
- <slot name="selection-column">
34
- <el-table-column type="selection" width="50" align="center" />
35
- </slot>
36
- <template v-if="dataPorp.slotHeader">
37
- <el-table-column
38
- v-for="item in dataPorp.slotHeader"
39
- :key="item.prop"
40
- :label="item.label"
41
- :prop="item.prop"
42
- :align="item.align || 'center'"
43
- :width="item.width || 'auto'"
44
- :show-overflow-tooltip="item.showOverflowTooltip || item['show-overflow-tooltip']"
45
- />
46
- </template>
47
- <template v-else>
48
- <el-table-column :label="'附件类型' | $lc" :prop="keys.type">
49
- <slot slot="header" slot-scope="scope" name="type-header" :column="scope.column">{{ '附件类型' | $lc }}</slot>
50
- <slot slot-scope="{ row }" name="type" :row="row">
51
- <span v-if="readonly">{{ row[keys.type] | typeFiter(typeOptions) }}</span>
52
- <div v-else class="flex-box flex-v">
53
- <span v-if="requiredTypes.includes(row[keys.type])" style="color: red" class="m-r-s">*</span>
54
- <el-select
55
- v-model="row[keys.type]"
56
- :disabled="row._typeDisabled"
57
- :placeholder="'请选择' | $lc"
58
- style="width: calc(100% - 16px)"
59
- @change="$emit('typeChange', row[keys.type])"
60
- >
61
- <el-option
62
- v-for="item in typeOptions"
63
- :key="item.type"
64
- :disabled="item.disabled"
65
- :value="item.type"
66
- :label="item.label"
67
- />
68
- </el-select>
15
+
16
+ <div class="type-list">
17
+ <div v-for="(group, index) in displayTypeGroups" :key="group.typeValue || index" class="type-item">
18
+ <div class="type-row" :class="[{ 'type-row__is-success': getTypeStatus(group.typeValue) === 'success' }]">
19
+ <div class="type-meta">
20
+ <span v-if="isRequiredType(group.item)" class="required">*</span>
21
+ <span class="type-index">{{ index + 1 }}、</span>
22
+ <span class="type-name">{{ group.item[typeMap.label] }}</span>
23
+ <img v-if="getTypeStatus(group.typeValue) === 'success'" src="./ysc.svg" class="status-icon" />
24
+ <img v-else-if="getTypeStatus(group.typeValue) === 'warning'" src="./jytg.svg" class="status-icon" />
25
+ <img v-else src="./wsc.svg" class="status-icon" />
69
26
  </div>
70
- </slot>
71
- </el-table-column>
72
- <el-table-column :label="'附件名称' | $lc" :prop="keys.name">
73
- <slot slot="header" slot-scope="scope" name="name-header" :column="scope.column">{{ '附件名称' | $lc }}</slot>
74
- <slot slot-scope="{ row }" name="name" :row="row">
75
- <span v-if="readonly">{{ row[keys.name] ? row[keys.name].replace(/\.[A-z0-9]+$/, '') : '' }}</span>
76
- <el-input
77
- v-else
78
- v-title
79
- :value="row[keys.name] ? row[keys.name].replace(/\.[A-z0-9]+$/, '') : ''"
80
- disabled
81
- :placeholder="'请输入' | $lc"
82
- />
83
- </slot>
84
- </el-table-column>
85
- <el-table-column v-if="readonly" :label="'附件上传' | $lc" :prop="keys.name">
86
- <slot slot="header" slot-scope="scope" name="upload-header" :column="scope.column"
87
- >{{ '附件上传' | $lc }}
88
- </slot>
89
- <slot slot-scope="{ row }" name="upload" :row="row">
90
- {{ $options.filters.fileName(row, keys.url) || row[keys.name] }}
91
- </slot>
92
- </el-table-column>
93
- <el-table-column v-if="!readonly" :label="'附件上传' | $lc">
94
- <slot slot="header" slot-scope="scope" name="upload-header" :column="scope.column"
95
- >{{ '附件上传' | $lc }}
96
- </slot>
97
- <slot slot-scope="{ row, $index }" name="upload" :row="row" :[indexK]="$index">
27
+
98
28
  <Upload
99
- :ref="'upload' + $index"
29
+ :ref="'upload' + index"
100
30
  class="n20-upload-table-up"
101
- :file-name="row | fileName(keys.url)"
102
- :data="row['_fileData'] || fileData"
31
+ :file-name="''"
103
32
  :msg-type="null"
104
33
  :show-clear="false"
105
34
  :action="apiPrefix ? apiPrefix + action : action"
106
35
  :multiple="multiple"
107
36
  :headers="headers"
108
- :disabled="!row[keys.type] || row._typeDisabled"
109
- :accept="row[keys.type] | acceptFilter(typeOptions, fileAccept)"
110
- :size="row[keys.type] | sizeFilter(typeOptions, fileSize)"
111
- :http-request="uploadHttpRequest ? (options) => uploadHttpRequest(options, row) : undefined"
112
- :before-upload="(file) => beforeUploadFn(file, row)"
113
- :on-progress="(event) => onProgressFn(event, row)"
114
- :on-success="
115
- (response, file, fileList) =>
116
- multiple
117
- ? MultipleSUccessFn(response, file, fileList, row)
118
- : onSuccessFn(response, file, fileList, row)
37
+ :disabled="readonly || group.item.disabled"
38
+ :data="buildTypeUploadData(group.item)"
39
+ :accept="group.item.accept || fileAccept"
40
+ :size="group.item.size || fileSize"
41
+ :http-request="
42
+ uploadHttpRequest ? (options) => uploadHttpRequest(options, buildUploadRow(group.item)) : undefined
119
43
  "
120
- :on-error="(err, file, fileList) => errorFn(err, file, fileList, row, $index)"
121
- />
122
- </slot>
123
- </el-table-column>
124
- <slot name="slotCol"></slot>
125
- <el-table-column v-if="!hideTime" :label="'上传时间' | $lc" :prop="keys.time" sortable width="170" align="left">
126
- <slot slot="header" slot-scope="scope" name="time-header" :column="scope.column">{{ '上传时间' | $lc }}</slot>
127
- <slot slot-scope="{ row }" name="time" :row="row">{{ row[keys.time] }}</slot>
128
- </el-table-column>
129
- <el-table-column v-if="!hideUser" :label="'上传人' | $lc" :prop="keys.user" width="100">
130
- <slot slot="header" slot-scope="scope" name="user-header" :column="scope.column">{{ '上传人' | $lc }}</slot>
131
- <slot slot-scope="{ row }" name="user" :row="row">{{ row[keys.user] }}</slot>
132
- </el-table-column>
133
- </template>
134
- <el-table-column v-if="!readonly" :label="'上传进度' | $lc" width="220">
135
- <template slot="header" slot-scope="scope">
136
- <slot name="percent-header" :column="scope.column">{{ '上传进度' | $lc }}</slot>
137
- </template>
138
- <template slot-scope="{ row, $index }">
139
- <slot name="percent" :row="row" :[indexK]="$index">
140
- <div v-if="row['_percent'] >= 0 && !readonly" class="flex-box flex-v">
141
- <el-progress
142
- class="n20-upload-table-progress"
143
- :percentage="row['_percent']"
144
- :status="row['_status']"
145
- text-color=""
146
- style="width: 140px"
147
- />
148
- <el-button
149
- v-if="row['_status'] === 'exception'"
150
- type="text"
151
- size="mini"
152
- style="width: 60px"
153
- @click="anewSubmitFn(row, $index)"
154
- >{{ '重新上传' | $lc }}
155
- </el-button>
156
- <el-button
157
- v-else-if="row['_percent'] >= 0 && row['_percent'] < 100"
158
- type="text"
159
- size="mini"
160
- style="width: 60px"
161
- @click="abortFn(row, $index)"
162
- >{{ '取消' | $lc }}
163
- </el-button>
44
+ :before-upload="(file) => beforeUploadFn(file, buildUploadRow(group.item))"
45
+ :on-progress="(event) => onProgressFn(event, group.typeValue)"
46
+ :on-success="(response, file, fileList) => onTypeUploadSuccess(response, file, fileList, group.item)"
47
+ :on-error="(err, file, fileList) => onTypeUploadError(err, file, fileList, group.item, index)"
48
+ >
49
+ <el-button slot="trigger" type="text" class="upload-trigger">去上传</el-button>
50
+ </Upload>
51
+ </div>
52
+
53
+ <div v-if="group.rows.length" class="file-cards">
54
+ <div v-for="(row, rowIndex) in group.rows" :key="`${group.typeValue}-${rowIndex}`" class="file-card">
55
+ <div class="file-info">
56
+ <img v-if="matchFileExtension(row[keys.name], ['word', 'doc', 'docx'])" src="./46.svg" class="m-r-s" />
57
+ <img v-if="matchFileExtension(row[keys.name], ['xlsx', 'xls'])" src="./60.svg" class="m-r-s" />
58
+ <img v-if="matchFileExtension(row[keys.name], ['pdf'])" src="./37.svg" class="m-r-s" />
59
+ <div>
60
+ <div class="file-name" :title="row[keys.name]">{{ row[keys.name] }}</div>
61
+ <div class="file-meta">
62
+ <span class="m-r-m">{{ row[keys.user] || '-' }}</span>
63
+ <span>{{ row[keys.time] || '-' }}</span>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="file-actions">
68
+ <el-button type="text" icon="el-icon-view" :disabled="!row[keys.url]" @click="seeFile(row)" />
69
+ <el-button type="text" icon="v3-icon-download" :disabled="!row[keys.url]" @click="downFile(row)" />
70
+ <el-button v-if="!readonly" type="text" icon="v3-icon-delete" @click="deleteSingleRow(row)" />
71
+ </div>
164
72
  </div>
165
- </slot>
166
- </template>
167
- </el-table-column>
168
- <el-table-column :label="'操作' | $lc" align="center" width="90" fixed="right">
169
- <slot slot="header" slot-scope="scope" name="handle-header" :column="scope.column">{{ '操作' | $lc }}</slot>
170
- <slot slot-scope="{ row }" name="handle" :row="row">
171
- <el-button type="text" icon="el-icon-view" :disabled="!row[keys.url]" @click="seeFile(row)" />
172
- <el-button
173
- v-if="getOfficeStatus && openAI"
174
- v-title="'ai识别'"
175
- type="text"
176
- icon="n20-icon-query"
177
- :disabled="!row[keys.url]"
178
- @click="AiFn(row)"
179
- />
180
- <el-button
181
- v-if="readonly"
182
- type="text"
183
- icon="el-icon-download"
184
- :disabled="!row[keys.url]"
185
- @click="downFile(row)"
186
- />
187
- </slot>
188
- </el-table-column>
189
- </el-table> -->
190
- <div class="flex-box flex-v flex-lr">
191
- <div class="title">
192
- 附件上传
193
- <span>(带 * 为必要上传的附件)</span>
194
- </div>
195
- </div>
196
- <div class="file-list">
197
- <div v-for="(item, index) in typeOptions" :key="index" class="file-item">
198
- <div class="type-title">
199
- <div class="flex-box flex-v">
200
- <span class="m-r-s">*</span>
201
- <span>{{ index + 1 }}、</span>
202
- <span>{{ item.label }}</span>
203
- <img src="./ysc.svg" width="14px" class="m-l-s" />
204
73
  </div>
205
- <Upload
206
- :ref="'upload' + index"
207
- class="n20-upload-table-up"
208
- :msg-type="null"
209
- :show-clear="false"
210
- :action="apiPrefix ? apiPrefix + action : action"
211
- :multiple="multiple"
212
- :headers="headers"
213
- :data="fileData"
214
- :http-request="uploadHttpRequest ? (options) => uploadHttpRequest(options, row) : undefined"
215
- :on-success="
216
- (response, file, fileList) =>
217
- multiple ? MultipleSUccessFn(response, file, fileList, row) : onSuccessFn(response, file, fileList, row)
218
- "
219
- :on-error="(err, file, fileList) => errorFn(err, file, fileList, row, $index)"
220
- >
221
- <el-button slot="trigger" type="text" size="small">去上传</el-button>
222
- </Upload>
223
74
  </div>
224
75
  </div>
225
76
  </div>
@@ -254,73 +105,20 @@
254
105
  <i class="el-icon-s-release" style="font-size: 60px; color: #999"></i>
255
106
  <span style="margin-top: 16px">
256
107
  {{ '不支持在线预览,请' | $lc
257
- }}<el-link type="primary" class="color-primary" @click="downFile(seeRow)">{{ '下载' | $lc }}</el-link
258
- >{{ '到本地查看' | $lc }}
108
+ }}<el-link type="primary" class="color-primary" @click="downFile(seeRow)">{{ '下载' | $lc }}</el-link>
109
+ {{ '到本地查看' | $lc }}
259
110
  </span>
260
111
  </div>
261
112
  </component>
262
113
  </div>
263
114
  </Dialog>
264
- <Dialog
265
- v-drag
266
- :title="'附件批量上传' | $lc"
267
- :visible.sync="visibleBatch"
268
- top="5vh"
269
- width="692px"
270
- :close-on-click-modal="false"
271
- :destroy-on-open="true"
272
- >
273
- <el-select v-model="bathType" class="m-b-s" clearable @change="handleBathChange">
274
- <el-option
275
- v-for="item in typeOptions"
276
- :key="item.type"
277
- :disabled="item.disabled"
278
- :value="item.type"
279
- :label="item.label"
280
- />
281
- </el-select>
282
- <Upload
283
- v-if="bathType"
284
- ref="upload-batch"
285
- class="n20-upload-drag"
286
- :msg-type="null"
287
- :show-clear="false"
288
- :drag="true"
289
- :auto-upload="false"
290
- :multiple="true"
291
- :show-file-list="true"
292
- :action="apiPrefix ? apiPrefix + action : action"
293
- :data="fileData"
294
- :headers="headers"
295
- :accept="fileAccept"
296
- :size="fileSize"
297
- :http-request="uploadHttpRequestBath"
298
- :before-upload="(file) => batchBeforeUploadFn(file)"
299
- :on-remove="batchRemove"
300
- :on-success="batchSuccess"
301
- :on-error="batchError"
302
- >
303
- <template slot="trigger">
304
- <i class="drag-icon n20-icon-shangchuan"></i>
305
- <span class="drag-text">{{ '点击或将文件拖拽到这里上传' | $lc }}</span>
306
- <span class="drag-tip">{{ fileAcceptTip }}</span>
307
- </template>
308
- </Upload>
309
- <div class="dialog-footer">
310
- <el-button type="primary" @click="batchUploadFn">{{ '确认' | $lc }}</el-button>
311
- <el-button plain @click="visibleBatch = false">{{ '取消' | $lc }}</el-button>
312
- </div>
313
- </Dialog>
314
115
  </div>
315
116
  </template>
316
117
 
317
118
  <script>
318
- import getJsonc from '../../assets/getJsonc.js'
319
119
  import _axios from 'axios'
320
120
  import dayjs from 'dayjs'
321
121
  import XEUtils from 'xe-utils'
322
-
323
- import 'viewerjs/dist/viewer.css'
324
122
  import auth from '../../utils/auth.js'
325
123
  import axios from '../../utils/axios.js'
326
124
  import { $lc } from '../../utils/i18n/index'
@@ -328,10 +126,13 @@ import importG from '../../utils/importGlobal.js'
328
126
  import Dialog from '../Dialog/index.vue'
329
127
  import Upload from '../Upload/index.vue'
330
128
 
129
+ import 'viewerjs/dist/viewer.css'
130
+
331
131
  const ViewerImg = async function () {
332
132
  let { component } = await importG('v-viewer', () => import(/*webpackChunkName: "v-viewer"*/ 'v-viewer'))
333
133
  return component
334
134
  }
135
+
335
136
  const keysDefault = {
336
137
  rowKey: 'id',
337
138
  type: 'type',
@@ -340,11 +141,6 @@ const keysDefault = {
340
141
  time: 'time',
341
142
  user: 'user'
342
143
  }
343
- const typeOptionsDefault = [
344
- { type: '001', label: $lc('信贷合同') },
345
- { type: '002', label: $lc('贴现合同') },
346
- { type: '003', label: $lc('其他合同') }
347
- ]
348
144
 
349
145
  export default {
350
146
  name: 'FileUploadTableV3',
@@ -353,36 +149,7 @@ export default {
353
149
  Dialog,
354
150
  ViewerImg
355
151
  },
356
- filters: {
357
- typeFiter(type, typeOptions) {
358
- return typeOptions.find((c) => c.type === type)?.label || type
359
- },
360
- acceptFilter(type, typeOptions, fileAccept) {
361
- return typeOptions.find((c) => c.type === type)?.accept || fileAccept
362
- },
363
- sizeFilter(type, typeOptions, fileSize) {
364
- return typeOptions.find((c) => c.type === type)?.size || fileSize
365
- },
366
- fileName(row, urlK) {
367
- if (row['_name']) {
368
- return row['_name']
369
- } else if (row[urlK]) {
370
- let urlArr = row[urlK].split('/')
371
- let _n = urlArr[urlArr.length - 1]
372
- return _n ? decodeURI(_n) : undefined
373
- }
374
- return undefined
375
- }
376
- },
377
152
  props: {
378
- AIOptions: {
379
- type: Object,
380
- default: () => ({})
381
- },
382
- openAI: {
383
- type: Boolean,
384
- default: false
385
- },
386
153
  readonly: {
387
154
  type: Boolean,
388
155
  default: false
@@ -391,10 +158,6 @@ export default {
391
158
  type: String,
392
159
  default: '/bems/1.0/attach'
393
160
  },
394
- batchPrintMethod: {
395
- type: Function,
396
- default: undefined
397
- },
398
161
  headers: {
399
162
  type: Object,
400
163
  default: undefined
@@ -411,26 +174,14 @@ export default {
411
174
  type: RegExp,
412
175
  default: () => /\.(jpg|png|gif|svg|pdf)$/i
413
176
  },
414
- height: {
415
- type: [String, Number],
416
- default: '300px'
417
- },
418
177
  tableData: {
419
178
  type: Array,
420
179
  default: () => []
421
180
  },
422
- dataPorp: {
181
+ dataProp: {
423
182
  type: Object,
424
183
  default: () => ({})
425
184
  },
426
- showBatchUpload: {
427
- type: Boolean,
428
- default: false
429
- },
430
- showBatchPrint: {
431
- type: Boolean,
432
- default: false
433
- },
434
185
  uploadHttpRequest: {
435
186
  type: Function,
436
187
  default: undefined
@@ -438,21 +189,13 @@ export default {
438
189
  getFileMethod: {
439
190
  type: Function,
440
191
  default: undefined
441
- },
442
- hideUser: {
443
- type: Boolean,
444
- default: false
445
- },
446
- hideTime: {
447
- type: Boolean,
448
- default: false
449
192
  }
450
193
  },
451
194
  data() {
452
195
  this.viewerOptions = {
453
196
  debug: true,
454
197
  inline: true,
455
- scalable: false, // 不显示水平(垂直)翻转
198
+ scalable: false,
456
199
  navbar: false,
457
200
  button: false,
458
201
  title: true,
@@ -472,247 +215,317 @@ export default {
472
215
  }
473
216
 
474
217
  return {
475
- // 是否仅平铺必选附件
476
- fileRequired: true,
477
- // 必传附件
218
+ tabs: [
219
+ { key: 'apply', label: '申请附件' },
220
+ { key: 'base', label: '基础档案' },
221
+ { key: 'credit', label: '征信报告' },
222
+ { key: 'source', label: '原始附件' }
223
+ ],
224
+ activeTab: 0,
225
+ showAllTypes: true,
478
226
  requiredTypes: [],
479
- fileText: '',
480
- bathType: '',
481
227
  indexK: '$index',
482
228
  imgType: /\.(jpg|png|gif|svg)$/i,
483
- selectionList: [],
484
- currFileList: [],
485
229
  visibleP: false,
486
230
  visiblePv: false,
487
- visibleBatch: false,
488
231
  previewUrl: undefined,
489
232
  previewName: undefined,
490
233
  previewSameOrg: false,
491
234
  seeRow: {},
492
- officeStatus: false
235
+ uploadProgressMap: {}
493
236
  }
494
237
  },
495
238
  computed: {
496
239
  fileAccept() {
497
- return this.dataPorp.fileAccept || undefined
240
+ return this.dataProp.fileAccept || undefined
498
241
  },
499
242
  fileSize() {
500
- return this.dataPorp.fileSize || undefined
501
- },
502
- fileAcceptTip() {
503
- return this.dataPorp.fileAcceptTip || $lc('支持扩展名:.rar .zip .doc .docx .pdf .jpg...')
243
+ return this.dataProp.fileSize || undefined
504
244
  },
505
245
  fileData() {
506
- return this.dataPorp.fileData || undefined
246
+ return this.dataProp.fileData || undefined
507
247
  },
508
248
  typeOptions() {
509
- return this.dataPorp.typeOptions || typeOptionsDefault
249
+ return this.dataProp.typeOptions
510
250
  },
511
251
  keys() {
512
- return this.dataPorp.keys || keysDefault
252
+ return this.dataProp.keys || keysDefault
513
253
  },
514
254
  multiple() {
515
- return this.dataPorp.multiple ?? true
255
+ return this.dataProp.multiple ?? true
256
+ },
257
+ uploadedCount() {
258
+ return this.tableData.filter((row) => row[this.keys.url] || row._name).length
259
+ },
260
+ typeRowsMap() {
261
+ const map = Object.create(null)
262
+ const typeKey = this.keys.type
263
+ const urlKey = this.keys.url
264
+ ;(this.tableData || []).forEach((row) => {
265
+ const typeValue = row[typeKey]
266
+ if (!typeValue || (!row[urlKey] && !row._name)) return
267
+ if (!map[typeValue]) {
268
+ map[typeValue] = []
269
+ }
270
+ map[typeValue].push(row)
271
+ })
272
+ return map
273
+ },
274
+ displayTypeOptions() {
275
+ if (this.showAllTypes) return this.typeOptions
276
+ return (this.typeOptions || []).filter((item) => {
277
+ const typeValue = item[this.typeMap.value]
278
+ return (this.typeRowsMap[typeValue] || []).length > 0
279
+ })
280
+ },
281
+ displayTypeGroups() {
282
+ return (this.displayTypeOptions || []).map((item) => {
283
+ const typeValue = item[this.typeMap.value]
284
+ return {
285
+ item,
286
+ typeValue,
287
+ rows: this.typeRowsMap[typeValue] || []
288
+ }
289
+ })
290
+ },
291
+ typeMap() {
292
+ return (
293
+ this.dataProp.typeMap || {
294
+ label: 'attname',
295
+ value: 'attno'
296
+ }
297
+ )
516
298
  }
517
299
  },
518
300
  watch: {
519
- dataPorp: {
520
- handler() {
521
- this.requiredTypes = this.dataPorp.typeOptions
522
- .filter((item) => {
523
- return item.required === 1
524
- })
525
- .map((item) => {
526
- return item.attno
527
- })
528
- if (this.dataPorp.typeOptions.length === 0) {
529
- return false
530
- }
531
- this.setTableData()
301
+ dataProp: {
302
+ /**
303
+ * 根据附件类型配置初始化必传项。
304
+ * @param {Object} val 配置对象。
305
+ * @returns {void}
306
+ */
307
+ handler(val) {
308
+ const options = (val && val.typeOptions) || []
309
+ this.requiredTypes = options.filter((item) => item.required === 1).map((item) => item.attno || item.type)
532
310
  },
533
311
  immediate: true,
534
312
  deep: true
535
313
  }
536
314
  },
537
315
  mounted() {
538
- this.getConfiguration()
316
+ this.getFileTypes()
539
317
  },
540
318
  methods: {
541
- // ai识别
542
- async AiFn(row) {
543
- if (!this.AIOptions.bussType) {
544
- this.$message.error('请先配置bussType')
545
- return false
546
- }
547
- const { data, code } = await axios.post(
548
- this.apiPrefix ? `${this.apiPrefix}neams/eamsbaserecord/aiAttaFile` : `/neams/eamsbaserecord/aiAttaFile`,
549
- {
550
- beid: row.beid,
551
- bussType: this.AIOptions.bussType,
552
- extendPrompt: ''
553
- }
554
- )
555
- if (code === 200) {
556
- this.$emit('aiFn', data)
319
+ async getFileTypes() {
320
+ if (this.dataProp?.bussValues?.length) {
321
+ const { code, data } = await this.$axios.post(
322
+ `/neams/eamsattachfile/getByBussValues`,
323
+ this.dataProp?.bussValues
324
+ )
325
+ if (code !== 200) return
326
+ const { label, value } = this.typeMap
327
+ this.dataProp.typeOptions = data?.map((item) => {
328
+ return {
329
+ ...item,
330
+ [label]: item.attname,
331
+ [value]: item.attno
332
+ }
333
+ })
557
334
  }
558
335
  },
559
- // 平铺附件
560
- setTableData() {
561
- if (this.tableData.length === 0 && !this.readonly) {
562
- this.dataPorp.typeOptions
563
- .filter((item) => {
564
- if (this.fileRequired) {
565
- return item.required === 1
566
- } else {
567
- return item.required === 1 || item.required === 0
568
- }
569
- })
570
- .forEach((item) => {
571
- let obj
572
- obj = {
573
- id: 'n' + Math.random(),
574
- name: ''
575
- }
576
- obj[this.keys.type] = item.attno
577
- obj[this.keys.time] = ''
578
- obj[this.keys.user] = JSON.parse(sessionStorage.getItem('userInfo')).uname
579
-
580
- console.log(obj)
581
- this.tableData.push(obj)
582
- })
336
+ /**
337
+ * 从文件名中提取扩展名并统一为小写。
338
+ * @param {string} fileName 文件名。
339
+ * @returns {string} 不含点号的小写扩展名,取不到时返回空字符串。
340
+ */
341
+ getFileExtension(fileName) {
342
+ if (!fileName || typeof fileName !== 'string') return ''
343
+ const trimmedName = fileName.trim()
344
+ const lastDotIndex = trimmedName.lastIndexOf('.')
345
+ if (lastDotIndex < 0 || lastDotIndex === trimmedName.length - 1) return ''
346
+ return trimmedName.slice(lastDotIndex + 1).toLowerCase()
347
+ },
348
+ /**
349
+ * 判断文件扩展名是否命中指定后缀列表(大小写不敏感)。
350
+ * @param {string} fileName 文件名。
351
+ * @param {Array<string>} extList 扩展名白名单(不带点号)。
352
+ * @returns {boolean} 命中返回 true,否则返回 false。
353
+ */
354
+ matchFileExtension(fileName, extList = []) {
355
+ const extension = this.getFileExtension(fileName)
356
+ if (!extension || !Array.isArray(extList) || !extList.length) return false
357
+ return extList.some((ext) => extension === String(ext).toLowerCase())
358
+ },
359
+ /**
360
+ * 判断当前类型是否为必传。
361
+ * @param {Object} item 类型项。
362
+ * @returns {boolean} 是否必传。
363
+ */
364
+ isRequiredType(item) {
365
+ return this.requiredTypes.includes(item.attno || item.type)
366
+ },
367
+ /**
368
+ * 获取某附件类型的所有文件行。
369
+ * @param {string} type 附件类型值。
370
+ * @returns {Array<Object>} 该类型对应的文件列表。
371
+ */
372
+ getRowsByType(type) {
373
+ return this.typeRowsMap[type] || []
374
+ },
375
+ /**
376
+ * 生成上传时使用的虚拟行对象。
377
+ * @param {Object} item 附件类型。
378
+ * @returns {Object} 供上传回调处理的行对象。
379
+ */
380
+ buildUploadRow(item) {
381
+ const row = {
382
+ [this.keys.type]: item.type || item.attno,
383
+ [this.keys.user]: JSON.parse(sessionStorage.getItem('userInfo') || '{}').uname || ''
583
384
  }
584
- },
585
- getConfiguration() {
586
- getJsonc('portal/server-config.jsonc', null, true)
587
- .then(({ loginConf, fileRequired }) => {
588
- this.fileText = loginConf?.sLogan?.fileText
589
- this.fileRequired = fileRequired ?? true
590
- })
591
- .catch(() => {
592
- this.fileText = ''
593
- this.fileRequired = true
594
- })
595
- },
596
- async getOfficeStatus() {
597
- const { data } = await axios.get(
598
- this.apiPrefix
599
- ? `${this.apiPrefix}/neams/eamsBaseFile/getOfficeIsEnable`
600
- : `/neams/eamsBaseFile/getOfficeIsEnable`
601
- )
602
- this.officeStatus = data
603
- },
604
- handleBathUpload() {
605
- this.visibleBatch = true
606
- },
607
- async batchPrint() {
608
- if (!this.selectionList.length) {
609
- return this.$message({
610
- message: $lc(`请先勾选要打印的数据!`),
611
- type: 'warning',
612
- showClose: true
613
- })
385
+ return row
386
+ },
387
+ /**
388
+ * 组装当前类型上传参数。
389
+ * @param {Object} item 附件类型。
390
+ * @returns {Object|undefined} 上传参数。
391
+ */
392
+ buildTypeUploadData(item) {
393
+ const data = XEUtils.clone(this.fileData || {}, true)
394
+ if (!data || !data.data) return data
395
+ try {
396
+ const dto = JSON.parse(data.data)
397
+ dto[this.keys.type] = item.type || item.attno
398
+ data.data = JSON.stringify(dto)
399
+ } catch (e) {
400
+ return data
614
401
  }
615
- if (this.batchPrintMethod) {
616
- return this.batchPrintMethod(this.selectionList)
402
+ return data
403
+ },
404
+ /**
405
+ * 处理上传进度。
406
+ * @param {Object} event 上传进度事件。
407
+ * @param {string} type 附件类型。
408
+ * @returns {void}
409
+ */
410
+ onProgressFn(event, type) {
411
+ const percent = event && event.percent ? Math.round(event.percent) : 0
412
+ this.$set(this.uploadProgressMap, type, percent <= 99 ? percent : 99)
413
+ },
414
+ /**
415
+ * 上传前拦截,透传外部回调。
416
+ * @param {File} file 文件对象。
417
+ * @param {Object} row 虚拟行对象。
418
+ * @returns {Promise|boolean|undefined} 上传前处理结果。
419
+ */
420
+ beforeUploadFn(file, row) {
421
+ let bu = this.$listeners['before-upload'] || this.$listeners.beforeUpload
422
+ if (bu) return bu(file, row)
423
+ return undefined
424
+ },
425
+ /**
426
+ * 类型维度上传成功处理。
427
+ * @param {Object} response 上传响应。
428
+ * @param {File} file 上传文件。
429
+ * @param {Array<Object>} fileList 当前文件列表。
430
+ * @param {Object} item 类型配置。
431
+ * @returns {void}
432
+ */
433
+ onTypeUploadSuccess(response, file, fileList, item) {
434
+ const row = {
435
+ id: 'n' + Math.random(),
436
+ _name: file.name,
437
+ [this.keys.rowKey]: response.data,
438
+ [this.keys.type]: item.type || item.attno,
439
+ [this.keys.time]: dayjs().format('YYYY-MM-DD HH:mm:ss'),
440
+ [this.keys.user]: JSON.parse(sessionStorage.getItem('userInfo') || '{}').uname || '',
441
+ [this.keys.url]: response.data,
442
+ [this.keys.name]: file.name,
443
+ _percent: response.code >= 900 || response.code === -1 ? 99 : 100,
444
+ _status: response.code >= 900 || response.code === -1 ? 'error' : 'success',
445
+ _typeDisabled: true
617
446
  }
618
- const { code, data } = await axios.post(
619
- this.apiPrefix
620
- ? this.apiPrefix + `/neams/eamsbaserecord/mergeDownladPdf`
621
- : `/neams/eamsbaserecord/mergeDownladPdf`,
622
- this.selectionList.map((res) => res.beid) || [],
623
- {
624
- responseType: 'blob'
625
- }
626
- )
627
- if (code === 200) {
628
- let aDom = document.createElement('a')
629
- aDom.href = data
630
- aDom.download = '批量附件.pdf'
631
- aDom.click()
632
447
 
448
+ this.tableData.unshift(row)
449
+ this.$set(this.uploadProgressMap, item.type || item.attno, 100)
450
+ this.$emit('on-success', file, row)
451
+
452
+ if (this.multiple && fileList && fileList.length > 1) {
633
453
  this.$nextTick(() => {
634
- aDom = undefined
635
- data && URL.revokeObjectURL(data)
454
+ const uploadRef =
455
+ this.$refs['upload' + this.typeOptions.findIndex((x) => (x.type || x.attno) === (item.type || item.attno))]
456
+ if (uploadRef) {
457
+ uploadRef.fileList = []
458
+ }
636
459
  })
637
460
  }
638
461
  },
639
- batchBeforeUploadFn(file) {
640
- let bu = this.$listeners['before-upload'] || this.$listeners['beforeUpload']
641
- if (bu) return bu(file)
642
- },
643
- handleBathChange() {
644
- let dto = JSON.parse(this.dataPorp.fileData.data)
645
- dto[this.keys.type] = this.bathType
646
- this.fileData.data = JSON.stringify(dto)
647
- },
648
- batchUploadFn() {
649
- let $uploadwrap = this.$refs['upload-batch']
650
- let fileList = $uploadwrap.fileList
651
- if (fileList.length !== 0 && fileList.every((f) => f.status === 'success')) {
652
- this.visibleBatch = false
653
- }
654
- $uploadwrap.submit()
655
- },
656
- batchSuccess(response, file, fileList) {
657
- let row = { ...this.dataPorp.keys }
658
- this.tableData.splice(0, 0, row)
659
- this.$nextTick(() => {
660
- this.onSuccessFn(response, file, fileList, row)
661
- })
662
-
663
- if (fileList.every((f) => f.status === 'success')) {
664
- this.visibleBatch = false
665
- }
666
- },
667
- batchError(err, file, fileList) {
668
- if (!fileList.some((f) => f.uid === file.uid)) {
669
- let list = [...fileList]
462
+ /**
463
+ * 类型维度上传失败处理。
464
+ * @param {Error|Object} err 错误对象。
465
+ * @param {File} file 上传文件。
466
+ * @param {Array<Object>} fileList 当前文件列表。
467
+ * @param {Object} item 类型配置。
468
+ * @param {number} index 当前索引。
469
+ * @returns {void}
470
+ */
471
+ onTypeUploadError(err, file, fileList, item, index) {
472
+ this.$set(this.uploadProgressMap, item.type || item.attno, 0)
473
+ const clUpload = this.$refs['upload' + index]
474
+ if (clUpload) {
670
475
  file.status = 'ready'
671
- if (err.msg) {
672
- file.OrgName || (file.OrgName = file.name)
673
- file.name = file.OrgName + ` *(${err.msg})`
674
- }
675
-
676
- list.push(file)
677
- let $uploadwrap = this.$refs['upload-batch']
678
- $uploadwrap.fileList = list
476
+ clUpload.fileList = [file]
679
477
  }
680
- },
681
- batchRemove(file, fileList) {
682
- let list = fileList.filter((f) => f.uid !== file.uid)
683
-
684
- let $uploadwrap = this.$refs['upload-batch']
685
- $uploadwrap.fileList = list
686
-
687
- this.$emit('on-remove', file)
688
- },
689
- addRow() {
690
- this.$emit('add-row')
691
- },
478
+ this.$emit('on-error', file, item)
479
+ },
480
+ /**
481
+ * 删除单条附件记录。
482
+ * @param {Object} row 文件行数据。
483
+ * @returns {void}
484
+ */
485
+ deleteSingleRow(row) {
486
+ const index = this.tableData.findIndex((item) => item === row)
487
+ if (index > -1) this.tableData.splice(index, 1)
488
+ this.$emit('delete-rows', [row])
489
+ },
490
+ /**
491
+ * 切换展开/收起未上传类型。
492
+ * @returns {void}
493
+ */
494
+ toggleCollapse() {
495
+ this.showAllTypes = !this.showAllTypes
496
+ },
497
+ /**
498
+ * 计算类型状态图标。
499
+ * @param {string} type 附件类型值。
500
+ * @returns {string} success/warning/default。
501
+ */
502
+ getTypeStatus(type) {
503
+ const rows = this.getRowsByType(type)
504
+ if (!rows.length) return 'default'
505
+ if (rows.some((row) => row._status === 'error' || row._status === 'exception')) return 'warning'
506
+ return 'success'
507
+ },
508
+ /**
509
+ * 批量下载全部已上传附件。
510
+ * @returns {void}
511
+ */
692
512
  downRows() {
693
- if (!this.selectionList.length) {
513
+ const list = this.tableData.filter((item) => item[this.keys.url] || item._name)
514
+ if (!list.length) {
694
515
  return this.$message({
695
- message: $lc(`请先勾选要下载的数据!`),
516
+ message: $lc('请先上传要下载的数据!'),
696
517
  type: 'warning',
697
518
  showClose: true
698
519
  })
699
520
  }
700
- this.$emit(
701
- 'down-rows',
702
- this.selectionList.filter((item) => item[this.keys.url] || item._name)
703
- )
704
- },
705
- deleteRows() {
706
- if (!this.selectionList.length) {
707
- return this.$message({
708
- message: $lc('请先勾选要删除的数据!'),
709
- type: 'warning',
710
- showClose: true
711
- })
712
- }
713
- this.$emit('delete-rows', this.selectionList)
714
- },
715
-
521
+ this.$emit('down-rows', list)
522
+ },
523
+ /**
524
+ * 获取附件下载或预览信息。
525
+ * @param {Object} row 文件行数据。
526
+ * @param {'preview'} [type] 读取类型。
527
+ * @returns {Promise<Object>} 文件信息。
528
+ */
716
529
  async getFileInfo(row, type) {
717
530
  if (this.getFileMethod) {
718
531
  return this.getFileMethod(row, type)
@@ -721,36 +534,39 @@ export default {
721
534
  this.seePrefix && (_url = this.seePrefix + _url)
722
535
  if (_url) {
723
536
  if (type === 'preview') {
724
- // 预览
725
537
  return {
726
538
  url: `/api/onlinePreview?beid=${row[this.keys.rowKey]}`,
727
539
  name: row[this.keys.name] + '.html',
728
540
  sameOrg: this.seeTypes.test(row[this.keys.name])
729
541
  }
730
- } else {
731
- // 下载
732
- let blob = await axios.get(
733
- this.apiPrefix
734
- ? this.apiPrefix + `/neams/eamsbaserecord/download/${row[this.keys.rowKey]}`
735
- : `/neams/eamsbaserecord/download/${row[this.keys.rowKey]}`,
736
- null,
737
- {
738
- responseType: 'blob'
739
- }
740
- )
741
- let name = row[this.keys.name] || blob.name
742
- return {
743
- url: URL.createObjectURL(blob),
744
- name: name,
745
- sameOrg: this.seeTypes.test(name)
542
+ }
543
+ let blob = await axios.get(
544
+ this.apiPrefix
545
+ ? this.apiPrefix + `/neams/eamsbaserecord/download/${row[this.keys.rowKey]}`
546
+ : `/neams/eamsbaserecord/download/${row[this.keys.rowKey]}`,
547
+ null,
548
+ {
549
+ responseType: 'blob'
746
550
  }
551
+ )
552
+ let name = row[this.keys.name] || blob.name
553
+ return {
554
+ url: URL.createObjectURL(blob),
555
+ name: name,
556
+ sameOrg: this.seeTypes.test(name)
747
557
  }
748
- } else {
749
- return {}
750
558
  }
559
+ return {}
751
560
  },
561
+ /**
562
+ * 下载单个附件。
563
+ * @param {Object} row 文件行数据。
564
+ * @returns {Promise<void>} 下载执行结果。
565
+ */
752
566
  async downFile(row) {
753
- let { url, name } = await this.getFileInfo(row)
567
+ let info = await this.getFileInfo(row)
568
+ let url = info.url
569
+ let name = info.name
754
570
  if (url) {
755
571
  let aDom = document.createElement('a')
756
572
  aDom.href = url
@@ -763,8 +579,16 @@ export default {
763
579
  })
764
580
  }
765
581
  },
582
+ /**
583
+ * 预览单个附件。
584
+ * @param {Object} row 文件行数据。
585
+ * @returns {Promise<void>} 预览执行结果。
586
+ */
766
587
  async seeFile(row) {
767
- let { url, name, sameOrg } = await this.getFileInfo(row, 'preview')
588
+ let info = await this.getFileInfo(row, 'preview')
589
+ let url = info.url
590
+ let name = info.name
591
+ let sameOrg = info.sameOrg
768
592
  if (url) {
769
593
  this.previewSameOrg = sameOrg
770
594
  this.previewUrl = url
@@ -776,16 +600,28 @@ export default {
776
600
  this.seeRow = row
777
601
  }
778
602
  },
603
+ /**
604
+ * 预览上一条附件。
605
+ * @returns {void}
606
+ */
779
607
  preSee() {
780
608
  let i = this.tableData.findIndex((row) => row === this.seeRow)
781
609
  let row = this.tableData[i - 1] || this.tableData[this.tableData.length - 1]
782
610
  this.seeFile(row)
783
611
  },
612
+ /**
613
+ * 预览下一条附件。
614
+ * @returns {void}
615
+ */
784
616
  nextSee() {
785
617
  let i = this.tableData.findIndex((row) => row === this.seeRow)
786
618
  let row = this.tableData[i + 1] || this.tableData[0]
787
619
  this.seeFile(row)
788
620
  },
621
+ /**
622
+ * 关闭预览并释放资源。
623
+ * @returns {void}
624
+ */
789
625
  closeSee() {
790
626
  this.previewUrl && URL.revokeObjectURL(this.previewUrl)
791
627
  this.previewUrl = undefined
@@ -794,144 +630,211 @@ export default {
794
630
  this.visiblePv = false
795
631
  }, 300)
796
632
  },
633
+ /**
634
+ * 兜底上传请求实现(兼容外部不传 uploadHttpRequest)。
635
+ * @param {Object} opt 上传参数。
636
+ * @returns {Promise<any>} 上传结果。
637
+ */
797
638
  uploadHttpRequestBath(opt) {
798
- console.log(opt)
799
639
  if (this.uploadHttpRequest) {
800
640
  return this.uploadHttpRequest(opt)
801
- } else {
802
- let FD = new FormData()
803
- FD.append(opt.filename, opt.file)
804
-
805
- let data
806
- if (window._fileData) {
807
- data = window._fileData
808
- delete window._fileData
809
- } else {
810
- data = opt.data
641
+ }
642
+ let FD = new FormData()
643
+ FD.append(opt.filename, opt.file)
644
+
645
+ let data = opt.data
646
+ if (data) {
647
+ for (let k in data) {
648
+ FD.append(k, data[k])
811
649
  }
650
+ }
812
651
 
813
- if (data) {
814
- let dto = JSON.parse(data.data)
815
- dto[this.keys.type] = this.bathType
816
- data.data = JSON.stringify(dto)
817
- for (let k in data) {
818
- FD.append(k, data[k])
652
+ let abort
653
+ let Pro = axios.post(opt.action + '?r=' + Math.random(), FD, {
654
+ headers: Object.assign(auth.setHeaders(this.headers), { 'Content-Type': 'multipart/form-data' }),
655
+ loading: false,
656
+ timeout: 900000,
657
+ onUploadProgress: (arg) => {
658
+ if (opt.onProgress) {
659
+ arg.percent = arg.progress * 100
660
+ opt.onProgress(arg)
819
661
  }
662
+ },
663
+ cancelToken: new _axios.CancelToken((cancel) => {
664
+ abort = cancel
665
+ })
666
+ })
667
+ Pro.abort = abort
668
+ return Pro
669
+ }
670
+ }
671
+ }
672
+ </script>
673
+
674
+ <style lang="scss" scoped>
675
+ .file-upload-table-v3 {
676
+ width: 100%;
677
+
678
+ background: #fff;
679
+
680
+ .panel {
681
+ padding: 0 16px 16px;
682
+
683
+ .panel-top {
684
+ height: 40px;
685
+ display: flex;
686
+ align-items: center;
687
+ justify-content: space-between;
688
+ font-size: 14px;
689
+ .summary {
690
+ display: flex;
691
+ align-items: center;
692
+ color: #4e5969;
693
+
694
+ i {
695
+ margin-right: 4px;
820
696
  }
821
697
 
822
- let abort
823
- let Pro = axios.post(opt.action + '?r=' + Math.random(), FD, {
824
- headers: Object.assign(auth.setHeaders(this.headers), { 'Content-Type': 'multipart/form-data' }),
825
- loading: false,
826
- timeout: 900000,
827
- onUploadProgress: (arg) => {
828
- if (opt.onProgress) {
829
- arg.percent = arg.progress * 100
830
- opt.onProgress(arg)
831
- }
832
- },
833
- cancelToken: new _axios.CancelToken((cancel) => {
834
- abort = cancel
835
- })
836
- })
837
- Pro.abort = abort
838
- return Pro
698
+ .summary-action {
699
+ margin-left: 12px;
700
+ padding: 0;
701
+ }
839
702
  }
840
- },
841
- beforeUploadFn(file, row) {
842
- this.$set(row, '_percent', 0)
843
- this.$set(row, '_status', undefined)
844
703
 
845
- let bu = this.$listeners['before-upload'] || this.$listeners['beforeUpload']
846
- if (bu) return bu(file, row)
847
- },
848
- onProgressFn({ percent }, row) {
849
- this.$set(row, '_percent', percent <= 99 ? Math.round(percent) : 99)
850
- },
851
- onSuccessFn(response, file, fileList, row) {
852
- console.log(response, file, fileList)
853
- console.log(this.tableData)
854
- this.$emit('on-success', file, row)
855
- /* this.$set(row, '_typeDisabled', true)
856
- this.$set(row, '_name', file.name)
857
-
858
- this.$set(row, [this.keys.rowKey], response.data)
859
- this.$set(row, [this.keys.type], row[this.keys.type] || this.bathType)
860
- row[this.keys.time] = dayjs().format('YYYY-MM-DD HH:mm:ss')
861
- if (response.code >= 900 || response.code === -1) {
862
- this.$set(row, '_percent', 99)
863
- this.$set(row, '_status', 'error')
864
- } else {
865
- this.$set(row, '_percent', 100)
866
- this.$set(row, '_status', 'success')
704
+ .toggle-btn {
705
+ padding: 0;
706
+
707
+ i {
708
+ margin-left: 4px;
709
+ transition: transform 0.2s;
710
+ }
711
+
712
+ .is-reverse {
713
+ transform: rotate(180deg);
714
+ }
867
715
  }
868
- row[this.keys.url] = response.data
869
- row[this.keys.name] = file.name
716
+ }
717
+ .type-list {
718
+ min-height: 200px;
719
+ max-height: 680px;
720
+ overflow-y: auto;
721
+ .type-item {
722
+ margin-top: 8px;
870
723
 
871
- this.$emit('on-success', file, row) */
872
- },
873
- MultipleSUccessFn(response, file, fileList, row) {
874
- if (fileList.length > 1) {
875
- let deepRow = XEUtils.clone(row, true)
876
- this.$nextTick(() => {
877
- if (this.tableData.length && !this.tableData[0][this.keys.url]) {
878
- this.tableData.shift()
724
+ .type-row {
725
+ min-height: 46px;
726
+ background: #f7f8fa;
727
+ border-radius: 4px;
728
+ padding: 0 12px;
729
+ display: flex;
730
+ align-items: center;
731
+ justify-content: space-between;
732
+
733
+ .type-meta {
734
+ display: flex;
735
+ align-items: center;
736
+ color: #1d2129;
737
+ font-family: 'PingFang SC';
738
+ font-size: 14px;
739
+ font-style: normal;
740
+ font-weight: 500;
741
+ line-height: 22px;
742
+
743
+ .required {
744
+ color: #f53f3f;
745
+ margin-right: 2px;
746
+ }
747
+
748
+ .type-index {
749
+ margin-right: 2px;
750
+ }
751
+
752
+ .status-icon {
753
+ width: 16px;
754
+ height: 16px;
755
+ margin-left: 8px;
756
+ }
757
+ }
758
+
759
+ .upload-trigger {
760
+ font-size: 14px;
761
+ font-style: normal;
762
+ font-weight: 400;
763
+ line-height: 22px;
764
+ padding: 0;
765
+ color: #007aff;
879
766
  }
880
- })
881
- let _percent = 0
882
- let _status = undefined
883
- if (response.code >= 900 || response.code === -1) {
884
- _percent = 99
885
- _status = 'error'
886
- } else {
887
- _percent = 99
888
- _status = 'success'
889
767
  }
890
- setTimeout(() => {
891
- this.tableData.splice(0, 0, {
892
- _name: file.name,
893
- _percent,
894
- [this.keys.time]: dayjs().format('YYYY-MM-DD HH:mm:ss'),
895
- [this.keys.url]: response.data,
896
- [this.keys.name]: file.name,
897
- _status,
898
- _typeDisabled: true,
899
- [this.keys.user]: deepRow[this.keys.user],
900
- [this.keys.type]: deepRow[this.keys.type]
901
- })
902
- }, 100)
903
- } else {
904
- this.onSuccessFn(response, file, fileList, row)
905
- }
906
- },
907
- errorFn(err, file, fileList, row, $index) {
908
- this.$set(row, '_status', 'exception')
768
+ .type-row__is-success {
769
+ border-radius: 4px;
770
+ background-color: #e8fff3;
771
+ }
909
772
 
910
- let clUpload = this.$refs['upload' + $index]
911
- /* 重置文件状态,并加入文件队列 */
912
- file.status = 'ready'
913
- clUpload.fileList = [file]
773
+ .file-cards {
774
+ margin-top: 8px;
775
+ display: grid;
776
+ grid-template-columns: repeat(2, minmax(0, 1fr));
777
+ gap: 12px;
914
778
 
915
- this.$emit('on-error', file, row)
916
- },
917
- abortFn(row, $index) {
918
- this.$set(row, '_status', 'exception')
779
+ .file-card {
780
+ height: 50px;
781
+ background: #f7f8fa;
782
+ border-radius: 2px;
783
+ padding: 4px 8px;
784
+ display: flex;
785
+ align-items: center;
786
+ justify-content: space-between;
919
787
 
920
- let clUpload = this.$refs['upload' + $index]
921
- let elUpload = clUpload.$refs['upload']
788
+ .file-info {
789
+ flex: 1;
790
+ display: flex;
791
+ align-items: center;
792
+ min-width: 0;
793
+ cursor: pointer;
922
794
 
923
- // let uploadFiles = elUpload.uploadFiles
924
- // /* 重置文件状态,并加入文件队列 */
925
- // let file = uploadFiles[uploadFiles.length - 1]
926
- // file.status = 'ready'
927
- // clUpload.fileList = [file]
795
+ .file-name {
796
+ font-size: 14px;
797
+ color: #1d2129;
798
+ font-weight: 500;
799
+ line-height: 22px;
800
+ overflow: hidden;
801
+ text-overflow: ellipsis;
802
+ white-space: nowrap;
803
+ }
928
804
 
929
- elUpload.abort()
930
- },
931
- anewSubmitFn(row, $index) {
932
- this.$set(row, '_status', undefined)
933
- this.$refs['upload' + $index]?.$refs['upload']?.submit()
805
+ .file-meta {
806
+ margin-top: 2px;
807
+ font-size: 12px;
808
+ font-style: normal;
809
+ font-weight: 400;
810
+ line-height: 20px;
811
+ color: #4e5969;
812
+ }
813
+ }
814
+
815
+ .file-actions {
816
+ margin-left: 8px;
817
+ border-left: 1px solid #e5e6eb;
818
+ display: flex;
819
+ align-items: center;
820
+ .el-button {
821
+ padding: 0 0 0 12px;
822
+ color: #4e5969;
823
+ :hover {
824
+ color: #007aff;
825
+ }
826
+ }
827
+ }
828
+ }
829
+ }
830
+ }
934
831
  }
935
832
  }
936
833
  }
937
- </script>
834
+
835
+ .file-upload-table_preview-pn {
836
+ position: absolute;
837
+ right: 46px;
838
+ top: 10px;
839
+ }
840
+ </style>