Browse Source

2025-11-11

QC文件预览优化
master
fengyuan_yang 2 months ago
parent
commit
2ef7565bdb
  1. 21
      package-lock.json
  2. 2
      package.json
  3. 10
      src/main.js
  4. 192
      src/views/modules/qc/com_qc_itemImage_upload_file.vue

21
package-lock.json

@ -18664,6 +18664,22 @@
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
}, },
"v-viewer": {
"version": "1.6.4",
"resolved": "https://registry.npmmirror.com/v-viewer/-/v-viewer-1.6.4.tgz",
"integrity": "sha512-LVkiUHpmsbsZXebeNXnu8krRCi5i2n07FeLFxoIVGhw8lVvTBO0ffpbDC6mLEuacCjrIh09HjIqpciwUtWE8lQ==",
"requires": {
"throttle-debounce": "^2.0.1",
"viewerjs": "^1.5.0"
},
"dependencies": {
"throttle-debounce": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ=="
}
}
},
"v8flags": { "v8flags": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmmirror.com/v8flags/-/v8flags-3.2.0.tgz", "resolved": "https://registry.npmmirror.com/v8flags/-/v8flags-3.2.0.tgz",
@ -18715,6 +18731,11 @@
} }
} }
}, },
"viewerjs": {
"version": "1.11.7",
"resolved": "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.11.7.tgz",
"integrity": "sha512-0JuVqOmL5v1jmEAlG5EBDR3XquxY8DWFQbFMprOXgaBB0F7Q/X9xWdEaQc59D8xzwkdUgXEMSSknTpriq95igg=="
},
"vinyl": { "vinyl": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmmirror.com/vinyl/-/vinyl-2.2.1.tgz", "resolved": "https://registry.npmmirror.com/vinyl/-/vinyl-2.2.1.tgz",

2
package.json

@ -34,6 +34,8 @@
"sass-loader": "6.0.6", "sass-loader": "6.0.6",
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"svg-sprite-loader": "3.7.3", "svg-sprite-loader": "3.7.3",
"v-viewer": "^1.6.4",
"viewerjs": "^1.11.7",
"vue": "2.6.10", "vue": "2.6.10",
"vue-cookie": "1.1.4", "vue-cookie": "1.1.4",
"vue-i18n": "^8.25.0", "vue-i18n": "^8.25.0",

10
src/main.js

@ -18,10 +18,13 @@ import decimalUtil from '@/utils/decimalUtil.js'
import getLodop from '@/utils/LodopFuncs.js' import getLodop from '@/utils/LodopFuncs.js'
import pdf from 'vue-pdf' import pdf from 'vue-pdf'
import { debounce,throttle} from '@/utils/common.js' import { debounce,throttle} from '@/utils/common.js'
import Viewer from 'v-viewer'
import 'viewerjs/dist/viewer.css'
Vue.component('downloadExcel', JsonExcel) Vue.component('downloadExcel', JsonExcel)
Vue.component('pdf', pdf) Vue.component('pdf', pdf)
Vue.use(VueCookie) Vue.use(VueCookie)
Vue.use(Viewer)
Vue.config.productionTip = false Vue.config.productionTip = false
// 非生产环境, 适配mockjs模拟数据 // api: https://github.com/nuysoft/Mock // 非生产环境, 适配mockjs模拟数据 // api: https://github.com/nuysoft/Mock
@ -50,3 +53,10 @@ new Vue({
template: '<App/>', template: '<App/>',
components: { App } components: { App }
}) })
Viewer.setDefaults({
// 需要配置的属性 注意属性并没有引号
title: false,
toolbar: true,
zIndex: 99999,
})

192
src/views/modules/qc/com_qc_itemImage_upload_file.vue

