Commit ceb42edfe8b7fd856a84ee7e548baa37543553d7

Authored by 严涛
2 parents bcf8483e 7cf91dd1

Merge branch 'cjerp-contract' of http://gitlab.qgutech.com/zhuyuanliang/erp-mobile into test_cjerp

  1 +<template>
  2 + <view class="file-upload-wrapper">
  3 + <view class="btn" @click="selectFile" v-if="fileList.length < limit">
  4 + <image class="icon" src="/static/images/add.png" mode="widthFix" />
  5 + <text>上传附件</text>
  6 + </view>
  7 + <view v-if="fileList.length > 0" class="file-list">
  8 + <view v-for="(item, index) in fileList" :key="index" class="file-item">
  9 + <text class="file-name">{{ item.fileName || item.originalFileName || item.name }}</text>
  10 + <text class="delete-btn" @click.stop="deleteFile(index)">×</text>
  11 + </view>
  12 + </view>
  13 + </view>
  14 +</template>
  15 +
  16 +<script>
  17 +import { uploadFileApi } from '@/api/base.js'
  18 +import upload from '@/utils/upload'
  19 +
  20 +export default {
  21 + name: 'FileUpload',
  22 + props: {
  23 + value: { type: [Array, String], default: () => [] },
  24 + limit: { type: Number, default: 1 },
  25 + fileSize: { type: Number, default: 5 }, // MB
  26 + fileType: { type: Array, default: () => [] } // e.g. ['png', 'jpg', 'pdf']
  27 + },
  28 + data() {
  29 + return {
  30 + fileList: []
  31 + }
  32 + },
  33 + watch: {
  34 + value: {
  35 + handler(val) {
  36 + if (Array.isArray(val)) {
  37 + this.fileList = val
  38 + } else if (val) {
  39 + try {
  40 + this.fileList = JSON.parse(val)
  41 + } catch (e) {
  42 + this.fileList = []
  43 + }
  44 + } else {
  45 + this.fileList = []
  46 + }
  47 + },
  48 + immediate: true,
  49 + deep: true
  50 + }
  51 + },
  52 + methods: {
  53 + extractId(res) {
  54 + const data = res && res.data ? res.data : res
  55 + const id = (data && (data.id || data.fileId || (data.data && (data.data.id || data.data.fileId))))
  56 + ? String(data.id || data.fileId || data.data.id || data.data.fileId)
  57 + : ''
  58 + return id
  59 + },
  60 + // 校验文件
  61 + validateFile(file) {
  62 + // 校验类型
  63 + if (this.fileType && this.fileType.length > 0) {
  64 + const name = file.name || ''
  65 + const ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase()
  66 + if (!this.fileType.includes(ext)) {
  67 + uni.showToast({ title: `仅支持 ${this.fileType.join(',')} 格式`, icon: 'none' })
  68 + return false
  69 + }
  70 + }
  71 + // 校验大小
  72 + if (this.fileSize && file.size > this.fileSize * 1024 * 1024) {
  73 + uni.showToast({ title: `文件大小不能超过${this.fileSize}MB`, icon: 'none' })
  74 + return false
  75 + }
  76 + return true
  77 + },
  78 +
  79 + async uploadFileByPath(filePath, name) {
  80 + const r = await upload({ url: '/sw/filebox/uploadFile', filePath, name: 'file' })
  81 + const id = this.extractId(r)
  82 + return { id, name }
  83 + },
  84 +
  85 + async uploadFileByFormData(fd, name) {
  86 + const r = await uploadFileApi(fd)
  87 + const id = this.extractId(r)
  88 + return { id, name }
  89 + },
  90 +
  91 + selectFile() {
  92 + const count = this.limit - this.fileList.length
  93 + if (count <= 0) return
  94 + if (typeof uni !== 'undefined' && typeof uni.chooseMessageFile === 'function') {
  95 + uni.chooseMessageFile({
  96 + count,
  97 + type: 'all',
  98 + success: (res) => {
  99 + const tempFiles = (res && res.tempFiles) ? res.tempFiles : []
  100 + this.processFiles(tempFiles)
  101 + },
  102 + fail: () => {
  103 + uni.showToast({ title: '未选择文件', icon: 'none' })
  104 + }
  105 + })
  106 + return
  107 + }
  108 + // #ifdef H5
  109 + this.selectFileH5()
  110 + // #endif
  111 + },
  112 +
  113 + selectFileH5() {
  114 + const input = document.createElement('input')
  115 + input.type = 'file'
  116 + input.multiple = this.limit > 1
  117 + input.style.display = 'none'
  118 + document.body.appendChild(input)
  119 + input.addEventListener('change', (e) => {
  120 + const files = Array.from(e.target.files)
  121 + const processedFiles = files.map(f => ({
  122 + path: null,
  123 + file: f,
  124 + name: f.name,
  125 + size: f.size
  126 + }))
  127 + this.processFiles(processedFiles)
  128 + document.body.removeChild(input)
  129 + })
  130 + input.click()
  131 + },
  132 +
  133 + async processFiles(files) {
  134 + if (!files || files.length === 0) return
  135 +
  136 + const validFiles = []
  137 + for (let i = 0; i < files.length; i++) {
  138 + const file = files[i]
  139 + if (this.validateFile(file)) {
  140 + validFiles.push(file)
  141 + }
  142 + }
  143 +
  144 + if (validFiles.length === 0) return
  145 +
  146 + if (this.fileList.length + validFiles.length > this.limit) {
  147 + uni.showToast({ title: `最多只能上传${this.limit}个文件`, icon: 'none' })
  148 + return
  149 + }
  150 +
  151 + uni.showLoading({ title: '上传中...' })
  152 +
  153 + try {
  154 + const uploadPromises = validFiles.map((f) => {
  155 + const name = f.name || 'file'
  156 + if (f.file) {
  157 + const fd = new FormData()
  158 + fd.append('file', f.file, name)
  159 + return this.uploadFileByFormData(fd, name)
  160 + }
  161 + if (f.path) {
  162 + return this.uploadFileByPath(f.path, name)
  163 + }
  164 + return Promise.resolve({ id: '', name })
  165 + })
  166 +
  167 + const results = await Promise.all(uploadPromises)
  168 +
  169 + const newFiles = results
  170 + .filter(r => r && r.id)
  171 + .map(r => ({ id: r.id, name: r.name, fileId: r.id, fileName: r.name }))
  172 +
  173 + this.fileList = [...this.fileList, ...newFiles]
  174 + this.emitChange()
  175 +
  176 + } catch (e) {
  177 + console.error(e)
  178 + uni.showToast({ title: '上传失败', icon: 'none' })
  179 + } finally {
  180 + uni.hideLoading()
  181 + }
  182 + },
  183 +
  184 + deleteFile(index) {
  185 + this.fileList.splice(index, 1)
  186 + this.emitChange()
  187 + },
  188 +
  189 + emitChange() {
  190 + this.$emit('input', this.fileList)
  191 + this.$emit('change', this.fileList)
  192 + this.$emit('update:value', this.fileList)
  193 + }
  194 + }
  195 +}
  196 +</script>
  197 +
  198 +<style scoped lang="scss">
  199 +.file-upload-wrapper {
  200 + width: 100%;
  201 +}
  202 +
  203 +.btn {
  204 + display: inline-flex;
  205 + align-items: center;
  206 + color: #3D48A3;
  207 + font-size: 28rpx;
  208 + cursor: pointer;
  209 + height: 48rpx;
  210 +
  211 + .icon {
  212 + width: 32rpx;
  213 + height: 32rpx;
  214 + margin-right: 8rpx;
  215 + }
  216 +}
  217 +
  218 +.file-list {
  219 + display: flex;
  220 + flex-direction: column;
  221 + gap: 16rpx;
  222 +}
  223 +
  224 +.file-item {
  225 + display: flex;
  226 + align-items: center;
  227 + justify-content: space-between;
  228 + background-color: #f5f7fa;
  229 + padding: 12rpx 20rpx;
  230 + border-radius: 8rpx;
  231 +
  232 + .file-name {
  233 + font-size: 26rpx;
  234 + color: #333;
  235 + flex: 1;
  236 + margin-right: 20rpx;
  237 + white-space: pre-wrap;
  238 + word-break: break-all;
  239 + }
  240 +
  241 + .delete-btn {
  242 + color: #999;
  243 + font-size: 32rpx;
  244 + padding: 0 10rpx;
  245 + }
  246 +}
  247 +</style>
