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