报告查询一键下载

This commit is contained in:
hayu 2025-12-10 19:20:34 +08:00
parent f18a9b53c1
commit dc7a03c2f8
1 changed files with 198 additions and 615 deletions

View File

@ -100,50 +100,10 @@
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button <el-button icon="el-icon-download" size="mini" @click="handleBulkDownload">一键下载</el-button>
icon="el-icon-download"
size="mini"
@click="handleBulkDownload"
:disabled="!canDownload"
:loading="isExporting">
{{ exportButtonText }}
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 进度条显示区域 -->
<div v-if="showProgress" class="progress-container">
<el-progress
:percentage="progressPercentage"
:status="progressStatus"
:stroke-width="18"
:text-inside="true"
:show-text="true">
</el-progress>
<div class="progress-info">
<span class="progress-text">{{ progressText }}</span>
<span class="progress-detail">{{ currentItem }}/{{ totalItems }} {{ currentFileName }}</span>
<span class="progress-speed" v-if="downloadSpeed > 0">
速度: {{ formatBytes(downloadSpeed) }}/s
</span>
</div>
<div class="progress-actions" v-if="showProgress">
<el-button
size="mini"
@click="cancelDownload"
:disabled="!isExporting"
v-if="!downloadComplete">
取消下载
</el-button>
<el-button
size="mini"
type="success"
@click="clearProgress"
v-if="downloadComplete">
完成
</el-button>
</div>
</div>
<el-table v-loading="loading" :data="tableList" ref="multipleTable" row-key="id" @selection-change="handleSelectionChange" border> <el-table v-loading="loading" :data="tableList" ref="multipleTable" row-key="id" @selection-change="handleSelectionChange" border>
<el-table-column type="selection" width="45" :reserve-selection="true"></el-table-column> <el-table-column type="selection" width="45" :reserve-selection="true"></el-table-column>
@ -341,6 +301,7 @@
</div> </div>
<div slot="footer" class="dialog-footer" style="text-align: center"> <div slot="footer" class="dialog-footer" style="text-align: center">
<!-- <el-button type="primary" @click="handleExportCheck">导出</el-button>-->
<el-button type="primary" @click="printCheck"> </el-button> <el-button type="primary" @click="printCheck"> </el-button>
<el-button @click="closeDialogAndRefresh"> </el-button> <el-button @click="closeDialogAndRefresh"> </el-button>
</div> </div>
@ -372,9 +333,6 @@ import printJS from "print-js";
import QRCode from "qrcodejs2"; import QRCode from "qrcodejs2";
import axios from 'axios' import axios from 'axios'
import {downloadFile} from "@/utils/download"; import {downloadFile} from "@/utils/download";
import request from "@/utils/request";
import {getToken} from "@/utils/auth";
export default { export default {
name: "ReportQuery", name: "ReportQuery",
dicts: ['part_task_status'], dicts: ['part_task_status'],
@ -426,68 +384,23 @@ export default {
rowObj: {}, rowObj: {},
// //
selectedIds: [], selectedItems: new Map(),
selectedData: {}, // 使keyid
//
isExporting: false,
showProgress: false,
downloadComplete: false,
progressPercentage: 0,
currentItem: 0,
totalItems: 0,
currentFileName: '',
progressText: '准备下载...',
progressStatus: '',
downloadSpeed: 0,
downloadController: null,
retryCount: 0,
maxRetries: 3,
downloadStartTime: 0,
downloadedBytes: 0,
}; };
}, },
computed: {
/** 是否可以下载 */
canDownload() {
const can = this.selectedIds.length > 0 && !this.isExporting;
console.log('canDownload检查:', {
selectedIds: this.selectedIds.length,
isExporting: this.isExporting,
can: can
});
return can;
},
/** 下载按钮文字 */
exportButtonText() {
if (this.isExporting) {
return `下载中 ${this.progressPercentage}%`;
}
return `一键下载${this.selectedIds.length > 0 ? ` (${this.selectedIds.length})` : ''}`;
}
},
created() { created() {
console.log('组件初始化');
this.getList(); this.getList();
this.getImpUnitOptions() this.getImpUnitOptions()
this.departList() this.departList()
this.proList() this.proList()
this.getDeviceType() this.getDeviceType()
}, },
beforeDestroy() {
//
if (this.downloadController) {
this.downloadController.abort();
}
},
methods: { methods: {
/** 获取分公司下拉 */ /** 获取分公司下拉 */
async getImpUnitOptions() { async getImpUnitOptions() {
try { try {
const res = await getImpUnitListApi() const res = await getImpUnitListApi() //
this.impUnitOptions = res.data.map(item => ({ this.impUnitOptions = res.data.map(item => ({
label: item.impUnitName, label: item.impUnitName, //
value: item.impUnitName value: item.impUnitName
})) }))
} catch (e) { } catch (e) {
@ -497,9 +410,9 @@ export default {
/** 获取项目部下拉 */ /** 获取项目部下拉 */
async departList() { async departList() {
try { try {
const res = await getDepartListByImpUnitApi() const res = await getDepartListByImpUnitApi() //
this.departOptions = res.data.map(item => ({ this.departOptions = res.data.map(item => ({
label: item.departName, label: item.departName, //
value: item.departName value: item.departName
})) }))
} catch (e) { } catch (e) {
@ -509,9 +422,9 @@ export default {
/** 获取工程下拉 */ /** 获取工程下拉 */
async proList() { async proList() {
try { try {
const res = await getProListByDepartApi() const res = await getProListByDepartApi() //
this.proOptions = res.data.map(item => ({ this.proOptions = res.data.map(item => ({
label: item.proName, label: item.proName, //
value: item.proName value: item.proName
})) }))
} catch (e) { } catch (e) {
@ -520,22 +433,22 @@ export default {
}, },
/** 分公司选择变化,加载项目部 */ /** 分公司选择变化,加载项目部 */
async handleImpUnitChange() { async handleImpUnitChange() {
this.queryParams.departName = null this.queryParams.departName = null //
this.departOptions = [] this.departOptions = [] //
this.queryParams.proName = null this.queryParams.proName = null //
this.proOptions = [] this.proOptions = [] //
try { try {
const params = { const params = {
impUnitName: this.queryParams.impUnitName, impUnitName: this.queryParams.impUnitName, //
departName: this.queryParams.departName, departName: this.queryParams.departName, //
proName: this.queryParams.proName, proName: this.queryParams.proName,
teamName:this.queryParams.teamName, teamName:this.queryParams.teamName,
subUnitName:this.queryParams.subUnitName, subUnitName:this.queryParams.subUnitName,
} }
const res = await getDepartListByImpUnitApi(params) const res = await getDepartListByImpUnitApi(params)
this.departOptions = res.data.map(item => ({ this.departOptions = res.data.map(item => ({
label: item.departName, label: item.departName, //
value: item.departName value: item.departName
})) }))
} catch (e) { } catch (e) {
@ -544,20 +457,21 @@ export default {
}, },
/** 项目部选择变化,加载工程 */ /** 项目部选择变化,加载工程 */
async handleDepartChange() { async handleDepartChange() {
this.queryParams.proName = null this.queryParams.proName = null //
this.proOptions = [] this.proOptions = [] //
try { try {
//
const params = { const params = {
impUnitName: this.queryParams.impUnitName, impUnitName: this.queryParams.impUnitName, //
departName: this.queryParams.departName, departName: this.queryParams.departName, //
proName: this.queryParams.proName, proName: this.queryParams.proName,
teamName:this.queryParams.teamName, teamName:this.queryParams.teamName,
subUnitName:this.queryParams.subUnitName, subUnitName:this.queryParams.subUnitName,
} }
const res = await getProListByDepartApi(params) const res = await getProListByDepartApi(params)
this.proOptions = res.data.map(item => ({ this.proOptions = res.data.map(item => ({
label: item.proName, label: item.proName, //
value: item.proName value: item.proName
})) }))
} catch (e) { } catch (e) {
@ -594,6 +508,7 @@ export default {
} else { } else {
typeId = this.materialNameList.find(item => item.label == e).value typeId = this.materialNameList.find(item => item.label == e).value
} }
console.log('🚀 ~ handleMaModel ~ typeId:', typeId)
getDeviceType({ level: 4, skipPermission: 1, typeId }).then(response => { getDeviceType({ level: 4, skipPermission: 1, typeId }).then(response => {
let matModelRes = response.data let matModelRes = response.data
this.materialModelList = matModelRes.map((item) => { this.materialModelList = matModelRes.map((item) => {
@ -620,11 +535,11 @@ export default {
this.total = response.data.total; this.total = response.data.total;
this.loading = false; this.loading = false;
//
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.multipleTable) { if (this.$refs.multipleTable) {
//
this.tableList.forEach(row => { this.tableList.forEach(row => {
if (this.selectedIds.includes(row.id)) { if (this.selectedItems.has(row.id)) {
this.$refs.multipleTable.toggleRowSelection(row, true); this.$refs.multipleTable.toggleRowSelection(row, true);
} }
}); });
@ -637,8 +552,8 @@ export default {
this.resetForm("queryForm"); this.resetForm("queryForm");
this.dateRange=[] this.dateRange=[]
this.queryParams.keyWord=null; this.queryParams.keyWord=null;
this.selectedIds = []; //
this.selectedData = {}; this.selectedItems.clear();
if (this.$refs.multipleTable) { if (this.$refs.multipleTable) {
this.$refs.multipleTable.clearSelection(); this.$refs.multipleTable.clearSelection();
} }
@ -647,38 +562,40 @@ export default {
/** 搜索按钮操作 */ /** 搜索按钮操作 */
handleQuery() { handleQuery() {
this.queryParams.pageNum = 1; this.queryParams.pageNum = 1;
this.selectedIds = []; //
this.selectedData = {}; this.selectedItems.clear();
if (this.$refs.multipleTable) { if (this.$refs.multipleTable) {
this.$refs.multipleTable.clearSelection(); this.$refs.multipleTable.clearSelection();
} }
this.getList(); this.getList();
}, },
// ==== ==== //
handleSelectionChange(selection) { handleSelectionChange(selection) {
console.log('选中项变化:', selection.length, '项'); //
//
this.selectedIds = [];
this.selectedData = {};
//
selection.forEach(item => { selection.forEach(item => {
this.selectedIds.push(item.id); this.selectedItems.set(item.id, item);
this.selectedData[item.id] = item;
}); });
console.log('当前选中ID:', this.selectedIds); //
console.log('选中数据:', this.selectedData); const currentIds = selection.map(item => item.id);
for (let id of this.selectedItems.keys()) {
if (!currentIds.includes(id)) {
const itemInCurrentPage = this.tableList.find(row => row.id === id);
if (itemInCurrentPage) {
this.selectedItems.delete(id);
}
}
}
// // idsinfos
this.ids = this.selectedIds; this.ids = Array.from(this.selectedItems.keys());
this.infos = selection.map(item => ({id: item.id})); this.infos = Array.from(this.selectedItems.values()).map(item => ({ id: item.id }));
this.single = this.ids.length !== 1; this.single = this.ids.length !== 1;
this.multiple = this.ids.length === 0; this.multiple = this.ids.length === 0;
}, },
// //
handleView(row){ handleView(row){
console.log(row)
let query = { Id:row.id,taskId: row.taskId,isView:"true" } let query = { Id:row.id,taskId: row.taskId,isView:"true" }
this.$tab.closeOpenPage({ this.$tab.closeOpenPage({
path: '/part/partAcceptDetail', path: '/part/partAcceptDetail',
@ -689,18 +606,25 @@ export default {
handleFileView(url) { handleFileView(url) {
if (!url) return; if (!url) return;
//
const fileExt = url.split('.').pop().toLowerCase(); const fileExt = url.split('.').pop().toLowerCase();
//
const imgExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; const imgExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const pdfExts = ['pdf']; const pdfExts = ['pdf'];
const docExts = ['doc', 'docx', 'xls', 'xlsx']; const docExts = ['doc', 'docx', 'xls', 'xlsx'];
if (imgExts.includes(fileExt)) { if (imgExts.includes(fileExt)) {
//
this.$viewerApi ? this.$viewerApi({ images: [url] }) : window.open(url); this.$viewerApi ? this.$viewerApi({ images: [url] }) : window.open(url);
} else if (pdfExts.includes(fileExt)) { } else if (pdfExts.includes(fileExt)) {
// PDF
window.open(url); window.open(url);
} else if (docExts.includes(fileExt)) { } else if (docExts.includes(fileExt)) {
// WordExcel
this.downloadFile(url); this.downloadFile(url);
} else { } else {
//
this.downloadFile(url); this.downloadFile(url);
} }
}, },
@ -745,6 +669,13 @@ export default {
}, },
// //
printCheck() { printCheck() {
// this.$refs.remarksPrintRefCheck.print()
// printJS({
// printable: 'checkId1',
// type: 'html',
// targetStyles: ['*']
// //
// })
this.$nextTick(() => { this.$nextTick(() => {
printJS({ printJS({
printable: 'checkId1', printable: 'checkId1',
@ -767,32 +698,40 @@ export default {
let context = canvas.getContext('2d') let context = canvas.getContext('2d')
canvas.width = canvas.width canvas.width = canvas.width
context.height = canvas.height context.height = canvas.height
// //
// context.clearRect(0, 0, canvas.width, canvas.height);
//let text = "XXX";
//let companyName = "XXX";
//
let width = canvas.width / 2 let width = canvas.width / 2
let height = canvas.height / 2 let height = canvas.height / 2
context.lineWidth = 3 context.lineWidth = 3
context.strokeStyle = '#f00' context.strokeStyle = '#f00'
context.beginPath() context.beginPath()
context.arc(width, height, 80, 0, Math.PI * 2) context.arc(width, height, 80, 0, Math.PI * 2) //
context.stroke() context.stroke()
//
this.create5star(context, width, height, 20, '#f00', 0) this.create5star(context, width, height, 20, '#f00', 0)
//
context.font = '100 13px 宋体' context.font = '100 13px 宋体'
context.textBaseline = 'middle' context.textBaseline = 'middle' //
context.textAlign = 'center' context.textAlign = 'center' //
context.lineWidth = 1 context.lineWidth = 1
context.strokeStyle = '#ff2f2f' context.strokeStyle = '#ff2f2f'
context.strokeText(text, width, height + 50) context.strokeText(text, width, height + 50)
context.translate(width, height) //
context.translate(width, height) // ,
context.font = '100 13px 宋体' context.font = '100 13px 宋体'
let count = companyName.length let count = companyName.length //
let angle = (4 * Math.PI) / (3 * (count - 1)) let angle = (4 * Math.PI) / (3 * (count - 1)) //
let chars = companyName.split('') let chars = companyName.split('')
let c let c
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
c = chars[i] c = chars[i] //
if (i == 0) { if (i == 0) {
context.rotate((5 * Math.PI) / 6) context.rotate((5 * Math.PI) / 6)
} else { } else {
@ -800,10 +739,10 @@ export default {
} }
context.save() context.save()
context.translate(65, 0) context.translate(65, 0) // ,x
context.rotate(Math.PI / 2) context.rotate(Math.PI / 2) // 90,x
context.strokeStyle = '#ff5050' context.strokeStyle = '#ff5050' //
context.strokeText(c, 0, 0) context.strokeText(c, 0, 0) //
context.restore() context.restore()
} }
}, },
@ -811,11 +750,14 @@ export default {
create5star(context, sx, sy, radius, color, rotato) { create5star(context, sx, sy, radius, color, rotato) {
context.save() context.save()
context.fillStyle = color context.fillStyle = color
context.translate(sx, sy) context.translate(sx, sy) //
context.rotate(Math.PI + rotato) context.rotate(Math.PI + rotato) //
context.beginPath() context.beginPath() //
// let x = Math.sin(0);
// let y = Math.cos(0);
let dig = (Math.PI / 5) * 4 let dig = (Math.PI / 5) * 4
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
//
let x = Math.sin(i * dig) let x = Math.sin(i * dig)
let y = Math.cos(i * dig) let y = Math.cos(i * dig)
context.lineTo(x * radius, y * radius) context.lineTo(x * radius, y * radius)
@ -827,14 +769,16 @@ export default {
}, },
// //
handleViewQrCode(row) { handleViewQrCode(row) {
console.log('🚀 ~ handleViewQrCode ~ row:', row)
this.rowObj = row this.rowObj = row
this.uploadOpen = true this.uploadOpen = true
this.qrCode = row.qrCode this.qrCode = row.qrCode
let str = 'http://ahjj.jypxks.com:9988/imw/backstage/machine/qrCodePage?qrcode=' + row.qrCode let str = 'http://ahjj.jypxks.com:9988/imw/backstage/machine/qrCodePage?qrcode=' + row.qrCode
console.log('🚀 ~ handleViewQrCode ~ str:', str)
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.codeItem.innerHTML = '' this.$refs.codeItem.innerHTML = ''
var qrcode = new QRCode(this.$refs.codeItem, { var qrcode = new QRCode(this.$refs.codeItem, {
text: str, text: str, //
width: 256, width: 256,
height: 256, height: 256,
colorDark: '#000000', colorDark: '#000000',
@ -848,9 +792,9 @@ export default {
const qrContainer = document.createElement('div') const qrContainer = document.createElement('div')
document.body.appendChild(qrContainer) document.body.appendChild(qrContainer)
const qrSize = 512 const qrSize = 512 //
const padding = 20 const padding = 20 //
const fontSize = 68 const fontSize = 68 //
const maxTextWidth = qrSize const maxTextWidth = qrSize
const qrcode = new QRCode(qrContainer, { const qrcode = new QRCode(qrContainer, {
@ -866,6 +810,7 @@ export default {
const img = qrContainer.querySelector('img') || qrContainer.querySelector('canvas') const img = qrContainer.querySelector('img') || qrContainer.querySelector('canvas')
const text = row.maCode || '' const text = row.maCode || ''
//
const ctxMeasure = document.createElement('canvas').getContext('2d') const ctxMeasure = document.createElement('canvas').getContext('2d')
ctxMeasure.font = `${fontSize}px Arial` ctxMeasure.font = `${fontSize}px Arial`
const words = text.split('') const words = text.split('')
@ -882,6 +827,7 @@ export default {
} }
lines.push(line) lines.push(line)
//
const lineHeight = fontSize + 10 const lineHeight = fontSize + 10
const qrCanvas = document.createElement('canvas') const qrCanvas = document.createElement('canvas')
qrCanvas.width = qrSize + padding * 2 qrCanvas.width = qrSize + padding * 2
@ -893,6 +839,7 @@ export default {
ctx.drawImage(img, padding, padding, qrSize, qrSize) ctx.drawImage(img, padding, padding, qrSize, qrSize)
//
ctx.fillStyle = '#000' ctx.fillStyle = '#000'
ctx.font = `${fontSize}px Arial` ctx.font = `${fontSize}px Arial`
ctx.textAlign = 'center' ctx.textAlign = 'center'
@ -900,6 +847,7 @@ export default {
ctx.fillText(ln, qrCanvas.width / 2, qrSize + padding + fontSize / 1.2 + index * lineHeight) ctx.fillText(ln, qrCanvas.width / 2, qrSize + padding + fontSize / 1.2 + index * lineHeight)
}) })
//
const a = document.createElement('a') const a = document.createElement('a')
a.href = qrCanvas.toDataURL('image/png') a.href = qrCanvas.toDataURL('image/png')
a.download = text + '.png' a.download = text + '.png'
@ -910,6 +858,7 @@ export default {
}, },
/** 单条下载 */ /** 单条下载 */
handleDownload(row) { handleDownload(row) {
//
const payload = { const payload = {
items: [ items: [
{ {
@ -937,38 +886,28 @@ export default {
zipName: row.proName || 'report', zipName: row.proName || 'report',
}; };
this.downloadSingleFile(payload, `${row.proName || '文件档案下载'}.zip`); this.downloadZip(
'/material/bm_report/downloadSingle',
JSON.stringify(payload),
`${row.proName || '文件档案下载'}.zip`
);
}, },
/** 一键下载(多选) */
/**
* 一键下载多选
*/
async handleBulkDownload() { async handleBulkDownload() {
const grouped = {};
try {
// //
if (this.selectedIds.length === 0) { if (!this.selectedItems || this.selectedItems.size === 0) {
this.$message.warning('请先勾选要下载的行'); this.$message.warning('请先勾选要下载的行');
return; return;
} }
console.log('开始下载,选中数量:', this.selectedIds.length); // items tableList
const items = Array.from(this.selectedItems.values()).map(item => ({
//
try {
await this.$confirm(`确定要下载 ${this.selectedIds.length} 个选中项吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
});
} catch (cancel) {
console.log('用户取消下载');
return;
}
//
this.initializeDownload();
try {
// - selectedData
const items = Object.values(this.selectedData).map(item => ({
proName: item.proName || '', proName: item.proName || '',
departName: item.departName || '', departName: item.departName || '',
typeName: item.typeName || '', typeName: item.typeName || '',
@ -990,254 +929,54 @@ export default {
remark: '' remark: ''
})); }));
console.log('构建了', items.length, '个下载项'); // -> -> -
items.forEach(item => {
const proName = item.proName || "";
const leaseDate = item.testTime || "";
const typeFolder = `${item.typeName || ""}-${item.typeModelName || ""}`;
if (!grouped[proName]) grouped[proName] = {};
if (!grouped[proName][leaseDate]) grouped[proName][leaseDate] = {};
if (!grouped[proName][leaseDate][typeFolder]) grouped[proName][leaseDate][typeFolder] = [];
grouped[proName][leaseDate][typeFolder].push(item);
});
//
const flatItems = [];
Object.keys(grouped).forEach(proName => {
Object.keys(grouped[proName]).forEach(leaseDate => {
Object.keys(grouped[proName][leaseDate]).forEach(typeFolder => {
grouped[proName][leaseDate][typeFolder].forEach(item => {
// leaseDate item.testTime使
flatItems.push({ ...item, testTime: leaseDate });
});
});
});
});
//
this.totalItems = this.calculateTotalFiles(items);
console.log('总文件数:', this.totalItems);
const taskId = crypto.randomUUID()
const payload = { const payload = {
items: items, items: flatItems,
taskId:taskId, zipName: `报告下载_${(new Date()).toISOString().slice(0,10)}`,
zipName: `报告下载_${new Date().getTime()}`,
stream: true
}; };
// this.downloadZip(
await this.streamDownload(payload); '/material/bm_report/downloadBulk',
JSON.stringify(payload),
payload.zipName + '.zip'
);
} catch (err) { } catch (err) {
console.error('一键下载失败', err); console.error('一键下载失败', err);
this.handleDownloadError(err); this.$message.error('一键下载失败');
}
},
/** 初始化下载状态 */
initializeDownload() {
this.isExporting = true;
this.showProgress = true;
this.downloadComplete = false;
this.progressPercentage = 0;
this.currentItem = 0;
this.totalItems = 0;
this.currentFileName = '';
this.progressText = '准备下载...';
this.progressStatus = null;
this.downloadSpeed = 0;
this.retryCount = 0;
this.downloadedBytes = 0;
this.downloadStartTime = Date.now();
this.downloadController = new AbortController();
},
/** 计算总文件数 */
calculateTotalFiles(items) {
let total = 0;
items.forEach(item => {
//
total += 1;
//
if (item.qualifiedUrl) total += 1;
if (item.testReportUrl) total += 1;
if (item.thirdReportUrl) total += 1;
if (item.factoryReportUrl) total += 1;
if (item.otherReportUrl) total += 1;
});
return total;
},
/** 流式下载方法 - 使用原始的request方式 */
/** 流式下载方法 - 使用原始的request方式 */
async streamDownload(payload) {
try {
this.progressText = '正在连接服务器...';
const response = await request({
url: '/material/bm_report/downloadBulkStream',
method: 'POST',
data: payload,
responseType: 'blob',
timeout: 0,
headers: {
encryptRequest: false,
checkIntegrity: false,
encryptResponse: false
},
onDownloadProgress: (progressEvent) => {
this.handleDownloadProgress(progressEvent);
}
});
//
const blob = new Blob([response]);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `${payload.zipName}.zip`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.handleDownloadComplete();
} catch (error) {
if (error.message && error.message.includes('canceled')) {
this.progressText = '下载已取消';
this.progressPercentage = 0;
this.progressStatus = 'exception';
} else {
console.error('下载失败:', error);
this.handleDownloadError(error);
} }
} }
},
/** 处理下载进度 */
handleDownloadProgress(progressEvent) {
if (progressEvent.total) {
// ZIP
const percentage = Math.round((progressEvent.loaded * 100) / progressEvent.total);
this.progressPercentage = Math.min(percentage, 99);
const elapsed = Date.now() - this.downloadStartTime;
if (elapsed > 0) {
this.downloadSpeed = progressEvent.loaded * 1000 / elapsed;
this.progressText = `${this.formatBytes(progressEvent.loaded)} / ${this.formatBytes(progressEvent.total)}`;
}
} else {
//
// 1. currentItem
if (this.totalItems > 0) {
this.currentItem = Math.min(this.currentItem + 1, this.totalItems);
}
// 2. 100
this.progressPercentage = Math.min(
Math.round((this.currentItem / this.totalItems) * 100),
100
);
this.progressText = `处理中:${this.currentItem}/${this.totalItems}`;
}
},
/** 处理下载完成 */
handleDownloadComplete() {
// 2.
this.currentItem = this.totalItems;
this.progressPercentage = 100;
this.progressText = '下载完成';
this.progressStatus = 'success';
this.downloadComplete = true;
this.isExporting = false;
this.downloadSpeed = 0;
this.$message({
message: '下载完成!',
type: 'success',
duration: 3000
});
},
/** 处理下载错误 */
handleDownloadError(error) {
console.error('下载失败:', error);
if (this.retryCount < this.maxRetries) {
this.retryCount++;
this.progressText = `下载失败,正在重试 (${this.retryCount}/${this.maxRetries})...`;
this.progressStatus = 'warning';
// 3
setTimeout(() => {
this.handleBulkDownload();
}, 3000);
} else {
this.progressText = '下载失败,请重试';
this.progressStatus = 'exception';
this.isExporting = false;
this.$message.error('下载失败: ' + (error.message || '未知错误'));
}
},
/** 取消下载 */
cancelDownload() {
if (this.downloadController) {
this.downloadController.abort();
}
this.isExporting = false;
this.showProgress = false;
this.$message.info('下载已取消');
},
/** 清除进度显示 */
clearProgress() {
this.showProgress = false;
this.downloadComplete = false;
this.progressPercentage = 0;
this.currentItem = 0;
this.totalItems = 0;
this.downloadSpeed = 0;
this.progressStatus = null;
this.currentFileName = '';
},
/** 格式化字节大小 */
formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
},
/** 单文件下载 */
async downloadSingleFile(payload, fileName) {
try {
this.isExporting = true;
const response = await request({
url: '/material/bm_report/downloadSingle',
method: 'post',
data: payload,
responseType: 'blob'
});
const blob = new Blob([response]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
this.$message.success('下载成功!');
} catch (error) {
console.error('下载失败:', error);
this.$message.error('下载失败');
} finally {
this.isExporting = false;
}
},
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.uploadImg { .uploadImg {
padding-top: 20px; padding-top: 20px;
@ -1245,175 +984,19 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.deviceCode { .deviceCode {
margin-top: 10px; margin-top: 10px;
padding-bottom: 20px; padding-bottom: 20px;
font-size: 18px; font-size: 18px;
} }
::v-deep.el-table .fixed-width .el-button--mini { ::v-deep.el-table .fixed-width .el-button--mini {
width: 60px !important; width: 60px !important;
margin-bottom: 10px; margin-bottom: 10px;
} }
//css //css
::v-deep.disabled { ::v-deep.disabled {
.el-upload--picture-card { .el-upload--picture-card {
display: none; display: none;
} }
} }
/* 按钮样式优化 */
.el-button {
transition: all 0.3s ease;
}
.el-button:disabled {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
cursor: not-allowed;
}
.el-button--mini {
padding: 7px 15px;
}
/* 下载按钮激活状态 */
.el-button:not(:disabled):hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
}
.el-button--primary:not(:disabled) {
background-color: #409eff;
border-color: #409eff;
}
/* 下载按钮加载状态 */
.el-button.is-loading {
position: relative;
pointer-events: none;
}
.el-button.is-loading:before {
content: '';
position: absolute;
left: -1px;
top: -1px;
right: -1px;
bottom: -1px;
border-radius: inherit;
background-color: rgba(255, 255, 255, 0.35);
}
/* 进度条容器样式 */
.progress-container {
margin: 20px 0;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.progress-info {
margin-top: 15px;
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.progress-text {
font-size: 14px;
color: #333;
font-weight: 500;
}
.progress-detail {
font-size: 12px;
color: #666;
}
.progress-speed {
font-size: 12px;
color: #409eff;
font-weight: 500;
}
.progress-actions {
margin-top: 15px;
display: flex;
justify-content: center;
gap: 10px;
}
/* Element UI Progress 自定义样式 */
::v-deep .el-progress-bar__inner {
background: linear-gradient(90deg, #409eff 0%, #67c23a 100%);
transition: width 0.3s ease;
}
::v-deep .el-progress-bar__innerText {
color: #fff;
font-weight: bold;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
/* 响应式调整 */
@media (max-width: 768px) {
.progress-container {
padding: 15px;
margin: 15px 0;
}
.progress-text {
font-size: 13px;
}
.progress-detail,
.progress-speed {
font-size: 11px;
}
.el-button--mini {
min-width: 80px;
padding: 5px 12px;
font-size: 12px;
}
}
/* 表格复选框高亮 */
::v-deep .el-table__row.selected-row {
background-color: #f0f9ff;
}
::v-deep .el-table__row:hover {
background-color: #f5f7fa;
}
/* 复选框样式修复 */
::v-deep .el-table .el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #409eff;
border-color: #409eff;
}
::v-deep .el-table .el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: #409eff;
border-color: #409eff;
}
</style> </style>