... ...
... ... @@ -32,9 +32,23 @@
32 32 <view class="row"><text class="label">经营年限</text><text class="value">{{ form.businessYears }}</text></view>
33 33 <view class="row"><text class="label">单位地址</text><text class="value">{{ form.companyAddress }}</text></view>
34 34 <view class="row"><text class="label">经营范围</text><text class="value">{{ form.businessScope }}</text></view>
35   - <view class="row"><text class="label">工商信息</text><text class="value act" @click="downloadFile(form.businessFileId, form.businessFileName)">{{ form.businessFileName }}</text>
  35 + <view class="row">
  36 + <text class="label">工商信息</text>
  37 + <view class="value">
  38 + <view v-for="item in (form.businessFileInfoList || [])" :key="item.fileId || item.fileName" class="file-item"
  39 + @click="downloadFile(item.fileId, item.fileName)">
  40 + <text class="value act">{{ item.fileName }}</text>
  41 + </view>
  42 + </view>
36 43 </view>
37   - <view class="row"><text class="label">股东信息</text><text class="value act" @click="downloadFile(form.shareholderFileId, form.shareholderFileName)">{{ form.shareholderFileName }}</text>
  44 + <view class="row">
  45 + <text class="label">股东信息</text>
  46 + <view class="value">
  47 + <view v-for="item in (form.shareholderFileInfoList || [])" :key="item.fileId || item.fileName"
  48 + class="file-item" @click="downloadFile(item.fileId, item.fileName)">
  49 + <text class="value act">{{ item.fileName }}</text>
  50 + </view>
  51 + </view>
