diff --git a/package-lock.json b/package-lock.json index 95c902a..2a5d04e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18664,6 +18664,22 @@ "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", "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": { "version": "3.2.0", "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": { "version": "2.2.1", "resolved": "https://registry.npmmirror.com/vinyl/-/vinyl-2.2.1.tgz", diff --git a/package.json b/package.json index 4141c0e..7f6cac6 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "sass-loader": "6.0.6", "sortablejs": "^1.15.6", "svg-sprite-loader": "3.7.3", + "v-viewer": "^1.6.4", + "viewerjs": "^1.11.7", "vue": "2.6.10", "vue-cookie": "1.1.4", "vue-i18n": "^8.25.0", diff --git a/src/main.js b/src/main.js index 073463b..0a3f8de 100644 --- a/src/main.js +++ b/src/main.js @@ -18,10 +18,13 @@ import decimalUtil from '@/utils/decimalUtil.js' import getLodop from '@/utils/LodopFuncs.js' import pdf from 'vue-pdf' import { debounce,throttle} from '@/utils/common.js' +import Viewer from 'v-viewer' +import 'viewerjs/dist/viewer.css' Vue.component('downloadExcel', JsonExcel) Vue.component('pdf', pdf) Vue.use(VueCookie) +Vue.use(Viewer) Vue.config.productionTip = false // 非生产环境, 适配mockjs模拟数据 // api: https://github.com/nuysoft/Mock @@ -50,3 +53,10 @@ new Vue({ template: '', components: { App } }) + +Viewer.setDefaults({ + // 需要配置的属性 注意属性并没有引号 + title: false, + toolbar: true, + zIndex: 99999, +}) diff --git a/src/views/modules/qc/com_qc_itemImage_upload_file.vue b/src/views/modules/qc/com_qc_itemImage_upload_file.vue index 390b697..6f8538c 100644 --- a/src/views/modules/qc/com_qc_itemImage_upload_file.vue +++ b/src/views/modules/qc/com_qc_itemImage_upload_file.vue @@ -16,16 +16,51 @@ - + + +
+ 暂无图片 +
+
+ + + +
+ +
+
+ 上一张 + {{ previewIndex + 1 }} / {{ allBlobUrls.length }} + 下一张 +
+
import {uploadEamObjectFile} from '@/api/eam/com_eam_object_upload_file.js'; +import {downLoadObjectFile} from '@/api/eam/eam_object_list.js'; import { searchItemFileUrl, // 查询文件路径 imageDelete, // 删除图片 @@ -74,9 +110,72 @@ export default { }, dataListLoading: false, descImgs: [], + previewVisible: false, + previewUrl: '', + previewIndex: 0, + } + }, + computed: { + // 所有图片Blob URL数组,供预览使用 + allBlobUrls() { + return this.descImgs.filter(item => item.blobUrl).map(item => item.blobUrl) } }, 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) { // 初始化参数 @@ -84,21 +183,61 @@ export default { // 打开页面 this.visible = true this.descImgs = [] + this.previewVisible = false this.searchItemFileUrl() }, // 查询图片列表 searchItemFileUrl () { + // 释放旧的Blob URL + this.descImgs.forEach(item => { + if (item.blobUrl) { + URL.revokeObjectURL(item.blobUrl) + } + }) + this.descImgs = [] searchItemFileUrl(this.pageData).then(({data}) => { 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 { 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) { @@ -116,6 +255,12 @@ export default { this.fileList = [] // 清空文件上传记录 this.$refs.uploadFile.clearFiles() + // 释放Blob URL,避免内存泄漏 + this.descImgs.forEach(item => { + if (item.blobUrl) { + URL.revokeObjectURL(item.blobUrl) + } + }) // 刷新报工的页面 //this.$emit('refreshPageTables2') // 关闭当前的页面 @@ -186,8 +331,35 @@ export default { //this.$emit('refreshPageTables2') } }, + + beforeDestroy() { + // 组件销毁时清理事件监听 + document.removeEventListener('keydown', this.handleKeydown) + // 释放所有Blob URL + this.descImgs.forEach(item => { + if (item.blobUrl) { + URL.revokeObjectURL(item.blobUrl) + } + }) + } } +