From 0d14a3d3497f9f968e6a3c3177067aa6fe94a54c Mon Sep 17 00:00:00 2001
From: hongchao <3228015117@qq.com>
Date: Wed, 10 Dec 2025 17:03:03 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9C=AA=E7=BB=93=E7=AE=97=E6=8A=A5=E8=A1=A8zi?=
=?UTF-8?q?p=E5=AF=BC=E5=87=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../material/cost/component/unreportHome.vue | 558 +++++++++++-------
1 file changed, 351 insertions(+), 207 deletions(-)
diff --git a/src/views/material/cost/component/unreportHome.vue b/src/views/material/cost/component/unreportHome.vue
index 85d98f3d..f04b40ce 100644
--- a/src/views/material/cost/component/unreportHome.vue
+++ b/src/views/material/cost/component/unreportHome.vue
@@ -38,17 +38,52 @@
重置
导出Excel
查看历史报表
- {{ exporting ? '导出中...' : '批量导出ZIP' }}
-
-
-
{{ progress }}% ({{ current }}/{{ totalZip }})
-
{{ statusText }}
-
+
+ {{ exportButtonText }}
+
+
+
+
+
+
+ {{ progressText }}
+ {{ currentItem }}/{{ totalItems }} {{ currentFileName }}
+
+ 速度: {{ formatBytes(downloadSpeed) }}/s
+
+
+
+
+ 取消导出
+
+
+ 完成
+
+
+
+
0 && !this.isExporting;
+ console.log('canDownload检查:', {
+ ids: this.ids.length,
+ isExporting: this.isExporting,
+ can: can
+ });
+ return can;
+ },
+
+ /** 下载按钮文字 */
+ exportButtonText() {
+ if (this.isExporting) {
+ return `导出中 ${this.progressPercentage}%`;
+ }
+ return `批量导出ZIP${this.ids.length > 0 ? ` (${this.ids.length})` : ''}`;
+ },
+ },
created() {
this.GetUnitData()
this.GetProData()
@@ -841,113 +903,221 @@ export default {
//********************************************************************** */
//批量导出zip
- // exportZip() {
- // if (!this.ids.length) {
- // this.$message.error('请选择要导出的记录')
- // return false
- // }
- // let param = []
- // this.ids.map(item => {
- // param.push({ agreementId: item.agreementId,settlementType:item.settlementType })
- // })
- // // exportLeaseAll(this.exportParams).then(res => {
- // // downloadFile({ fileName: `月结明细_${new Date().getTime()}.zip`, fileData: res, fileType: 'application/zip;charset=utf-8' })
- // // })
- // this.downloadZip(
- // 'material/slt_agreement_info/exportUnsettled',
- // JSON.stringify(param),
- // `未结算批量明细导出_${new Date().getTime()}.zip`,
- // { background: true, showLoading: false, timeout: 600000 }
- // )
-
- // this.$refs.tableRef.clearSelection()
- // },
-
async exportZip() {
if (!this.ids.length) {
this.$message.error('请选择要导出的记录')
return false
}
- let param = []
- this.ids.map(item => {
- param.push({ agreementId: item.agreementId,settlementType:item.settlementType })
- })
+ // 确认对话框
try {
- this.exporting = true
- this.progress = 0
- this.current = 0
- this.totalZip = 0
- this.statusText = '开始导出...'
-
-
- const response = await getExportZipUnsettled(param)
- const result = response
- this.taskId = result.taskId
- this.totalZip = result.total
- this.statusText = '导出任务已开始...'
- this.exportFlag = true
- // 开始轮询进度
- this.startPolling()
- } catch (error) {
- console.error('导出请求失败', error)
- this.statusText = '导出请求失败'
- this.exporting = false
+ await this.$confirm(`确定要导出 ${this.ids.length} 个选中项吗?`, '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ center: true
+ });
+ } catch (cancel) {
+ console.log('用户取消导出');
+ return;
}
- this.$refs.tableRef.clearSelection()
- },
- startPolling() {
- this.pollInterval = setInterval(async () => {
- try {
- const param = { taskId: this.taskId }
- const response = await getExportZipProgress(param)
- const progress = response
- this.progress = progress.percentage
- this.current = progress.current
-
- if (progress.status === 'completed') {
- this.statusText = '导出完成,开始下载...'
- clearInterval(this.pollInterval)
- this.downloadFile()
- this.exporting = false
- this.exportFlag = false
- } else if (progress.status === 'error') {
- this.statusText = `导出失败: ${progress.errorMsg}`
- clearInterval(this.pollInterval)
- this.exporting = false
- } else {
- this.statusText = `处理中: ${progress.percentage}%`
- }
- } catch (error) {
- console.error('查询进度失败', error)
- this.statusText = '查询进度失败'
- this.exportFlag = false
- }
- }, 5000)
- },
-
- downloadFile() {
- if (this.taskId) {
- console.log("xxxxxxxxxxtaskId",this.taskId)
- const param = { taskId: this.taskId }
- this.downloadZip(
- 'material/slt_agreement_info/dlExProgress',
- JSON.stringify(param),
- `未结算批量明细导出_${new Date().getTime()}.zip`,
- )
+ // 初始化下载状态
+ this.initializeDownload();
+ try {
+ let param = []
+ this.ids.map(item => {
+ param.push({ agreementId: item.agreementId,settlementType:item.settlementType })
+ })
+ this.totalItems = param.length;
+
+ const taskId = crypto.randomUUID()
+ const payload = {
+ list: param,
+ taskId:taskId,
+ zipName: `未结算批量明细导出_${new Date().getTime()}`,
+ stream: true
+ };
+
+ // 开始下载
+ await this.streamDownload(payload);
+ this.$refs.tableRef.clearSelection()
+ } catch (err) {
+ console.error('未结算批量明细导出失败', err);
+ this.handleDownloadError(err);
}
},
-
- beforeUnmount() {
- if (this.pollInterval) {
- clearInterval(this.pollInterval)
+
+ /** 初始化下载状态 */
+ 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();
+ },
+
+ /** 流式下载方法 - 使用原始的request方式 */
+ async streamDownload(payload) {
+ try {
+ this.progressText = '正在连接服务器...';
+
+ // 使用原始的request方法
+ const response = await request({
+ url: '/material/slt_agreement_info/dlExProgress',
+ method: 'POST',
+ data: payload,
+ responseType: 'blob',
+ timeout: 0,
+ 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.exportZip();
+ }, 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];
+ },
+
//********************************************************************** */
@@ -1707,41 +1877,97 @@ export default {
}
//导出zip
+/* 进度条容器样式 */
.progress-container {
- margin-top: 20px;
- padding: 15px;
+ margin: 20px 0;
+ padding: 20px;
border: 1px solid #e0e0e0;
- border-radius: 4px;
+ border-radius: 8px;
+ background-color: #f9f9f9;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ animation: fadeIn 0.3s ease;
}
-.progress-bar {
- width: 100%;
- height: 20px;
- background-color: #f0f0f0;
- border-radius: 10px;
- overflow: hidden;
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
-.progress-fill {
- height: 100%;
- background-color: #4CAF50;
- transition: width 0.3s ease;
+.progress-info {
+ margin-top: 15px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
}
.progress-text {
- margin-top: 8px;
- text-align: center;
font-size: 14px;
+ color: #333;
+ font-weight: 500;
+}
+
+.progress-detail {
+ font-size: 12px;
color: #666;
}
-.status-text {
- margin-top: 5px;
- text-align: center;
+.progress-speed {
font-size: 12px;
- color: #999;
+ 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;
+ }
+}
+
+
+
button:disabled {
opacity: 0.6;
cursor: not-allowed;
@@ -1749,85 +1975,3 @@ button:disabled {
//
-
-
-
\ No newline at end of file