38 52 </view>
39 53 </view>
40 54
... ... @@ -489,6 +503,14 @@ export default {
489 503 }
490 504 }
491 505
  506 +.file-item {
  507 + margin-bottom: 12rpx;
  508 +
  509 + &:last-child {
  510 + margin-bottom: 0;
  511 + }
  512 +}
  513 +
492 514 .card {
493 515 padding: 16rpx 0 8rpx;
494 516 border-top: 1rpx solid #f0f0f0;
... ...
... ... @@ -30,10 +30,23 @@
30 30 <view class="row"><text class="label">经营年限</text><text class="value">{{ form.businessYears }}</text></view>
31 31 <view class="row"><text class="label">单位地址</text><text class="value">{{ form.companyAddress }}</text></view>
32 32 <view class="row"><text class="label">经营范围</text><text class="value">{{ form.businessScope }}</text></view>
33   - <view class="row"><text class="label">工商信息</text><text class="value act" @click="downloadFile(form.businessFileId, form.businessFileName)">{{ form.businessFileName }}</text>
  33 + <view class="row">
  34 + <text class="label">工商信息</text>
  35 + <view class="value">
  36 + <view v-for="item in (form.businessFileInfoList || [])" :key="item.fileId || item.fileName" class="file-item"
  37 + @click="downloadFile(item.fileId, item.fileName)">
  38 + <text class="value act">{{ item.fileName }}</text>
  39 + </view>
  40 + </view>
34 41 </view>
35   - <view class="row"><text class="label">股东信息</text><text class="value act" @click="downloadFile(form.shareholderFileId, form.shareholderFileName)">{{ form.shareholderFileName
36   - }}</text>
  42 + <view class="row">
  43 + <text class="label">股东信息</text>
  44 + <view class="value">
  45 + <view v-for="item in (form.shareholderFileInfoList || [])" :key="item.fileId || item.fileName" class="file-item"
  46 + @click="downloadFile(item.fileId, item.fileName)">
  47 + <text class="value act">{{ item.fileName }}</text>
  48 + </view>
  49 + </view>
37 50 </view>
38 51 </view>
39 52
... ... @@ -444,4 +457,12 @@ export default {
444 457 .mgb10 {
445 458 margin-bottom: 20rpx;
446 459 }
  460 +
  461 +.file-item {
  462 + margin-bottom: 12rpx;
  463 +}
  464 +
  465 +.file-item:last-child {
  466 + margin-bottom: 0;
  467 +}
447 468 </style>
... ...
... ... @@ -43,11 +43,11 @@
43 43 <view class="dialog_body">
44 44 <view class="dialog_row">
45 45 <text class="dialog_label">上传工商信息</text>
46   - <FileUpload v-model="businessFile" />
  46 + <FileUpload v-model="businessFileList" :limit="9" />
47 47 </view>
48 48 <view class="dialog_row">
49 49 <text class="dialog_label">上传股东信息</text>
50   - <FileUpload v-model="shareholderFile" />
  50 + <FileUpload v-model="shareholderFileList" :limit="9" />
51 51 </view>
52 52 </view>
53 53 <view class="dialog_footer">
... ... @@ -112,7 +112,7 @@
112 112 <script>
113 113 import FlowTimeline from '@/components/flow-timeline/index.vue'
114 114 import SingleSelectSheet from '@/components/single-select/index.vue'
115   -import FileUpload from '@/components/file-upload/index.vue'
  115 +import FileUpload from '@/components/file-upload/mutiple.vue'
116 116 import { getSysFlowComponentPath } from '@/utils/flow-components.js'
117 117 import { getFlowLinkByInstanceIdApi, getInstanceByBusinessIdApi, approvePassApi, approveRefuseApi } from '@/api/flow.js'
118 118 import { getDicByCodeApi } from '@/api/base.js'
... ... @@ -137,9 +137,9 @@ export default {
137 137 message: '',
138 138 customerInfoVisible: false,
139 139 companyReviewVisible: false,
140   - businessFile: { id: '', name: '' },
141   - shareholderFile: { id: '', name: '' },
142   - customerInfo: { businessFileName: '', businessFileId: '', shareholderFileName: '', shareholderFileId: '' },
  140 + businessFileList: [],
  141 + shareholderFileList: [],
  142 + customerInfo: { businessFileList: [], shareholderFileList: [] },
143 143 companyReview: { companySettlementPeriod: '', companyMaterialSupplyPlan: '', companySuggestedCategory: '', companySuggestedCategoryName: '', companyCreditLimit: '' },
144 144 categoryOptions: [],
145 145 sheet: { visible: false, title: '请选择', options: [], value: '', field: '' },
... ... @@ -330,10 +330,9 @@ export default {
330 330 },
331 331 // 是否填写-客户信息
332 332 hsCustomerInfo() {
333   - if (this.customerInfo.businessFileId || this.customerInfo.shareholderFileId) {
334   - return true
335   - }
336   - return false
  333 + const b = this.customerInfo && Array.isArray(this.customerInfo.businessFileList) ? this.customerInfo.businessFileList : []
  334 + const s = this.customerInfo && Array.isArray(this.customerInfo.shareholderFileList) ? this.customerInfo.shareholderFileList : []
  335 + return b.length > 0 || s.length > 0
337 336 },
338 337 // 是否填写-公司评审信息
339 338 hsCompanyReview() {
... ... @@ -346,6 +345,24 @@ export default {
346 345 // 更多按钮操作
347 346 onExtra() {
348 347 if (this.extraBtnText === '客户信息') {
  348 + const toListModel = (arr) => {
  349 + const list = Array.isArray(arr) ? arr : []
  350 + return list.map(it => ({
  351 + id: (it && (it.fileId || it.id)) ? String(it.fileId || it.id) : '',
  352 + name: (it && (it.fileName || it.name)) ? String(it.fileName || it.name) : '',
  353 + fileId: (it && (it.fileId || it.id)) ? String(it.fileId || it.id) : '',
  354 + fileName: (it && (it.fileName || it.name)) ? String(it.fileName || it.name) : '',
  355 + })).filter(it => it.id)
  356 + }
  357 + const vals = this.getPayload() || {}
  358 + const b = (this.customerInfo && this.customerInfo.businessFileList && this.customerInfo.businessFileList.length > 0)
  359 + ? this.customerInfo.businessFileList
  360 + : (vals.businessFileList || [])
  361 + const s = (this.customerInfo && this.customerInfo.shareholderFileList && this.customerInfo.shareholderFileList.length > 0)
  362 + ? this.customerInfo.shareholderFileList
  363 + : (vals.shareholderFileList || [])
  364 + this.businessFileList = toListModel(b)
  365 + this.shareholderFileList = toListModel(s)
349 366 this.$refs.customerInfoPopup && this.$refs.customerInfoPopup.open()
350 367 } else if (this.extraBtnText === '公司评审') {
351 368 this.$refs.companyReviewPopup && this.$refs.companyReviewPopup.open()
... ... @@ -353,12 +370,14 @@ export default {
353 370 },
354 371 // 上传客户信息 保存
355 372 onCustomerInfoSave() {
356   - const b = this.businessFile || {}
357   - const s = this.shareholderFile || {}
358   - this.customerInfo.businessFileId = b.id || ''
359   - this.customerInfo.businessFileName = b.name || ''
360   - this.customerInfo.shareholderFileId = s.id || ''
361   - this.customerInfo.shareholderFileName = s.name || ''
  373 + const b = Array.isArray(this.businessFileList) ? this.businessFileList : []
  374 + const s = Array.isArray(this.shareholderFileList) ? this.shareholderFileList : []
  375 + this.customerInfo.businessFileList = b
  376 + .map(it => ({ fileId: it.fileId || '', fileName: it.fileName || it.name || '' }))
  377 + .filter(it => it.fileId)
  378 + this.customerInfo.shareholderFileList = s
  379 + .map(it => ({ fileId: it.fileId || '', fileName: it.fileName || it.name || '' }))
  380 + .filter(it => it.fileId)
362 381 this.$refs.customerInfoPopup && this.$refs.customerInfoPopup.close()
363 382 console.log('审核__customerInfo', this.customerInfo)
364 383 uni.showToast({ title: '已保存', icon: 'none' })
... ...