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/DownloadDialog.vue | 580 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 580 insertions(+), 0 deletions(-) diff --git a/src/views/invoiceCollaboration/components/DownloadDialog.vue b/src/views/invoiceCollaboration/components/DownloadDialog.vue new file mode 100644 index 0000000..bd0ade3 --- /dev/null +++ b/src/views/invoiceCollaboration/components/DownloadDialog.vue @@ -0,0 +1,580 @@ +<template> + <el-dialog + :model-value="dialogVisible" + @update:model-value="$emit('update:dialogVisible', $event)" + title="涓嬭浇鍙戠エ" + width="600px" + :close-on-click-modal="false" + > + <div class="download-container"> + <!-- 鍙戠エ淇℃伅 --> + <el-card class="invoice-info" shadow="never"> + <template #header> + <div class="card-header"> + <span>鍙戠エ淇℃伅</span> + </div> + </template> + + <el-descriptions :column="2" border> + <el-descriptions-item label="鍙戠エ鍙风爜">{{ invoice.invoiceNo || '-' }}</el-descriptions-item> + <el-descriptions-item label="鍙戠エ浠g爜">{{ invoice.invoiceCode || '-' }}</el-descriptions-item> + <el-descriptions-item label="寮�绁ㄦ棩鏈�">{{ invoice.invoiceDate || '-' }}</el-descriptions-item> + <el-descriptions-item label="璐拱鏂�">{{ invoice.buyerName || '-' }}</el-descriptions-item> + <el-descriptions-item label="閲戦">{{ (invoice.amount || 0).toFixed(2) }} 鍏�</el-descriptions-item> + <el-descriptions-item label="浠风◣鍚堣">{{ (invoice.totalAmount || 0).toFixed(2) }} 鍏�</el-descriptions-item> + </el-descriptions> + </el-card> + + <!-- 涓嬭浇閫夐」 --> + <el-card class="download-options" shadow="never"> + <template #header> + <div class="card-header"> + <span>涓嬭浇閫夐」</span> + </div> + </template> + + <el-form :model="downloadOptions" label-width="120px"> + <el-form-item label="鏂囦欢鏍煎紡"> + <el-radio-group v-model="downloadOptions.format"> + <el-radio label="pdf">PDF鏍煎紡</el-radio> + <el-radio label="excel">Excel鏍煎紡</el-radio> + <el-radio label="image">鍥剧墖鏍煎紡</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="basic">鍩烘湰淇℃伅</el-checkbox> + <el-checkbox label="buyer">璐拱鏂逛俊鎭�</el-checkbox> + <el-checkbox label="seller">閿�鍞柟淇℃伅</el-checkbox> + <el-checkbox label="items">鍟嗗搧鏄庣粏</el-checkbox> + <el-checkbox label="summary">鍚堣淇℃伅</el-checkbox> + </el-checkbox-group> + </el-form-item> + + <el-form-item label="鏂囦欢鍛藉悕"> + <el-input + v-model="downloadOptions.fileName" + placeholder="璇疯緭鍏ユ枃浠跺悕锛堜笉鍖呭惈鎵╁睍鍚嶏級" + style="width: 100%" + /> + </el-form-item> + + <el-form-item label="姘村嵃璁剧疆"> + <el-switch + v-model="downloadOptions.watermark" + active-text="娣诲姞姘村嵃" + inactive-text="鏃犳按鍗�" + /> + </el-form-item> + + <el-form-item label="鍘嬬缉璁剧疆" v-if="downloadOptions.format === 'image'"> + <el-select v-model="downloadOptions.compression" placeholder="閫夋嫨鍘嬬缉璐ㄩ噺" style="width: 100%"> + <el-option label="楂樿川閲忥紙鏂囦欢杈冨ぇ锛�" value="high" /> + <el-option label="涓瓑璐ㄩ噺" value="medium" /> + <el-option label="浣庤川閲忥紙鏂囦欢杈冨皬锛�" value="low" /> + </el-select> + </el-form-item> + </el-form> + </el-card> + + <!-- 涓嬭浇杩涘害 --> + <el-card v-if="downloading" class="download-progress" shadow="never"> + <template #header> + <div class="card-header"> + <span>涓嬭浇杩涘害</span> + </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" v-if="downloadProgress < 100"> + <span>姝e湪鐢熸垚{{ getFormatText(downloadOptions.format) }}鏂囦欢...</span> + </div> + </div> + </el-card> + </div> + + <template #footer> + <div class="dialog-footer"> + <el-button @click="handleClose" :disabled="downloading">鍙栨秷</el-button> + <el-button + type="primary" + @click="handleDownload" + :loading="downloading" + :disabled="!canDownload" + > + {{ downloading ? '涓嬭浇涓�...' : '寮�濮嬩笅杞�' }} + </el-button> + </div> + </template> + </el-dialog> +</template> + +<script setup> +import { ref, reactive, computed, watch } from "vue"; +import { ElMessage } from "element-plus"; + +// Props +const props = defineProps({ + dialogVisible: { + type: Boolean, + default: false + }, + invoice: { + type: Object, + default: () => ({}) + } +}); + +// Emits +const emit = defineEmits(['update:dialogVisible', 'success']); + +// 鍝嶅簲寮忔暟鎹� +const downloading = ref(false); +const downloadProgress = ref(0); + +// 涓嬭浇閫夐」 +const downloadOptions = reactive({ + format: 'pdf', + content: ['basic', 'buyer', 'seller', 'items', 'summary'], + fileName: '', + watermark: true, + compression: 'medium' +}); + +// 璁$畻灞炴�� +const canDownload = computed(() => { + return downloadOptions.content.length > 0 && downloadOptions.fileName.trim() !== ''; +}); + +// 鐩戝惉鍙戠エ鍙樺寲锛岃嚜鍔ㄨ缃枃浠跺悕 +watch(() => props.invoice, (newInvoice) => { + if (newInvoice && newInvoice.invoiceNo) { + downloadOptions.fileName = `${newInvoice.invoiceNo}_${newInvoice.invoiceDate}`; + } +}, { immediate: true }); + +// 鑾峰彇鏍煎紡鏂囨湰 +const getFormatText = (format) => { + const formatMap = { + pdf: 'PDF', + excel: 'Excel', + image: '鍥剧墖' + }; + return formatMap[format] || format; +}; + +// 鍏抽棴瀵硅瘽妗� +const handleClose = () => { + if (downloading.value) { + ElMessage.warning("涓嬭浇杩涜涓紝璇风瓑寰呭畬鎴�"); + return; + } + emit('update:dialogVisible', false); + // 閲嶇疆鐘舵�� + downloading.value = false; + downloadProgress.value = 0; +}; + +// 寮�濮嬩笅杞� +const handleDownload = async () => { + if (!canDownload.value) { + ElMessage.warning("璇峰畬鍠勪笅杞介�夐」"); + return; + } + + downloading.value = true; + downloadProgress.value = 0; + + try { + // 妯℃嫙涓嬭浇杩囩▼ + const steps = [ + { progress: 20, message: "姝e湪楠岃瘉鍙戠エ淇℃伅..." }, + { progress: 40, message: "姝e湪鐢熸垚鏂囦欢鍐呭..." }, + { progress: 60, message: "姝e湪搴旂敤鏍煎紡璁剧疆..." }, + { progress: 80, message: "姝e湪鐢熸垚鏂囦欢..." }, + { progress: 100, message: "涓嬭浇瀹屾垚" } + ]; + + for (let i = 0; i < steps.length; i++) { + const step = steps[i]; + await new Promise(resolve => { + setTimeout(() => { + downloadProgress.value = step.progress; + resolve(); + }, 800); + }); + } + + // 鐢熸垚鐪熷疄鐨勬枃浠跺苟涓嬭浇 + await generateAndDownloadFile(); + + } catch (error) { + ElMessage.error("涓嬭浇澶辫触锛岃閲嶈瘯"); + downloading.value = false; + downloadProgress.value = 0; + } +}; + +// 鐢熸垚骞朵笅杞芥枃浠� +const generateAndDownloadFile = async () => { + try { + let fileContent, fileName, mimeType; + + if (downloadOptions.format === 'pdf') { + // 鐢熸垚PDF鍐呭锛堟ā鎷燂級 + fileContent = generatePDFContent(); + fileName = `${downloadOptions.fileName}.pdf`; + mimeType = 'application/pdf'; + } else if (downloadOptions.format === 'excel') { + // 鐢熸垚Excel鍐呭锛圕SV鏍煎紡锛屽吋瀹规�ф洿濂斤級 + fileContent = generateExcelContent(); + fileName = `${downloadOptions.fileName}.csv`; + mimeType = 'text/csv'; + } else if (downloadOptions.format === 'image') { + // 鐢熸垚鍥剧墖鍐呭锛圫VG鏍煎紡锛� + fileContent = generateImageContent(); + fileName = `${downloadOptions.fileName}.svg`; + mimeType = 'image/svg+xml'; + } else if (downloadOptions.format === 'zip') { + // 鐢熸垚ZIP鍘嬬缉鍖� + await generateZIPFile(); + return; // ZIP涓嬭浇瀹屾垚鍚庣洿鎺ヨ繑鍥� + } + + // 鍒涘缓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); + + ElMessage.success(`鍙戠エ涓嬭浇鎴愬姛锛佹枃浠跺悕锛�${fileName}`); + emit('success'); + + // 寤惰繜鍏抽棴瀵硅瘽妗� + setTimeout(() => { + handleClose(); + }, 1500); + + } catch (error) { + console.error('鏂囦欢鐢熸垚澶辫触:', error); + ElMessage.error("鏂囦欢鐢熸垚澶辫触锛岃閲嶈瘯"); + } +}; + +// 鐢熸垚ZIP鍘嬬缉鍖� +const generateZIPFile = async () => { + try { + // 鍔ㄦ�佸鍏SZip搴� + const JSZip = await import('jszip'); + const zip = new JSZip.default(); + + // 鏍规嵁閫夋嫨鐨勫唴瀹规坊鍔犳枃浠跺埌ZIP + if (downloadOptions.content.includes('basic')) { + const basicContent = generateBasicContent(); + zip.file('鍩烘湰淇℃伅.csv', basicContent); + } + + if (downloadOptions.content.includes('buyer')) { + const buyerContent = generateBuyerContent(); + zip.file('璐拱鏂逛俊鎭�.csv', buyerContent); + } + + if (downloadOptions.content.includes('seller')) { + const sellerContent = generateSellerContent(); + zip.file('閿�鍞柟淇℃伅.csv', sellerContent); + } + + if (downloadOptions.content.includes('items')) { + const itemsContent = generateItemsContent(); + zip.file('鍟嗗搧鏄庣粏.csv', itemsContent); + } + + if (downloadOptions.content.includes('summary')) { + const summaryContent = generateSummaryContent(); + zip.file('鍚堣淇℃伅.csv', summaryContent); + } + + // 鐢熸垚ZIP鏂囦欢 + const zipBlob = await zip.generateAsync({ + type: 'blob', + compression: 'DEFLATE' + }); + + // 涓嬭浇ZIP鏂囦欢 + const fileName = `${downloadOptions.fileName}.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); + + ElMessage.success(`鍙戠エ涓嬭浇鎴愬姛锛佹枃浠跺悕锛�${fileName}`); + emit('success'); + + // 寤惰繜鍏抽棴瀵硅瘽妗� + setTimeout(() => { + handleClose(); + }, 1500); + + } catch (error) { + console.error('ZIP鏂囦欢鐢熸垚澶辫触:', error); + ElMessage.error('ZIP鏂囦欢鐢熸垚澶辫触锛岃妫�鏌ユ槸鍚﹀畨瑁呬簡jszip搴�'); + } +}; + +// 鐢熸垚PDF鍐呭锛堟ā鎷燂級 +const generatePDFContent = () => { + const invoice = props.invoice; + const content = ` +%PDF-1.4 +1 0 obj +<< +/Type /Catalog +/Pages 2 0 R +>> +endobj + +2 0 obj +<< +/Type /Pages +/Kids [3 0 R] +/Count 1 +>> +endobj + +3 0 obj +<< +/Type /Page +/Parent 2 0 R +/MediaBox [0 0 612 792] +/Contents 4 0 R +>> +endobj + +4 0 obj +<< +/Length 200 +>> +stream +BT +/F1 12 Tf +72 720 Td +(鍙戠エ鍙风爜: ${invoice.invoiceNo || 'N/A'}) Tj +0 -20 Td +(寮�绁ㄦ棩鏈�: ${invoice.invoiceDate || 'N/A'}) Tj +0 -20 Td +(璐拱鏂�: ${invoice.buyerName || 'N/A'}) Tj +0 -20 Td +(閲戦: ${(invoice.amount || 0).toFixed(2)} 鍏�) Tj +0 -20 Td +(浠风◣鍚堣: ${(invoice.totalAmount || 0).toFixed(2)} 鍏�) Tj +ET +endstream +endobj + +xref +0 5 +0000000000 65535 f +0000000009 00000 n +0000000058 00000 n +0000000115 00000 n +0000000204 00000 n +trailer +<< +/Size 5 +/Root 1 0 R +>> +startxref +295 +%%EOF + `; + return content; +}; + +// 鐢熸垚Excel鍐呭锛圕SV鏍煎紡锛� +const generateExcelContent = () => { + const invoice = props.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; +}; + +// 鐢熸垚鍥剧墖鍐呭锛圫VG鏍煎紡锛� +const generateImageContent = () => { + const invoice = props.invoice; + const content = `<?xml version="1.0" encoding="UTF-8"?> +<svg width="600" height="400" xmlns="http://www.w3.org/2000/svg"> + <rect width="600" height="400" fill="white" stroke="black" stroke-width="2"/> + <text x="20" y="40" font-family="Arial" font-size="24" fill="black">鍙戠エ</text> + <text x="20" y="80" font-family="Arial" font-size="16" fill="black">鍙戠エ鍙风爜: ${invoice.invoiceNo || 'N/A'}</text> + <text x="20" y="110" font-family="Arial" font-size="16" fill="black">寮�绁ㄦ棩鏈�: ${invoice.invoiceDate || 'N/A'}</text> + <text x="20" y="140" font-family="Arial" font-size="16" fill="black">璐拱鏂�: ${invoice.buyerName || 'N/A'}</text> + <text x="20" y="170" font-family="Arial" font-size="16" fill="black">閲戦: ${(invoice.amount || 0).toFixed(2)} 鍏�</text> + <text x="20" y="200" font-family="Arial" font-size="16" fill="black">浠风◣鍚堣: ${(invoice.totalAmount || 0).toFixed(2)} 鍏�</text> + <text x="20" y="230" font-family="Arial" font-size="16" fill="black">鐘舵��: ${getStatusText(invoice.status)}</text> +</svg>`; + return content; +}; + +// 鑾峰彇鐘舵�佹枃鏈� +const getStatusText = (status) => { + const statusMap = { + 'draft': '鑽夌', + 'pending': '寰呭紑绁�', + 'issuing': '寮�绁ㄤ腑', + 'issued': '宸插紑绁�', + 'failed': '寮�绁ㄥけ璐�', + 'cancelled': '宸蹭綔搴�' + }; + return statusMap[status] || status; +}; + +// 鐢熸垚鍩烘湰淇℃伅鍐呭 +const generateBasicContent = () => { + const invoice = props.invoice; + return `鍩烘湰淇℃伅 +鍙戠エ鍙风爜,${invoice.invoiceNo || 'N/A'} +鍙戠エ浠g爜,${invoice.invoiceCode || 'N/A'} +寮�绁ㄦ棩鏈�,${invoice.invoiceDate || 'N/A'} +鐘舵��,${getStatusText(invoice.status)} +鍒涘缓鏃堕棿,${invoice.createTime || 'N/A'}`; +}; + +// 鐢熸垚璐拱鏂逛俊鎭唴瀹� +const generateBuyerContent = () => { + const invoice = props.invoice; + return `璐拱鏂逛俊鎭� +璐拱鏂瑰悕绉�,${invoice.buyerName || 'N/A'} +璐拱鏂圭◣鍙�,${invoice.buyerTaxNo || 'N/A'} +璐拱鏂瑰湴鍧�,${invoice.buyerAddress || 'N/A'} +璐拱鏂归摱琛岃处鎴�,${invoice.buyerBankAccount || 'N/A'}`; +}; + +// 鐢熸垚閿�鍞柟淇℃伅鍐呭 +const generateSellerContent = () => { + const invoice = props.invoice; + return `閿�鍞柟淇℃伅 +閿�鍞柟鍚嶇О,${invoice.sellerName || 'N/A'} +閿�鍞柟绋庡彿,${invoice.sellerTaxNo || 'N/A'} +閿�鍞柟鍦板潃,${invoice.sellerAddress || 'N/A'} +閿�鍞柟閾惰璐︽埛,${invoice.sellerBankAccount || 'N/A'}`; +}; + +// 鐢熸垚鍟嗗搧鏄庣粏鍐呭 +const generateItemsContent = () => { + const invoice = props.invoice; + if (!invoice.items || invoice.items.length === 0) { + return `鍟嗗搧鏄庣粏 +鏆傛棤鍟嗗搧鏄庣粏淇℃伅`; + } + + let content = '鍟嗗搧鏄庣粏\n鍟嗗搧鍚嶇О,瑙勬牸鍨嬪彿,鏁伴噺,鍗曚环,閲戦,绋庣巼,绋庨,浠风◣鍚堣\n'; + invoice.items.forEach(item => { + content += `${item.name || 'N/A'},${item.spec || 'N/A'},${item.quantity || 0},${(item.price || 0).toFixed(2)},${(item.amount || 0).toFixed(2)},${(item.taxRate || 0).toFixed(2)}%,${(item.taxAmount || 0).toFixed(2)},${(item.totalAmount || 0).toFixed(2)}\n`; + }); + + return content; +}; + +// 鐢熸垚鍚堣淇℃伅鍐呭 +const generateSummaryContent = () => { + const invoice = props.invoice; + return `鍚堣淇℃伅 +閲戦鍚堣,${(invoice.amount || 0).toFixed(2)} 鍏� +绋庨鍚堣,${(invoice.taxAmount || 0).toFixed(2)} 鍏� +浠风◣鍚堣,${(invoice.totalAmount || 0).toFixed(2)} 鍏� +澶囨敞,${invoice.remark || 'N/A'}`; +}; +</script> + +<style scoped> +.download-container { + padding: 0; +} + +.invoice-info, +.download-options, +.download-progress { + margin-bottom: 20px; +} + +.invoice-info:last-child, +.download-options:last-child, +.download-progress:last-child { + margin-bottom: 0; +} + +.card-header { + font-weight: bold; + font-size: 16px; +} + +.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-checkbox-group { + display: flex; + flex-direction: column; + gap: 10px; +} + +.el-radio-group { + display: flex; + flex-direction: column; + gap: 10px; +} +</style> \ No newline at end of file -- Gitblit v1.9.3