From ea6ad9ddc3d5b33897e93276282245f7023836ff Mon Sep 17 00:00:00 2001 From: spring <2396852758@qq.com> Date: 星期四, 28 八月 2025 17:45:28 +0800 Subject: [PATCH] 大数据市场分析 --- src/views/invoiceCollaboration/components/BatchDownloadDialog.vue | 704 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 704 insertions(+), 0 deletions(-) diff --git a/src/views/invoiceCollaboration/components/BatchDownloadDialog.vue b/src/views/invoiceCollaboration/components/BatchDownloadDialog.vue new file mode 100644 index 0000000..18e40a0 --- /dev/null +++ b/src/views/invoiceCollaboration/components/BatchDownloadDialog.vue @@ -0,0 +1,704 @@ +<template> + <el-dialog + :model-value="dialogVisible" + @update:model-value="$emit('update:dialogVisible', $event)" + title="鎵归噺涓嬭浇鍙戠エ" + width="800px" + :close-on-click-modal="false" + :close-on-press-escape="false" + > + <div class="batch-download-container"> + <!-- 鍙戠エ鍒楄〃淇℃伅 --> + <el-card class="invoice-list" shadow="never"> + <template #header> + <div class="card-header"> + <span>寰呬笅杞藉彂绁ㄥ垪琛�</span> + <el-tag type="info" size="small" style="margin-left: 10px"> + 鍏� {{ invoices.length }} 寮犲彂绁� + </el-tag> + </div> + </template> + + <el-table :data="invoices" style="width: 100%" max-height="300"> + <el-table-column prop="invoiceNo" label="鍙戠エ鍙风爜" width="120" /> + <el-table-column prop="buyerName" label="璐拱鏂�" width="150" /> + <el-table-column prop="amount" label="閲戦" width="100"> + <template #default="scope"> + 楼{{ scope.row.amount.toFixed(2) }} + </template> + </el-table-column> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)" size="small"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> + </el-table> + </el-card> + + <!-- 涓嬭浇閫夐」 --> + <el-card class="download-options" shadow="never"> + <template #header> + <div class="card-header">涓嬭浇閫夐」</div> + </template> + + <el-form :model="downloadOptions" label-width="120px"> + <!-- 鏂囦欢鏍煎紡 --> + <el-form-item label="鏂囦欢鏍煎紡"> + <el-radio-group v-model="downloadOptions.format"> + <el-radio label="html">HTML鏍煎紡</el-radio> + <el-radio label="excel">Excel鏍煎紡</el-radio> + <el-radio label="zip">ZIP鍘嬬缉鍖�</el-radio> + </el-radio-group> + </el-form-item> + + <!-- 涓嬭浇鍐呭 --> + <el-form-item label="涓嬭浇鍐呭"> + <el-checkbox-group v-model="downloadOptions.content"> + <el-checkbox label="invoice">鍙戠エ姝f湰</el-checkbox> + <el-checkbox label="details">鏄庣粏娓呭崟</el-checkbox> + <el-checkbox label="summary">姹囨�绘姤琛�</el-checkbox> + </el-checkbox-group> + </el-form-item> + + <!-- 鏂囦欢鍛藉悕 --> + <el-form-item label="鏂囦欢鍛藉悕"> + <el-select v-model="downloadOptions.naming" style="width: 100%"> + <el-option label="鍙戠エ鍙风爜_璐拱鏂瑰悕绉�" value="invoice_buyer" /> + <el-option label="鍙戠エ鍙风爜_鏃ユ湡" value="invoice_date" /> + <el-option label="璐拱鏂瑰悕绉癬鏃ユ湡" value="buyer_date" /> + <el-option label="鑷畾涔夊墠缂�" value="custom" /> + </el-select> + </el-form-item> + + <!-- 鑷畾涔夊墠缂� --> + <el-form-item v-if="downloadOptions.naming === 'custom'" label="鑷畾涔夊墠缂�"> + <el-input v-model="downloadOptions.customPrefix" placeholder="璇疯緭鍏ユ枃浠跺墠缂�" /> + </el-form-item> + + <!-- 鍘嬬缉閫夐」 --> + <el-form-item label="鍘嬬缉閫夐」"> + <el-checkbox v-model="downloadOptions.compress">鍚敤鍘嬬缉</el-checkbox> + <el-checkbox v-model="downloadOptions.password">璁剧疆瑙e帇瀵嗙爜</el-checkbox> + </el-form-item> + + <!-- 瑙e帇瀵嗙爜 --> + <el-form-item v-if="downloadOptions.password" label="瑙e帇瀵嗙爜"> + <el-input v-model="downloadOptions.extractPassword" placeholder="璇疯緭鍏ヨВ鍘嬪瘑鐮�" /> + </el-form-item> + </el-form> + </el-card> + + <!-- 涓嬭浇杩涘害 --> + <el-card v-if="downloading" class="download-progress" shadow="never"> + <template #header> + <div class="card-header">涓嬭浇杩涘害</div> + </template> + + <div class="progress-content"> + <el-progress + :percentage="downloadProgress" + :status="downloadProgress === 100 ? 'success' : ''" + :stroke-width="20" + /> + <div class="progress-text"> + {{ downloadProgress === 100 ? '涓嬭浇瀹屾垚' : `姝e湪涓嬭浇... ${downloadProgress}%` }} + </div> + <div class="progress-detail"> + {{ downloadProgress === 100 ? '鎵�鏈夊彂绁ㄤ笅杞藉畬鎴�' : `宸蹭笅杞� ${downloadedCount} 寮狅紝鍓╀綑 ${invoices.length - downloadedCount} 寮燻 }} + </div> + </div> + </el-card> + </div> + + <template #footer> + <div class="dialog-footer"> + <el-button @click="handleClose" :disabled="downloading">鍙栨秷</el-button> + <el-button + type="primary" + @click="handleBatchDownload" + :loading="downloading" + :disabled="!canDownload" + > + {{ downloading ? '涓嬭浇涓�...' : '寮�濮嬩笅杞�' }} + </el-button> + </div> + </template> + </el-dialog> +</template> + +<script setup> +import { ref, computed, watch } from 'vue'; +import { ElMessage } from 'element-plus'; + +// 瀹氫箟props +const props = defineProps({ + dialogVisible: { + type: Boolean, + default: false + }, + invoices: { + type: Array, + default: () => [] + } +}); + +// 瀹氫箟emits +const emit = defineEmits(['update:dialogVisible', 'success']); + +// 鍝嶅簲寮忔暟鎹� +const downloading = ref(false); +const downloadProgress = ref(0); +const downloadedCount = ref(0); +const downloadOptions = ref({ + format: 'pdf', + content: ['invoice'], + naming: 'invoice_buyer', + customPrefix: '', + compress: true, + password: false, + extractPassword: '' +}); + +// 璁$畻灞炴�� +const canDownload = computed(() => { + return props.invoices.length > 0 && !downloading.value; +}); + +// 鐩戝惉鍣� +watch(() => props.invoices, () => { + // 閲嶇疆涓嬭浇鐘舵�� + downloading.value = false; + downloadProgress.value = 0; + downloadedCount.value = 0; +}, { immediate: true }); + +// 鑾峰彇鐘舵�佺被鍨� +const getStatusType = (status) => { + const statusMap = { + 'issued': 'success', + 'pending': 'warning', + 'cancelled': 'danger' + }; + return statusMap[status] || 'info'; +}; + +// 鑾峰彇鐘舵�佹枃鏈� +const getStatusText = (status) => { + const statusMap = { + 'issued': '宸插紑绁�', + 'pending': '寰呭紑绁�', + 'cancelled': '宸蹭綔搴�' + }; + return statusMap[status] || '鏈煡'; +}; + +// 鍏抽棴瀵硅瘽妗� +const handleClose = () => { + if (!downloading.value) { + emit('update:dialogVisible', false); + } +}; + +// 鎵归噺涓嬭浇 +const handleBatchDownload = async () => { + if (downloading.value) return; + + try { + downloading.value = true; + downloadProgress.value = 0; + downloadedCount.value = 0; + + // 鎵归噺涓嬭浇杩囩▼ + if (downloadOptions.format === 'zip') { + // ZIP鏍煎紡锛氱敓鎴愬崟涓帇缂╁寘 + await generateZIPFile(); + downloadProgress.value = 100; + downloadedCount.value = props.invoices.length; + } else { + // 鍏朵粬鏍煎紡锛氶�愪釜涓嬭浇鏂囦欢 + const totalInvoices = props.invoices.length; + const progressStep = 100 / totalInvoices; + + for (let i = 0; i < totalInvoices; i++) { + // 鐢熸垚鍗曚釜鍙戠エ鏂囦欢 + await generateInvoiceFile(props.invoices[i], i); + + downloadedCount.value++; + downloadProgress.value = Math.round((i + 1) * progressStep); + + // 鐭殏寤惰繜锛岄伩鍏嶆祻瑙堝櫒闃诲 + await new Promise(resolve => setTimeout(resolve, 200)); + } + + // 鐢熸垚姹囨�绘枃浠� + if (downloadOptions.content.includes('summary')) { + await generateSummaryFile(); + } + } + + // 涓嬭浇瀹屾垚 + ElMessage.success(`鎴愬姛涓嬭浇 ${totalInvoices} 寮犲彂绁╜); + emit('success'); + + } catch (error) { + console.error('鎵归噺涓嬭浇澶辫触:', error); + ElMessage.error("鎵归噺涓嬭浇澶辫触锛岃閲嶈瘯"); + downloading.value = false; + downloadProgress.value = 0; + downloadedCount.value = 0; + } +}; + +// 鐢熸垚鍗曚釜鍙戠エ鏂囦欢 +const generateInvoiceFile = async (invoice, index) => { + try { + let fileContent, fileName, mimeType; + + if (downloadOptions.format === 'html') { + fileContent = generateHTMLContent(invoice); + fileName = `${getFileName(invoice, index)}.html`; + mimeType = 'text/html'; + } else if (downloadOptions.content.includes('details')) { + fileContent = generateExcelContent(invoice); + fileName = `${getFileName(invoice, index)}.csv`; + mimeType = 'text/csv'; + } else if (downloadOptions.format === 'excel') { + fileContent = generateExcelContent(invoice); + fileName = `${getFileName(invoice, index)}.csv`; + mimeType = 'text/csv'; + } else if (downloadOptions.format === 'zip') { + // ZIP鏍煎紡闇�瑕佺壒娈婂鐞嗭紝杩欓噷鍏堣烦杩� + return; + } + + // 鍒涘缓Blob瀵硅薄 + const blob = new Blob([fileContent], { type: mimeType }); + + // 鍒涘缓涓嬭浇閾炬帴 + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + + // 瑙﹀彂涓嬭浇 + document.body.appendChild(link); + link.click(); + + // 娓呯悊 + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + } catch (error) { + console.error(`鐢熸垚鍙戠エ鏂囦欢澶辫触: ${invoice.invoiceNo}`, error); + } +}; + +// 鐢熸垚ZIP鍘嬬缉鍖� +const generateZIPFile = async () => { + try { + // 鍔ㄦ�佸鍏SZip搴� + const JSZip = await import('jszip'); + const zip = new JSZip.default(); + + // 娣诲姞鍙戠エ鏂囦欢鍒癦IP + props.invoices.forEach((invoice, index) => { + let fileContent, fileName; + + if (downloadOptions.content.includes('invoice')) { + if (downloadOptions.content.includes('details')) { + fileContent = generateExcelContent(invoice); + fileName = `${getFileName(invoice, index)}.csv`; + } else { + fileContent = generateHTMLContent(invoice); + fileName = `${getFileName(invoice, index)}.html`; + } + zip.file(fileName, fileContent); + } + }); + + // 娣诲姞姹囨�绘枃浠� + if (downloadOptions.content.includes('summary')) { + const summaryContent = generateSummaryContent(); + zip.file('鍙戠エ姹囨��.csv', summaryContent); + } + + // 鐢熸垚ZIP鏂囦欢 + const zipBlob = await zip.generateAsync({ + type: 'blob', + compression: downloadOptions.compress ? 'DEFLATE' : 'STORE' + }); + + // 涓嬭浇ZIP鏂囦欢 + const fileName = `鍙戠エ鎵归噺涓嬭浇_${new Date().toISOString().split('T')[0]}.zip`; + const url = window.URL.createObjectURL(zipBlob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + } catch (error) { + console.error('鐢熸垚ZIP鏂囦欢澶辫触:', error); + ElMessage.error('ZIP鏂囦欢鐢熸垚澶辫触锛岃妫�鏌ユ槸鍚﹀畨瑁呬簡jszip搴�'); + } +}; + +// 鐢熸垚姹囨�绘枃浠� +const generateSummaryFile = async () => { + try { + const summaryContent = generateSummaryContent(); + const fileName = `鍙戠エ姹囨�籣${new Date().toISOString().split('T')[0]}.csv`; + + const blob = new Blob([summaryContent], { type: 'text/csv;charset=utf-8;' }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + } catch (error) { + console.error('鐢熸垚姹囨�绘枃浠跺け璐�:', error); + } +}; + +// 鑾峰彇鏂囦欢鍚� +const getFileName = (invoice, index) => { + let fileName = ''; + + if (downloadOptions.naming === 'invoice_buyer') { + fileName = `${invoice.invoiceNo}_${invoice.buyerName}`; + } else if (downloadOptions.naming === 'invoice_date') { + fileName = `${invoice.invoiceNo}_${invoice.invoiceDate}`; + } else if (downloadOptions.naming === 'buyer_date') { + fileName = `${invoice.buyerName}_${invoice.invoiceDate}`; + } else if (downloadOptions.naming === 'custom') { + fileName = `${downloadOptions.customPrefix || '鍙戠エ'}_${index + 1}`; + } + + // 娓呯悊鏂囦欢鍚嶄腑鐨勭壒娈婂瓧绗� + return fileName.replace(/[<>:"/\\|?*]/g, '_'); +}; + +// 鐢熸垚HTML鍐呭 +const generateHTMLContent = (invoice) => { + const content = `<!DOCTYPE html> +<html lang="zh-CN"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>鍙戠エ淇℃伅 - ${invoice.invoiceNo || 'N/A'}</title> + <style> + body { + font-family: 'Microsoft YaHei', Arial, sans-serif; + margin: 0; + padding: 20px; + background-color: #f5f5f5; + } + .invoice-container { + max-width: 800px; + margin: 0 auto; + background: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + overflow: hidden; + } + .invoice-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 30px; + text-align: center; + } + .invoice-header h1 { + margin: 0; + font-size: 28px; + font-weight: 300; + } + .invoice-header .subtitle { + margin-top: 10px; + opacity: 0.9; + font-size: 16px; + } + .invoice-content { + padding: 30px; + } + .info-section { + margin-bottom: 25px; + } + .info-section h3 { + color: #333; + border-bottom: 2px solid #667eea; + padding-bottom: 8px; + margin-bottom: 15px; + } + .info-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px; + } + .info-item { + display: flex; + align-items: center; + padding: 12px; + background: #f8f9fa; + border-radius: 6px; + border-left: 4px solid #667eea; + } + .info-label { + font-weight: bold; + color: #555; + min-width: 100px; + } + .info-value { + color: #333; + margin-left: 10px; + } + .amount-section { + background: #f8f9fa; + padding: 20px; + border-radius: 8px; + margin-top: 20px; + } + .amount-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + } + .amount-item { + text-align: center; + padding: 15px; + background: white; + border-radius: 6px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .amount-label { + font-size: 14px; + color: #666; + margin-bottom: 8px; + } + .amount-value { + font-size: 24px; + font-weight: bold; + color: #667eea; + } + .footer { + text-align: center; + padding: 20px; + color: #666; + border-top: 1px solid #eee; + margin-top: 20px; + } + @media print { + body { background: white; } + .invoice-container { box-shadow: none; } + } + </style> +</head> +<body> + <div class="invoice-container"> + <div class="invoice-header"> + <h1>鍙戠エ淇℃伅</h1> + <div class="subtitle">Invoice Information</div> + </div> + + <div class="invoice-content"> + <div class="info-section"> + <h3>鍩烘湰淇℃伅</h3> + <div class="info-grid"> + <div class="info-item"> + <span class="info-label">鍙戠エ鍙风爜:</span> + <span class="info-value">${invoice.invoiceNo || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">鍙戠エ浠g爜:</span> + <span class="info-value">${invoice.invoiceCode || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">寮�绁ㄦ棩鏈�:</span> + <span class="info-value">${invoice.invoiceDate || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">鐘舵��:</span> + <span class="info-value">${getStatusText(invoice.status)}</span> + </div> + </div> + </div> + + <div class="info-section"> + <h3>璐拱鏂逛俊鎭�</h3> + <div class="info-grid"> + <div class="info-item"> + <span class="info-label">璐拱鏂瑰悕绉�:</span> + <span class="info-value">${invoice.buyerName || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">璐拱鏂圭◣鍙�:</span> + <span class="info-value">${invoice.buyerTaxNo || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">璐拱鏂瑰湴鍧�:</span> + <span class="info-value">${invoice.buyerAddress || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">閾惰璐︽埛:</span> + <span class="info-value">${invoice.buyerBankAccount || 'N/A'}</span> + </div> + </div> + </div> + + <div class="info-section"> + <h3>閿�鍞柟淇℃伅</h3> + <div class="info-grid"> + <div class="info-item"> + <span class="info-label">閿�鍞柟鍚嶇О:</span> + <span class="info-value">${invoice.sellerName || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">閿�鍞柟绋庡彿:</span> + <span class="info-value">${invoice.sellerTaxNo || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">閿�鍞柟鍦板潃:</span> + <span class="info-value">${invoice.sellerAddress || 'N/A'}</span> + </div> + <div class="info-item"> + <span class="info-label">閾惰璐︽埛:</span> + <span class="info-value">${invoice.sellerBankAccount || 'N/A'}</span> + </div> + </div> + </div> + + <div class="amount-section"> + <h3>閲戦淇℃伅</h3> + <div class="amount-grid"> + <div class="amount-item"> + <div class="amount-label">閲戦</div> + <div class="amount-value">楼${(invoice.amount || 0).toFixed(2)}</div> + </div> + <div class="info-item"> + <div class="amount-label">绋庨</div> + <div class="amount-value">楼${(invoice.taxAmount || 0).toFixed(2)}</div> + </div> + <div class="amount-item"> + <div class="amount-label">浠风◣鍚堣</div> + <div class="amount-value">楼${(invoice.totalAmount || 0).toFixed(2)}</div> + </div> + </div> + </div> + + <div class="footer"> + <p>鐢熸垚鏃堕棿: ${new Date().toLocaleString()}</p> + <p>姝ゆ枃浠剁敱绯荤粺鑷姩鐢熸垚</p> + </div> + </div> + </div> +</body> +</html>`; + + return content; +}; + +// 鐢熸垚Excel鍐呭 +const generateExcelContent = (invoice) => { + const content = `鍙戠エ淇℃伅 +鍙戠エ鍙风爜,${invoice.invoiceNo || 'N/A'} +鍙戠エ浠g爜,${invoice.invoiceCode || 'N/A'} +寮�绁ㄦ棩鏈�,${invoice.invoiceDate || 'N/A'} +璐拱鏂瑰悕绉�,${invoice.buyerName || 'N/A'} +閿�鍞柟鍚嶇О,${invoice.sellerName || 'N/A'} +閲戦,${(invoice.amount || 0).toFixed(2)} +绋庨,${(invoice.taxAmount || 0).toFixed(2)} +浠风◣鍚堣,${(invoice.totalAmount || 0).toFixed(2)} +鐘舵��,${getStatusText(invoice.status)} +鍒涘缓鏃堕棿,${invoice.createTime || 'N/A'}`; + return content; +}; + +// 鐢熸垚姹囨�诲唴瀹� +const generateSummaryContent = () => { + let content = '鍙戠エ姹囨�绘姤琛╘n'; + content += '鍙戠エ鍙风爜,鍙戠エ浠g爜,寮�绁ㄦ棩鏈�,璐拱鏂瑰悕绉�,閿�鍞柟鍚嶇О,閲戦,绋庨,浠风◣鍚堣,鐘舵��,鍒涘缓鏃堕棿\n'; + + props.invoices.forEach(invoice => { + content += `${invoice.invoiceNo || 'N/A'},${invoice.invoiceCode || 'N/A'},${invoice.invoiceDate || 'N/A'},${invoice.buyerName || 'N/A'},${invoice.sellerName || 'N/A'},${(invoice.amount || 0).toFixed(2)},${(invoice.taxAmount || 0).toFixed(2)},${(invoice.totalAmount || 0).toFixed(2)},${getStatusText(invoice.status)},${invoice.createTime || 'N/A'}\n`; + }); + + // 娣诲姞鍚堣琛� + const totalAmount = props.invoices.reduce((sum, item) => sum + (item.amount || 0), 0); + const totalTaxAmount = props.invoices.reduce((sum, item) => sum + (item.taxAmount || 0), 0); + const totalSum = props.invoices.reduce((sum, item) => sum + (item.totalAmount || 0), 0); + + content += `鍚堣,,,,,${totalAmount.toFixed(2)},${totalTaxAmount.toFixed(2)},${totalSum.toFixed(2)},,`; + + return content; +}; +</script> + +<style scoped> +.batch-download-container { + padding: 0; +} + +.invoice-list, +.download-options, +.download-progress { + margin-bottom: 20px; +} + +.invoice-list:last-child, +.download-options:last-child, +.download-progress:last-child { + margin-bottom: 0; +} + +.card-header { + font-weight: bold; + font-size: 16px; + display: flex; + align-items: center; +} + +.progress-content { + padding: 20px 0; + text-align: center; +} + +.progress-text { + margin-top: 15px; + font-size: 16px; + font-weight: bold; + color: #409eff; +} + +.progress-detail { + margin-top: 10px; + color: #606266; + font-size: 14px; +} + +.dialog-footer { + text-align: right; +} + +.el-radio-group { + display: flex; + flex-direction: column; + gap: 10px; +} + +.el-checkbox-group { + display: flex; + flex-direction: column; + gap: 10px; +} +</style> -- Gitblit v1.9.3