@ -16,16 +16,51 @@
</el-row> </el-row>
<el-row> <el-row>
<!-- 图像区域 --> <!-- 图像区域 -->
<ul class="content-image" v-viewer>
<li v-for="(item, index) in descImgs" :key="index" style="float: left;display: inline">
<img :src="item.url" style="width:70px;height: 70px"/>
<div class="content-image" style="display: flex; flex-wrap: wrap; gap: 10px; min-height: 100px;" @click.stop>
<div v-for="(item, index) in descImgs" :key="index" style="position: relative; width: 80px; height: 80px;">
<!-- 加载中状态 -->
<div v-if="item.loading" style="width: 80px; height: 80px; border: 1px solid #ddd; border-radius: 4px; display: flex; justify-content: center; align-items: center; background: #f5f7fa;">
<i class="el-icon-loading" style="font-size: 24px; color: #409EFF;"></i>
</div>
<!-- 图片显示 -->
<div v-else-if="item.blobUrl" @click.stop="previewImage(index)" style="width: 80px; height: 80px; border: 1px solid #67C23A; border-radius: 4px; overflow: hidden; cursor: pointer;">
<img :src="item.blobUrl" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<!-- 加载失败 -->
<div v-else style="width: 80px; height: 80px; border: 1px solid #F56C6C; border-radius: 4px; display: flex; justify-content: center; align-items: center; background: #FEF0F0;">
<i class="el-icon-picture-outline" style="font-size: 24px; color: #F56C6C;"></i>
</div>
<!-- 删除图标 --> <!-- 删除图标 -->
<div class="delete-img">
<i class="el-icon-delete" @click="deleteImage(index,item.id)"></i>
<div v-if="!item.loading" @click.stop="deleteImage(index,item.id)" style="position: absolute; top: 2px; right: 2px; background: rgba(255,255,255,0.9); border-radius: 3px; padding: 2px; cursor: pointer; box-shadow: 0 0 3px rgba(0,0,0,0.3); z-index: 10;">
<i class="el-icon-delete" style="color: #f56c6c; font-size: 16px;"></i>
</div> </div>
</li>
</ul>
</div>
<!-- 空状态提示 -->
<div v-if="descImgs.length === 0" style="width: 100%; text-align: center; padding: 20px; color: #909399;">
暂无图片
</div>
</div>
</el-row> </el-row>
<!-- 图片预览对话框 -->
<el-dialog
:visible.sync="previewVisible"
width="80%"
append-to-body
:close-on-click-modal="true"
@open="handlePreviewOpen"
@close="handlePreviewClose"
:modal-append-to-body="true"
custom-class="image-preview-dialog">
<div style="text-align: center; background: #000; padding: 20px; min-height: 400px; display: flex; align-items: center; justify-content: center;">
<img :src="previewUrl" style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto;">
</div>
<div slot="footer" style="text-align: center; padding: 10px;">
<el-button @click="prevImage" :disabled="previewIndex <= 0" icon="el-icon-arrow-left">上一张</el-button>
<span style="margin: 0 20px; color: #606266;">{{ previewIndex + 1 }} / {{ allBlobUrls.length }}</span>
<el-button @click="nextImage" :disabled="previewIndex >= allBlobUrls.length - 1">下一张<i class="el-icon-arrow-right el-icon--right"></i></el-button>
</div>
</el-dialog>
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-upload class="customer-upload" drag multiple :file-list="fileList" <el-upload class="customer-upload" drag multiple :file-list="fileList"
@ -49,6 +84,7 @@
<script> <script>
import {uploadEamObjectFile} from '@/api/eam/com_eam_object_upload_file.js'; import {uploadEamObjectFile} from '@/api/eam/com_eam_object_upload_file.js';
import {downLoadObjectFile} from '@/api/eam/eam_object_list.js';
import { import {
searchItemFileUrl, // searchItemFileUrl, //
imageDelete, // imageDelete, //
@ -74,9 +110,72 @@ export default {
}, },
dataListLoading: false, dataListLoading: false,
descImgs: [], descImgs: [],
previewVisible: false,
previewUrl: '',
previewIndex: 0,
}
},
computed: {
// Blob URL使
allBlobUrls() {
return this.descImgs.filter(item => item.blobUrl).map(item => item.blobUrl)
} }
}, },
methods: { methods: {
//
previewImage(index) {
// blobUrl
const validImages = this.descImgs.filter(item => item.blobUrl)
const actualIndex = validImages.findIndex((item, i) => {
return this.descImgs[index] === item
})
if (actualIndex >= 0 && validImages[actualIndex]) {
this.previewIndex = actualIndex
this.previewUrl = validImages[actualIndex].blobUrl
this.previewVisible = true
}
},
//
prevImage() {
if (this.previewIndex > 0) {
this.previewIndex--
this.previewUrl = this.allBlobUrls[this.previewIndex]
}
},
//
nextImage() {
if (this.previewIndex < this.allBlobUrls.length - 1) {
this.previewIndex++
this.previewUrl = this.allBlobUrls[this.previewIndex]
}
},
//
handleKeydown(e) {
if (!this.previewVisible) return
if (e.key === 'ArrowLeft' || e.keyCode === 37) {
this.prevImage()
} else if (e.key === 'ArrowRight' || e.keyCode === 39) {
this.nextImage()
} else if (e.key === 'Escape' || e.keyCode === 27) {
this.previewVisible = false
}
},
//
handlePreviewOpen() {
document.addEventListener('keydown', this.handleKeydown)
},
//
handlePreviewClose() {
document.removeEventListener('keydown', this.handleKeydown)
},
// //
init (currentRow) { init (currentRow) {
// //
@ -84,22 +183,62 @@ export default {
// //
this.visible = true this.visible = true
this.descImgs = [] this.descImgs = []
this.previewVisible = false
this.searchItemFileUrl() this.searchItemFileUrl()
}, },
// //
searchItemFileUrl () { searchItemFileUrl () {
// Blob URL
this.descImgs.forEach(item => {
if (item.blobUrl) {
URL.revokeObjectURL(item.blobUrl)
}
})
this.descImgs = [] this.descImgs = []
searchItemFileUrl(this.pageData).then(({data}) => { searchItemFileUrl(this.pageData).then(({data}) => {
if (data.code === 0) { if (data.code === 0) {
for (let i = 0; i < data.rows.length; i++) {
this.descImgs.push(data.rows[i])
}
//
this.descImgs = data.rows.map(item => ({
...item,
blobUrl: null,
loading: true
}))
// Blob URL
this.descImgs.forEach((item, index) => {
this.loadImageAsBlob(item, index)
})
} else { } else {
this.$message.warning(data.msg) this.$message.warning(data.msg)
} }
}) })
}, },
// Blob URL
loadImageAsBlob(item, index) {
if (!item.id) {
console.error('图片没有ID:', item)
return
}
downLoadObjectFile({id: item.id}).then(({data}) => {
// Blob
const blob = new Blob([data], {type: 'image/png'})
// URL
const blobUrl = URL.createObjectURL(blob)
// URL
this.$set(this.descImgs[index], 'blobUrl', blobUrl)
this.$set(this.descImgs[index], 'loading', false)
console.log('✅ 图片加载成功:', item.url, '-> Blob URL')
}).catch(error => {
console.error('❌ 图片下载失败:', item.url, error)
this.$set(this.descImgs[index], 'loading', false)
})
},
// //
beforeUploadHandle (file) { beforeUploadHandle (file) {
if (file.type !== 'image/jpg' && file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') { if (file.type !== 'image/jpg' && file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
@ -116,6 +255,12 @@ export default {
this.fileList = [] this.fileList = []
// //
this.$refs.uploadFile.clearFiles() this.$refs.uploadFile.clearFiles()
// Blob URL
this.descImgs.forEach(item => {
if (item.blobUrl) {
URL.revokeObjectURL(item.blobUrl)
}
})
// //
//this.$emit('refreshPageTables2') //this.$emit('refreshPageTables2')
// //
@ -186,8 +331,35 @@ export default {
//this.$emit('refreshPageTables2') //this.$emit('refreshPageTables2')
} }
}, },
beforeDestroy() {
//
document.removeEventListener('keydown', this.handleKeydown)
// Blob URL
this.descImgs.forEach(item => {
if (item.blobUrl) {
URL.revokeObjectURL(item.blobUrl)
}
})
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
</style> </style>
<style>
/* 预览对话框样式 */
.image-preview-dialog .el-dialog__header {
padding: 10px 20px;
background: #f5f7fa;
}
.image-preview-dialog .el-dialog__body {
padding: 0;
}
.image-preview-dialog .el-dialog__footer {
padding: 10px 20px;
background: #f5f7fa;
}
</style>
Loading…
Cancel
Save