From 92e38481fc2f68dcd540434c6428d790b470a84d Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 28 八月 2025 15:42:44 +0800
Subject: [PATCH] 发票协同
---
src/views/invoiceCollaboration/components/InvoiceViewDialog.vue | 291 +++++
src/views/invoiceCollaboration/index.vue | 554 +++++++++++
package.json | 1
src/views/invoiceCollaboration/components/TaxControlSyncDialog.vue | 362 +++++++
src/views/invoiceCollaboration/components/BatchDownloadDialog.vue | 704 ++++++++++++++
src/views/invoiceCollaboration/components/InvoiceDialog.vue | 484 +++++++++
src/views/invoiceCollaboration/components/DownloadDialog.vue | 580 +++++++++++
7 files changed, 2,976 insertions(+), 0 deletions(-)
diff --git a/package.json b/package.json
index b241ca8..6d2877a 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
+ "jszip": "^3.10.1",
"nprogress": "0.2.0",
"pinia": "2.1.7",
"print-js": "^1.6.0",
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>
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
diff --git a/src/views/invoiceCollaboration/components/InvoiceDialog.vue b/src/views/invoiceCollaboration/components/InvoiceDialog.vue
new file mode 100644
index 0000000..3b8f2a6
--- /dev/null
+++ b/src/views/invoiceCollaboration/components/InvoiceDialog.vue
@@ -0,0 +1,484 @@
+<template>
+ <el-dialog
+ :model-value="dialogFormVisible"
+ @update:model-value="$emit('update:dialogFormVisible', $event)"
+ :title="title"
+ width="1200px"
+ :close-on-click-modal="false"
+ @close="handleClose"
+ >
+ <el-form
+ ref="formRef"
+ :model="formData"
+ :rules="rules"
+ label-width="120px"
+ class="invoice-form"
+ >
+ <!-- 璐拱鏂逛俊鎭� -->
+ <el-card class="buyer-card" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>璐拱鏂逛俊鎭�</span>
+ </div>
+ </template>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="璐拱鏂瑰悕绉�" prop="buyerName">
+ <el-input
+ v-model="formData.buyerName"
+ placeholder="璇疯緭鍏ヨ喘涔版柟鍚嶇О"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绾崇◣浜鸿瘑鍒彿" prop="buyerTaxNo">
+ <el-input
+ v-model="formData.buyerTaxNo"
+ placeholder="璇疯緭鍏ョ撼绋庝汉璇嗗埆鍙�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍦板潃鐢佃瘽" prop="buyerAddress">
+ <el-input
+ v-model="formData.buyerAddress"
+ placeholder="璇疯緭鍏ュ湴鍧�鐢佃瘽"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯鍙婅处鍙�" prop="buyerBankAccount">
+ <el-input
+ v-model="formData.buyerBankAccount"
+ placeholder="璇疯緭鍏ュ紑鎴疯鍙婅处鍙�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 閿�鍞柟淇℃伅 -->
+ <el-card class="seller-card" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>閿�鍞柟淇℃伅</span>
+ </div>
+ </template>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閿�鍞柟鍚嶇О" prop="sellerName">
+ <el-input
+ v-model="formData.sellerName"
+ placeholder="璇疯緭鍏ラ攢鍞柟鍚嶇О"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绾崇◣浜鸿瘑鍒彿" prop="sellerTaxNo">
+ <el-input
+ v-model="formData.sellerTaxNo"
+ placeholder="璇疯緭鍏ョ撼绋庝汉璇嗗埆鍙�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍦板潃鐢佃瘽" prop="sellerAddress">
+ <el-input
+ v-model="formData.sellerAddress"
+ placeholder="璇疯緭鍏ュ湴鍧�鐢佃瘽"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯鍙婅处鍙�" prop="sellerBankAccount">
+ <el-input
+ v-model="formData.sellerBankAccount"
+ placeholder="璇疯緭鍏ュ紑鎴疯鍙婅处鍙�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鍟嗗搧鏄庣粏 -->
+ <el-card class="items-card" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>鍟嗗搧鏄庣粏</span>
+ <el-button type="primary" size="small" @click="addItem">
+ 娣诲姞鍟嗗搧
+ </el-button>
+ </div>
+ </template>
+
+ <el-table :data="formData.items" border style="width: 100%">
+ <el-table-column label="鍟嗗搧鍚嶇О" width="200">
+ <template #default="scope">
+ <el-input
+ v-model="scope.row.name"
+ placeholder="鍟嗗搧鍚嶇О"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="瑙勬牸鍨嬪彿" width="150">
+ <template #default="scope">
+ <el-input
+ v-model="scope.row.specification"
+ placeholder="瑙勬牸鍨嬪彿"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" width="100">
+ <template #default="scope">
+ <el-input
+ v-model="scope.row.unit"
+ placeholder="鍗曚綅"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鏁伴噺" width="120">
+ <template #default="scope">
+ <el-input
+ v-model.number="scope.row.quantity"
+ placeholder="鏁伴噺"
+ type="number"
+ @input="calculateItemAmount(scope.$index)"
+ style="width: 100%"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚环" width="120">
+ <template #default="scope">
+ <el-input
+ v-model.number="scope.row.unitPrice"
+ placeholder="鍗曚环"
+ type="number"
+ @input="calculateItemAmount(scope.$index)"
+ style="width: 100%"
+ >
+ <template v-slot:suffix>
+ <span>鍏�</span>
+ </template>
+ </el-input>
+ </template>
+ </el-table-column>
+ <el-table-column label="閲戦" width="120">
+ <template #default="scope">
+ <span>{{ (scope.row.amount || 0).toFixed(2) }} 鍏�</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庣巼" width="120">
+ <template #default="scope">
+ <el-select
+ v-model="scope.row.taxRate"
+ placeholder="閫夋嫨绋庣巼"
+ @change="calculateItemAmount(scope.$index)"
+ style="width: 100%"
+ >
+ <el-option label="0%" value="0" />
+ <el-option label="1%" value="0.01" />
+ <el-option label="3%" value="0.03" />
+ <el-option label="6%" value="0.06" />
+ <el-option label="9%" value="0.09" />
+ <el-option label="13%" value="0.13" />
+ </el-select>
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庨" width="120">
+ <template #default="scope">
+ <span>{{ (scope.row.taxAmount || 0).toFixed(2) }} 鍏�</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="浠风◣鍚堣" width="120">
+ <template #default="scope">
+ <span>{{ (scope.row.totalAmount || 0).toFixed(2) }} 鍏�</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80">
+ <template #default="scope">
+ <el-button
+ type="danger"
+ size="small"
+ @click="removeItem(scope.$index)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍚堣淇℃伅 -->
+ <div class="summary-info">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <span class="summary-label">閲戦鍚堣锛�</span>
+ <span class="summary-value">{{ totalAmount.toFixed(2) }} 鍏�</span>
+ </el-col>
+ <el-col :span="6">
+ <span class="summary-label">绋庨鍚堣锛�</span>
+ <span class="summary-value">{{ totalTaxAmount.toFixed(2) }} 鍏�</span>
+ </el-col>
+ <el-col :span="6">
+ <span class="summary-label">浠风◣鍚堣锛�</span>
+ <span class="summary-value">{{ totalTotalAmount.toFixed(2) }} 鍏�</span>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
+ <!-- 澶囨敞淇℃伅 -->
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="formData.remark"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ />
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="handleClose">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSubmit" :loading="submitLoading">
+ 鎻愪氦
+ </el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, watch, computed } from "vue";
+import { ElMessage } from "element-plus";
+
+// Props
+const props = defineProps({
+ dialogFormVisible: {
+ type: Boolean,
+ default: false
+ },
+ form: {
+ type: Object,
+ default: () => ({})
+ },
+ title: {
+ type: String,
+ default: ""
+ },
+ isEdit: {
+ type: Boolean,
+ default: false
+ }
+});
+
+// Emits
+const emit = defineEmits(['update:dialogFormVisible', 'update:form', 'submit', 'success']);
+
+// 鍝嶅簲寮忔暟鎹�
+const formRef = ref(null);
+const submitLoading = ref(false);
+
+// 琛ㄥ崟鏁版嵁
+const formData = reactive({
+ buyerName: "",
+ buyerTaxNo: "",
+ buyerAddress: "",
+ buyerBankAccount: "",
+ sellerName: "鏈叕鍙�",
+ sellerTaxNo: "123456789012345678",
+ sellerAddress: "鍏徃鍦板潃",
+ sellerBankAccount: "閾惰璐︽埛",
+ items: [],
+ remark: ""
+});
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const rules = {
+ buyerName: [
+ { required: true, message: "璇疯緭鍏ヨ喘涔版柟鍚嶇О", trigger: "blur" }
+ ],
+ buyerTaxNo: [
+ { required: true, message: "璇疯緭鍏ョ撼绋庝汉璇嗗埆鍙�", trigger: "blur" }
+ ],
+ items: [
+ {
+ type: "array",
+ required: true,
+ message: "璇疯嚦灏戞坊鍔犱竴涓晢鍝�",
+ trigger: "change",
+ validator: (rule, value, callback) => {
+ if (!value || value.length === 0) {
+ callback(new Error("璇疯嚦灏戞坊鍔犱竴涓晢鍝�"));
+ } else {
+ callback();
+ }
+ }
+ }
+ ]
+};
+
+// 璁$畻灞炴��
+const totalAmount = computed(() => {
+ return formData.items.reduce((sum, item) => sum + (item.amount || 0), 0);
+});
+
+const totalTaxAmount = computed(() => {
+ return formData.items.reduce((sum, item) => sum + (item.taxAmount || 0), 0);
+});
+
+const totalTotalAmount = computed(() => {
+ return formData.items.reduce((sum, item) => sum + (item.totalAmount || 0), 0);
+});
+
+// 鐩戝惉琛ㄥ崟鏁版嵁鍙樺寲
+watch(() => props.form, (newVal) => {
+ Object.assign(formData, newVal);
+ if (!formData.items || formData.items.length === 0) {
+ formData.items = [];
+ }
+}, { deep: true, immediate: true });
+
+// 娣诲姞鍟嗗搧
+const addItem = () => {
+ formData.items.push({
+ name: "",
+ specification: "",
+ unit: "",
+ quantity: 0,
+ unitPrice: 0,
+ amount: 0,
+ taxRate: "0.13",
+ taxAmount: 0,
+ totalAmount: 0
+ });
+};
+
+// 鍒犻櫎鍟嗗搧
+const removeItem = (index) => {
+ formData.items.splice(index, 1);
+};
+
+// 璁$畻鍟嗗搧閲戦
+const calculateItemAmount = (index) => {
+ const item = formData.items[index];
+ if (item.quantity && item.unitPrice) {
+ item.amount = item.quantity * item.unitPrice;
+ item.taxAmount = item.amount * parseFloat(item.taxRate);
+ item.totalAmount = item.amount + item.taxAmount;
+ }
+};
+
+// 鍏抽棴瀵硅瘽妗�
+const handleClose = () => {
+ emit('update:dialogFormVisible', false);
+ formRef.value?.resetFields();
+};
+
+// 鎻愪氦琛ㄥ崟
+const handleSubmit = async () => {
+ if (!formRef.value) return;
+
+ try {
+ await formRef.value.validate();
+
+ // 楠岃瘉鍟嗗搧淇℃伅
+ if (formData.items.length === 0) {
+ ElMessage.warning("璇疯嚦灏戞坊鍔犱竴涓晢鍝�");
+ return;
+ }
+
+ for (let item of formData.items) {
+ if (!item.name) {
+ ElMessage.warning("璇疯緭鍏ュ晢鍝佸悕绉�");
+ return;
+ }
+ if (!item.quantity || item.quantity <= 0) {
+ ElMessage.warning("璇疯緭鍏ユ湁鏁堢殑鍟嗗搧鏁伴噺");
+ return;
+ }
+ if (!item.unitPrice || item.unitPrice <= 0) {
+ ElMessage.warning("璇疯緭鍏ユ湁鏁堢殑鍟嗗搧鍗曚环");
+ return;
+ }
+ }
+
+ submitLoading.value = true;
+
+ // 妯℃嫙鎻愪氦
+ setTimeout(() => {
+ submitLoading.value = false;
+ ElMessage.success("鎻愪氦鎴愬姛");
+ emit('submit', { ...formData });
+ handleClose();
+ }, 1000);
+
+ } catch (error) {
+ console.error('琛ㄥ崟楠岃瘉澶辫触:', error);
+ }
+};
+</script>
+
+<style scoped>
+.invoice-form {
+ padding: 20px 0;
+}
+
+.buyer-card,
+.seller-card,
+.items-card {
+ margin-bottom: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: bold;
+}
+
+.summary-info {
+ margin-top: 15px;
+ padding: 15px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+}
+
+.summary-label {
+ font-weight: bold;
+ margin-right: 10px;
+}
+
+.summary-value {
+ color: #409eff;
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+.el-table {
+ margin-top: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/invoiceCollaboration/components/InvoiceViewDialog.vue b/src/views/invoiceCollaboration/components/InvoiceViewDialog.vue
new file mode 100644
index 0000000..72e7091
--- /dev/null
+++ b/src/views/invoiceCollaboration/components/InvoiceViewDialog.vue
@@ -0,0 +1,291 @@
+<template>
+ <el-dialog
+ :model-value="dialogViewVisible"
+ @update:model-value="$emit('update:dialogViewVisible', $event)"
+ :title="title"
+ width="1000px"
+ :close-on-click-modal="false"
+ >
+ <div class="invoice-view">
+ <!-- 鍩烘湰淇℃伅 -->
+ <el-card class="basic-info" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>鍩烘湰淇℃伅</span>
+ </div>
+ </template>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鍙戠エ鍙风爜">{{ form.invoiceNo || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戠エ浠g爜">{{ form.invoiceCode || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="寮�绁ㄦ棩鏈�">{{ form.invoiceDate || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="寮�绁ㄧ姸鎬�">
+ <el-tag :type="getStatusType(form.status)">
+ {{ getStatusText(form.status) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="绋庢帶骞冲彴鐘舵��">
+ <el-tag :type="getTaxControlStatusType(form.taxControlStatus)">
+ {{ getTaxControlStatusText(form.taxControlStatus) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓鏃堕棿">{{ form.createTime || '-' }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- 璐拱鏂逛俊鎭� -->
+ <el-card class="buyer-info" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>璐拱鏂逛俊鎭�</span>
+ </div>
+ </template>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="璐拱鏂瑰悕绉�">{{ form.buyerName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="绾崇◣浜鸿瘑鍒彿">{{ form.buyerTaxNo || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="鍦板潃鐢佃瘽">{{ form.buyerAddress || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="寮�鎴疯鍙婅处鍙�">{{ form.buyerBankAccount || '-' }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- 閿�鍞柟淇℃伅 -->
+ <el-card class="seller-info" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>閿�鍞柟淇℃伅</span>
+ </div>
+ </template>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="閿�鍞柟鍚嶇О">{{ form.sellerName || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="绾崇◣浜鸿瘑鍒彿">{{ form.sellerTaxNo || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="鍦板潃鐢佃瘽">{{ form.sellerAddress || '-' }}</el-descriptions-item>
+ <el-descriptions-item label="寮�鎴疯鍙婅处鍙�">{{ form.sellerBankAccount || '-' }}</el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+
+ <!-- 鍟嗗搧鏄庣粏 -->
+ <el-card class="items-info" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>鍟嗗搧鏄庣粏</span>
+ </div>
+ </template>
+
+ <el-table :data="form.items || []" border style="width: 100%">
+ <el-table-column label="鍟嗗搧鍚嶇О" prop="name" width="200" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specification" width="150" />
+ <el-table-column label="鍗曚綅" prop="unit" width="100" />
+ <el-table-column label="鏁伴噺" prop="quantity" width="100" />
+ <el-table-column label="鍗曚环" prop="unitPrice" width="120">
+ <template #default="scope">
+ {{ scope.row.unitPrice }} 鍏�
+ </template>
+ </el-table-column>
+ <el-table-column label="閲戦" prop="amount" width="120">
+ <template #default="scope">
+ {{ (scope.row.amount || 0).toFixed(2) }} 鍏�
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庣巼" prop="taxRate" width="100">
+ <template #default="scope">
+ {{ (parseFloat(scope.row.taxRate || 0) * 100).toFixed(0) }}%
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庨" prop="taxAmount" width="120">
+ <template #default="scope">
+ {{ (scope.row.taxAmount || 0).toFixed(2) }} 鍏�
+ </template>
+ </el-table-column>
+ <el-table-column label="浠风◣鍚堣" prop="totalAmount" width="120">
+ <template #default="scope">
+ {{ (scope.row.totalAmount || 0).toFixed(2) }} 鍏�
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍚堣淇℃伅 -->
+ <div class="summary-info">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <span class="summary-label">閲戦鍚堣锛�</span>
+ <span class="summary-value">{{ getTotalAmount().toFixed(2) }} 鍏�</span>
+ </el-col>
+ <el-col :span="6">
+ <span class="summary-label">绋庨鍚堣锛�</span>
+ <span class="summary-value">{{ getTotalTaxAmount().toFixed(2) }} 鍏�</span>
+ </el-col>
+ <el-col :span="6">
+ <span class="summary-label">浠风◣鍚堣锛�</span>
+ <span class="summary-value">{{ getTotalTotalAmount().toFixed(2) }} 鍏�</span>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
+ <!-- 澶囨敞淇℃伅 -->
+ <el-card class="remark-info" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>澶囨敞淇℃伅</span>
+ </div>
+ </template>
+
+ <div class="remark-content">
+ {{ form.remark || '鏃�' }}
+ </div>
+ </el-card>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="handleClose">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { computed } from "vue";
+
+// Props
+const props = defineProps({
+ dialogViewVisible: {
+ type: Boolean,
+ default: false
+ },
+ form: {
+ type: Object,
+ default: () => ({})
+ },
+ title: {
+ type: String,
+ default: ""
+ }
+});
+
+// Emits
+const emit = defineEmits(['update:dialogViewVisible']);
+
+// 鑾峰彇鐘舵�佺被鍨�
+const getStatusType = (status) => {
+ const statusMap = {
+ draft: "",
+ pending: "warning",
+ issuing: "warning",
+ issued: "success",
+ failed: "danger",
+ cancelled: "info"
+ };
+ return statusMap[status] || "";
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusMap = {
+ draft: "鑽夌",
+ pending: "寰呭紑绁�",
+ issuing: "寮�绁ㄤ腑",
+ issued: "宸插紑绁�",
+ failed: "寮�绁ㄥけ璐�",
+ cancelled: "宸蹭綔搴�"
+ };
+ return statusMap[status] || status;
+};
+
+// 鑾峰彇绋庢帶骞冲彴鐘舵�佺被鍨�
+const getTaxControlStatusType = (status) => {
+ const statusMap = {
+ pending: "warning",
+ syncing: "warning",
+ synced: "success",
+ failed: "danger"
+ };
+ return statusMap[status] || "";
+};
+
+// 鑾峰彇绋庢帶骞冲彴鐘舵�佹枃鏈�
+const getTaxControlStatusText = (status) => {
+ const statusMap = {
+ pending: "寰呭悓姝�",
+ syncing: "鍚屾涓�",
+ synced: "宸插悓姝�",
+ failed: "鍚屾澶辫触"
+ };
+ return statusMap[status] || status;
+};
+
+// 璁$畻鎬婚噾棰�
+const getTotalAmount = () => {
+ return (props.form.items || []).reduce((sum, item) => sum + (item.amount || 0), 0);
+};
+
+// 璁$畻鎬荤◣棰�
+const getTotalTaxAmount = () => {
+ return (props.form.items || []).reduce((sum, item) => sum + (item.taxAmount || 0), 0);
+};
+
+// 璁$畻鎬讳环绋庡悎璁�
+const getTotalTotalAmount = () => {
+ return (props.form.items || []).reduce((sum, item) => sum + (item.totalAmount || 0), 0);
+};
+
+// 鍏抽棴瀵硅瘽妗�
+const handleClose = () => {
+ emit('update:dialogViewVisible', false);
+};
+</script>
+
+<style scoped>
+.invoice-view {
+ padding: 20px 0;
+}
+
+.basic-info,
+.buyer-info,
+.seller-info,
+.items-info,
+.remark-info {
+ margin-bottom: 20px;
+}
+
+.card-header {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.summary-info {
+ margin-top: 15px;
+ padding: 15px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+}
+
+.summary-label {
+ font-weight: bold;
+ margin-right: 10px;
+}
+
+.summary-value {
+ color: #409eff;
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.remark-content {
+ padding: 15px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ min-height: 60px;
+ line-height: 1.6;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+.el-table {
+ margin-top: 10px;
+}
+</style>
diff --git a/src/views/invoiceCollaboration/components/TaxControlSyncDialog.vue b/src/views/invoiceCollaboration/components/TaxControlSyncDialog.vue
new file mode 100644
index 0000000..19bcfcd
--- /dev/null
+++ b/src/views/invoiceCollaboration/components/TaxControlSyncDialog.vue
@@ -0,0 +1,362 @@
+<template>
+ <el-dialog
+ :model-value="dialogSyncVisible"
+ @update:model-value="$emit('update:dialogSyncVisible', $event)"
+ title="绋庢帶骞冲彴鍚屾"
+ width="800px"
+ :close-on-click-modal="false"
+ >
+ <div class="sync-container">
+ <!-- 鍚屾鐘舵�� -->
+ <el-card class="sync-status" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>鍚屾鐘舵��</span>
+ </div>
+ </template>
+
+ <div class="status-content">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <div class="status-item">
+ <div class="status-icon success">
+ <el-icon><Check /></el-icon>
+ </div>
+ <div class="status-text">
+ <div class="status-title">宸插悓姝�</div>
+ <div class="status-count">{{ syncedCount }}</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="status-item">
+ <div class="status-icon warning">
+ <el-icon><Clock /></el-icon>
+ </div>
+ <div class="status-text">
+ <div class="status-title">寰呭悓姝�</div>
+ <div class="status-count">{{ pendingCount }}</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="status-item">
+ <div class="status-icon danger">
+ <el-icon><Close /></el-icon>
+ </div>
+ <div class="status-text">
+ <div class="status-title">鍚屾澶辫触</div>
+ <div class="status-count">{{ failedCount }}</div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
+ <!-- 鍚屾閰嶇疆 -->
+ <el-card class="sync-config" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>鍚屾閰嶇疆</span>
+ </div>
+ </template>
+
+ <el-form :model="syncConfig" label-width="120px">
+ <el-form-item label="绋庢帶骞冲彴鍦板潃">
+ <el-input
+ v-model="syncConfig.taxControlUrl"
+ placeholder="璇疯緭鍏ョ◣鎺у钩鍙板湴鍧�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鍚屾棰戠巼">
+ <el-select
+ v-model="syncConfig.syncFrequency"
+ placeholder="璇烽�夋嫨鍚屾棰戠巼"
+ style="width: 100%"
+ >
+ <el-option label="瀹炴椂鍚屾" value="realtime" />
+ <el-option label="姣忓皬鏃跺悓姝�" value="hourly" />
+ <el-option label="姣忓ぉ鍚屾" value="daily" />
+ <el-option label="鎵嬪姩鍚屾" value="manual" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鑷姩閲嶈瘯">
+ <el-switch
+ v-model="syncConfig.autoRetry"
+ active-text="寮�鍚�"
+ inactive-text="鍏抽棴"
+ />
+ </el-form-item>
+ <el-form-item label="閲嶈瘯娆℃暟" v-if="syncConfig.autoRetry">
+ <el-input-number
+ v-model="syncConfig.retryCount"
+ :min="1"
+ :max="10"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鍚屾鏃ュ織 -->
+ <el-card class="sync-log" shadow="never">
+ <template #header>
+ <div class="card-header">
+ <span>鍚屾鏃ュ織</span>
+ <el-button type="primary" size="small" @click="refreshLog">
+ 鍒锋柊鏃ュ織
+ </el-button>
+ </div>
+ </template>
+
+ <el-table :data="syncLogs" border style="width: 100%" max-height="300">
+ <el-table-column label="鏃堕棿" prop="time" width="160" />
+ <el-table-column label="鎿嶄綔" prop="action" width="120" />
+ <el-table-column label="鐘舵��" prop="status" width="100">
+ <template #default="scope">
+ <el-tag :type="getLogStatusType(scope.row.status)">
+ {{ getLogStatusText(scope.row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="璇︽儏" prop="detail" show-overflow-tooltip />
+ </el-table>
+ </el-card>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="handleClose">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSync" :loading="syncLoading">
+ 寮�濮嬪悓姝�
+ </el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+import { Check, Clock, Close } from "@element-plus/icons-vue";
+
+// Props
+const props = defineProps({
+ dialogSyncVisible: {
+ type: Boolean,
+ default: false
+ }
+});
+
+// Emits
+const emit = defineEmits(['update:dialogSyncVisible', 'success']);
+
+// 鍝嶅簲寮忔暟鎹�
+const syncLoading = ref(false);
+const syncedCount = ref(15);
+const pendingCount = ref(8);
+const failedCount = ref(2);
+
+// 鍚屾閰嶇疆
+const syncConfig = reactive({
+ taxControlUrl: "https://tax-control.example.com/api",
+ syncFrequency: "manual",
+ autoRetry: true,
+ retryCount: 3
+});
+
+// 鍚屾鏃ュ織
+const syncLogs = ref([
+ {
+ time: "2024-12-01 15:30:00",
+ action: "鍙戠エ鍚屾",
+ status: "success",
+ detail: "鎴愬姛鍚屾15寮犲彂绁ㄥ埌绋庢帶骞冲彴"
+ },
+ {
+ time: "2024-12-01 15:25:00",
+ action: "鍙戠エ鍚屾",
+ status: "success",
+ detail: "鎴愬姛鍚屾8寮犲彂绁ㄥ埌绋庢帶骞冲彴"
+ },
+ {
+ time: "2024-12-01 15:20:00",
+ action: "鍙戠エ鍚屾",
+ status: "failed",
+ detail: "鍚屾澶辫触锛氱綉缁滆繛鎺ヨ秴鏃�"
+ },
+ {
+ time: "2024-12-01 15:15:00",
+ action: "鍙戠エ鍚屾",
+ status: "success",
+ detail: "鎴愬姛鍚屾12寮犲彂绁ㄥ埌绋庢帶骞冲彴"
+ }
+]);
+
+// 鑾峰彇鏃ュ織鐘舵�佺被鍨�
+const getLogStatusType = (status) => {
+ const statusMap = {
+ success: "success",
+ failed: "danger",
+ pending: "warning"
+ };
+ return statusMap[status] || "";
+};
+
+// 鑾峰彇鏃ュ織鐘舵�佹枃鏈�
+const getLogStatusText = (status) => {
+ const statusMap = {
+ success: "鎴愬姛",
+ failed: "澶辫触",
+ pending: "杩涜涓�"
+ };
+ return statusMap[status] || status;
+};
+
+// 鍒锋柊鏃ュ織
+const refreshLog = () => {
+ ElMessage.success("鏃ュ織宸插埛鏂�");
+};
+
+// 寮�濮嬪悓姝�
+const handleSync = async () => {
+ if (!syncConfig.taxControlUrl) {
+ ElMessage.warning("璇峰厛閰嶇疆绋庢帶骞冲彴鍦板潃");
+ return;
+ }
+
+ syncLoading.value = true;
+
+ try {
+ // 妯℃嫙鍚屾杩囩▼
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ // 鏇存柊鍚屾鐘舵��
+ const newSynced = Math.min(pendingCount.value, 5);
+ syncedCount.value += newSynced;
+ pendingCount.value -= newSynced;
+
+ // 娣诲姞鍚屾鏃ュ織
+ syncLogs.value.unshift({
+ time: new Date().toLocaleString(),
+ action: "鍙戠エ鍚屾",
+ status: "success",
+ detail: `鎴愬姛鍚屾${newSynced}寮犲彂绁ㄥ埌绋庢帶骞冲彴`
+ });
+
+ ElMessage.success("鍚屾瀹屾垚");
+ emit('success');
+ } catch (error) {
+ ElMessage.error("鍚屾澶辫触");
+ failedCount.value++;
+
+ // 娣诲姞澶辫触鏃ュ織
+ syncLogs.value.unshift({
+ time: new Date().toLocaleString(),
+ action: "鍙戠エ鍚屾",
+ status: "failed",
+ detail: "鍚屾澶辫触锛氱郴缁熼敊璇�"
+ });
+ } finally {
+ syncLoading.value = false;
+ }
+};
+
+// 鍏抽棴瀵硅瘽妗�
+const handleClose = () => {
+ emit('update:dialogSyncVisible', false);
+};
+
+// 缁勪欢鎸傝浇鏃跺垵濮嬪寲鏁版嵁
+onMounted(() => {
+ // 鍙互鍦ㄨ繖閲屽姞杞藉垵濮嬫暟鎹�
+});
+</script>
+
+<style scoped>
+.sync-container {
+ padding: 0;
+}
+
+.sync-status,
+.sync-config,
+.sync-log {
+ margin-bottom: 20px;
+}
+
+.sync-status:last-child,
+.sync-config:last-child,
+.sync-log:last-child {
+ margin-bottom: 0;
+}
+
+.card-header {
+ font-weight: bold;
+ font-size: 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.status-content {
+ padding: 20px 0;
+}
+
+.status-item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+ text-align: center;
+}
+
+.status-icon {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+ font-size: 24px;
+ color: white;
+}
+
+.status-icon.success {
+ background-color: #67c23a;
+}
+
+.status-icon.warning {
+ background-color: #e6a23c;
+}
+
+.status-icon.danger {
+ background-color: #f56c6c;
+}
+
+.status-text {
+ text-align: left;
+}
+
+.status-title {
+ font-size: 14px;
+ color: #606266;
+ margin-bottom: 5px;
+}
+
+.status-count {
+ font-size: 24px;
+ font-weight: bold;
+ color: #303133;
+}
+
+.dialog-footer {
+ text-align: right;
+}
+
+.el-table {
+ margin-top: 10px;
+}
+</style>
diff --git a/src/views/invoiceCollaboration/index.vue b/src/views/invoiceCollaboration/index.vue
new file mode 100644
index 0000000..b44ee92
--- /dev/null
+++ b/src/views/invoiceCollaboration/index.vue
@@ -0,0 +1,554 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储琛ㄥ崟 -->
+ <el-form :inline="true" :model="queryParams" class="search-form">
+ <el-form-item label="鍙戠エ鍙风爜">
+ <el-input
+ v-model="queryParams.invoiceNo"
+ placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�"
+ clearable
+ :style="{ width: '200px' }"
+ />
+ </el-form-item>
+ <el-form-item label="寮�绁ㄧ姸鎬�">
+ <el-select
+ v-model="queryParams.status"
+ placeholder="璇烽�夋嫨寮�绁ㄧ姸鎬�"
+ clearable
+ :style="{ width: '150px' }"
+ >
+ <el-option
+ :label="item.label"
+ v-for="item in statusList"
+ :key="item.value"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="寮�绁ㄦ棩鏈�">
+ <el-date-picker
+ v-model="queryParams.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ :style="{ width: '240px' }"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-card>
+ <!-- 鎿嶄綔鎸夐挳鍖� -->
+ <el-row :gutter="24" class="table-toolbar" justify="space-between">
+ <el-button type="primary" :icon="Plus" @click="handleAdd">
+ 鏂板鍙戠エ
+ </el-button>
+ <el-button type="success" :icon="Refresh" @click="handleSyncTaxControl">
+ 鍚屾绋庢帶骞冲彴
+ </el-button>
+ <el-button type="warning" :icon="Download" @click="handleBatchDownload">
+ 鎵归噺涓嬭浇
+ </el-button>
+ <el-button type="danger" :icon="Delete" @click="handleBatchDelete" :disabled="selectedIds.length === 0">
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </el-row>
+
+ <!-- 琛ㄦ牸缁勪欢 -->
+ <el-table
+ v-loading="loading"
+ :data="tableData"
+ @selection-change="handleSelectionChange"
+ border
+ style="width: 100%"
+ >
+ <el-table-column type="selection" width="55" />
+ <el-table-column label="鍙戠エ鍙风爜" prop="invoiceNo" width="180" />
+ <el-table-column label="鍙戠エ浠g爜" prop="invoiceCode" width="150" />
+ <el-table-column label="寮�绁ㄦ棩鏈�" prop="invoiceDate" width="120" />
+ <el-table-column label="璐拱鏂瑰悕绉�" prop="buyerName" width="200" show-overflow-tooltip />
+ <el-table-column label="閿�鍞柟鍚嶇О" prop="sellerName" width="200" show-overflow-tooltip />
+ <el-table-column label="閲戦" prop="amount" width="120">
+ <template #default="scope">
+ {{ scope.row.amount }} 鍏�
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庨" prop="taxAmount" width="120">
+ <template #default="scope">
+ {{ scope.row.taxAmount }} 鍏�
+ </template>
+ </el-table-column>
+ <el-table-column label="浠风◣鍚堣" prop="totalAmount" width="120">
+ <template #default="scope">
+ {{ scope.row.totalAmount }} 鍏�
+ </template>
+ </el-table-column>
+ <el-table-column label="寮�绁ㄧ姸鎬�" prop="status" width="100">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)">
+ {{ getStatusText(scope.row.status) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庢帶骞冲彴鐘舵��" prop="taxControlStatus" width="120">
+ <template #default="scope">
+ <el-tag :type="getTaxControlStatusType(scope.row.taxControlStatus)">
+ {{ getTaxControlStatusText(scope.row.taxControlStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓鏃堕棿" prop="createTime" width="160" />
+ <el-table-column label="鎿嶄綔" width="250" fixed="right">
+ <template #default="scope">
+ <el-button
+ size="small"
+ type="primary"
+ @click="handleView(scope.row)"
+ >
+ 鏌ョ湅
+ </el-button>
+ <el-button
+ size="small"
+ type="success"
+ @click="handleDownload(scope.row)"
+ v-if="scope.row.status === 'issued'"
+ >
+ 涓嬭浇
+ </el-button>
+ <el-button
+ size="small"
+ type="warning"
+ @click="handleEdit(scope.row)"
+ v-if="scope.row.status === 'draft'"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ size="small"
+ type="danger"
+ @click="handleDelete(scope.row)"
+ v-if="scope.row.status === 'draft'"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉缁勪欢 -->
+ <pagination
+ v-if="total > 0"
+ :page="current"
+ :limit="pageSize"
+ :total="total"
+ @pagination="handlePagination"
+ :layout="'total, prev, pager, next, jumper'"
+ />
+ </el-card>
+
+ <!-- 鏂板/缂栬緫瀵硅瘽妗� -->
+ <InvoiceDialog
+ v-model:dialogFormVisible="dialogFormVisible"
+ v-model:form="form"
+ :title="title"
+ :is-edit="isEdit"
+ @submit="handleSubmit"
+ @success="handleSuccess"
+ ref="invoiceDialog"
+ />
+
+ <!-- 鏌ョ湅璇︽儏瀵硅瘽妗� -->
+ <InvoiceViewDialog
+ v-model:dialogViewVisible="dialogViewVisible"
+ :form="viewForm"
+ title="鍙戠エ璇︽儏"
+ />
+
+ <!-- 绋庢帶骞冲彴鍚屾瀵硅瘽妗� -->
+ <TaxControlSyncDialog
+ v-model:dialogSyncVisible="dialogSyncVisible"
+ @success="handleSyncSuccess"
+ />
+
+ <!-- 鍗曚釜鍙戠エ涓嬭浇瀵硅瘽妗� -->
+ <DownloadDialog
+ v-model:dialogVisible="downloadDialogVisible"
+ :invoice="currentDownloadInvoice"
+ @success="handleDownloadSuccess"
+ />
+
+ <!-- 鎵归噺涓嬭浇瀵硅瘽妗� -->
+ <BatchDownloadDialog
+ v-model:dialogVisible="batchDownloadDialogVisible"
+ :invoices="batchDownloadInvoices"
+ @success="handleBatchDownloadSuccess"
+ />
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { Plus, Edit, Delete, Refresh, Download, View } from "@element-plus/icons-vue";
+import Pagination from "@/components/Pagination";
+import InvoiceDialog from "./components/InvoiceDialog.vue";
+import InvoiceViewDialog from "./components/InvoiceViewDialog.vue";
+import TaxControlSyncDialog from "./components/TaxControlSyncDialog.vue";
+import DownloadDialog from "./components/DownloadDialog.vue";
+import BatchDownloadDialog from "./components/BatchDownloadDialog.vue";
+
+// 鍝嶅簲寮忔暟鎹�
+const loading = ref(false);
+const tableData = ref([]);
+const selectedIds = ref([]);
+const current = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+const dialogFormVisible = ref(false);
+const dialogViewVisible = ref(false);
+const dialogSyncVisible = ref(false);
+const downloadDialogVisible = ref(false);
+const batchDownloadDialogVisible = ref(false);
+const isEdit = ref(false);
+const title = ref("");
+const form = ref({});
+const viewForm = ref({});
+const currentDownloadInvoice = ref({});
+const batchDownloadInvoices = ref([]);
+
+// 鏌ヨ鍙傛暟
+const queryParams = reactive({
+ invoiceNo: "",
+ status: "",
+ dateRange: []
+});
+
+// 鐘舵�佸垪琛�
+const statusList = ref([
+ { value: "draft", label: "鑽夌" },
+ { value: "pending", label: "寰呭紑绁�" },
+ { value: "issuing", label: "寮�绁ㄤ腑" },
+ { value: "issued", label: "宸插紑绁�" },
+ { value: "failed", label: "寮�绁ㄥけ璐�" },
+ { value: "cancelled", label: "宸蹭綔搴�" }
+]);
+
+// 妯℃嫙鏁版嵁
+const mockData = [
+ {
+ id: "1",
+ invoiceNo: "FP20241201001",
+ invoiceCode: "123456789",
+ invoiceDate: "2024-12-01",
+ buyerName: "瀹㈡埛A鍏徃",
+ sellerName: "鏈叕鍙�",
+ amount: 10000.00,
+ taxAmount: 1300.00,
+ totalAmount: 11300.00,
+ status: "issued",
+ taxControlStatus: "synced",
+ createTime: "2024-12-01 10:00:00"
+ },
+ {
+ id: "2",
+ invoiceNo: "FP20241201002",
+ invoiceCode: "123456790",
+ invoiceDate: "2024-12-01",
+ buyerName: "瀹㈡埛B鍏徃",
+ sellerName: "鏈叕鍙�",
+ amount: 5000.00,
+ taxAmount: 650.00,
+ totalAmount: 5650.00,
+ status: "pending",
+ taxControlStatus: "pending",
+ createTime: "2024-12-01 14:30:00"
+ }
+];
+
+// 鑾峰彇鐘舵�佺被鍨�
+const getStatusType = (status) => {
+ const statusMap = {
+ draft: "",
+ pending: "warning",
+ issuing: "warning",
+ issued: "success",
+ failed: "danger",
+ cancelled: "info"
+ };
+ return statusMap[status] || "";
+};
+
+// 鑾峰彇鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+ const statusMap = {
+ draft: "鑽夌",
+ pending: "寰呭紑绁�",
+ issuing: "寮�绁ㄤ腑",
+ issued: "宸插紑绁�",
+ failed: "寮�绁ㄥけ璐�",
+ cancelled: "宸蹭綔搴�"
+ };
+ return statusMap[status] || status;
+};
+
+// 鑾峰彇绋庢帶骞冲彴鐘舵�佺被鍨�
+const getTaxControlStatusType = (status) => {
+ const statusMap = {
+ pending: "warning",
+ syncing: "warning",
+ synced: "success",
+ failed: "danger"
+ };
+ return statusMap[status] || "";
+};
+
+// 鑾峰彇绋庢帶骞冲彴鐘舵�佹枃鏈�
+const getTaxControlStatusText = (status) => {
+ const statusMap = {
+ pending: "寰呭悓姝�",
+ syncing: "鍚屾涓�",
+ synced: "宸插悓姝�",
+ failed: "鍚屾澶辫触"
+ };
+ return statusMap[status] || status;
+};
+
+// 鏌ヨ
+const handleQuery = () => {
+ current.value = 1;
+ loadData();
+};
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+ Object.assign(queryParams, {
+ invoiceNo: "",
+ status: "",
+ dateRange: []
+ });
+ handleQuery();
+};
+
+// 鍔犺浇鏁版嵁
+const loadData = () => {
+ loading.value = true;
+ // 妯℃嫙API璋冪敤
+ setTimeout(() => {
+ tableData.value = mockData;
+ total.value = mockData.length;
+ loading.value = false;
+ }, 500);
+};
+
+// 鍒嗛〉澶勭悊
+const handlePagination = (pagination) => {
+ current.value = pagination.page;
+ pageSize.value = pagination.limit;
+ loadData();
+};
+
+// 閫夋嫨鍙樺寲
+const handleSelectionChange = (selection) => {
+ selectedIds.value = selection.map(item => item.id);
+};
+
+// 鏂板
+const handleAdd = () => {
+ isEdit.value = false;
+ title.value = "鏂板鍙戠エ";
+ form.value = {
+ buyerName: "",
+ buyerTaxNo: "",
+ buyerAddress: "",
+ buyerBankAccount: "",
+ sellerName: "鏈叕鍙�",
+ sellerTaxNo: "123456789012345678",
+ sellerAddress: "鍏徃鍦板潃",
+ sellerBankAccount: "閾惰璐︽埛",
+ items: [],
+ remark: ""
+ };
+ dialogFormVisible.value = true;
+};
+
+// 缂栬緫
+const handleEdit = (row) => {
+ isEdit.value = true;
+ title.value = "缂栬緫鍙戠エ";
+ form.value = { ...row };
+ dialogFormVisible.value = true;
+};
+
+// 鏌ョ湅
+const handleView = (row) => {
+ viewForm.value = { ...row };
+ dialogViewVisible.value = true;
+};
+
+// 鍒犻櫎
+const handleDelete = (row) => {
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ゅ彂绁� ${row.invoiceNo} 鍚楋紵`,
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ ).then(() => {
+ // 妯℃嫙鍒犻櫎
+ const index = tableData.value.findIndex(item => item.id === row.id);
+ if (index > -1) {
+ tableData.value.splice(index, 1);
+ total.value--;
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ }
+ });
+};
+
+// 鎵归噺鍒犻櫎
+const handleBatchDelete = () => {
+ if (selectedIds.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佸垹闄ょ殑璁板綍");
+ return;
+ }
+
+ ElMessageBox.confirm(
+ `纭畾瑕佸垹闄ら�変腑鐨� ${selectedIds.value.length} 鏉¤褰曞悧锛焋,
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ ).then(() => {
+ // 妯℃嫙鎵归噺鍒犻櫎
+ tableData.value = tableData.value.filter(item => !selectedIds.value.includes(item.id));
+ total.value = tableData.value.length;
+ selectedIds.value = [];
+ ElMessage.success("鎵归噺鍒犻櫎鎴愬姛");
+ });
+};
+
+// 涓嬭浇鍙戠エ
+const handleDownload = (row) => {
+ if (row.status !== 'issued') {
+ ElMessage.warning("鍙湁宸插紑绁ㄧ殑鍙戠エ鎵嶈兘涓嬭浇");
+ return;
+ }
+
+ // 鏄剧ず涓嬭浇閫夐」瀵硅瘽妗�
+ downloadDialogVisible.value = true;
+ currentDownloadInvoice.value = row;
+};
+
+// 鎵归噺涓嬭浇
+const handleBatchDownload = () => {
+ if (selectedIds.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨瑕佷笅杞界殑璁板綍");
+ return;
+ }
+
+ // 妫�鏌ラ�変腑鐨勫彂绁ㄧ姸鎬�
+ const selectedInvoices = tableData.value.filter(item => selectedIds.value.includes(item.id));
+ const issuedInvoices = selectedInvoices.filter(item => item.status === 'issued');
+
+ if (issuedInvoices.length === 0) {
+ ElMessage.warning("閫変腑鐨勫彂绁ㄤ腑娌℃湁宸插紑绁ㄧ殑鍙戠エ");
+ return;
+ }
+
+ if (issuedInvoices.length < selectedInvoices.length) {
+ ElMessage.warning(`閫変腑鐨�${selectedInvoices.length}寮犲彂绁ㄤ腑锛屽彧鏈�${issuedInvoices.length}寮犲凡寮�绁紝灏嗗彧涓嬭浇宸插紑绁ㄧ殑鍙戠エ`);
+ }
+
+ // 鏄剧ず鎵归噺涓嬭浇閫夐」瀵硅瘽妗�
+ batchDownloadDialogVisible.value = true;
+ batchDownloadInvoices.value = issuedInvoices;
+};
+
+// 鍚屾绋庢帶骞冲彴
+const handleSyncTaxControl = () => {
+ dialogSyncVisible.value = true;
+};
+
+// 鎻愪氦琛ㄥ崟
+const handleSubmit = (formData) => {
+ if (isEdit.value) {
+ // 缂栬緫
+ const index = tableData.value.findIndex(item => item.id === formData.id);
+ if (index > -1) {
+ tableData.value[index] = { ...formData };
+ ElMessage.success("缂栬緫鎴愬姛");
+ }
+ } else {
+ // 鏂板
+ const newItem = {
+ id: Date.now().toString(),
+ invoiceNo: `FP${Date.now()}`,
+ invoiceCode: "123456789",
+ invoiceDate: new Date().toISOString().split('T')[0],
+ buyerName: formData.buyerName,
+ sellerName: formData.sellerName,
+ amount: formData.items.reduce((sum, item) => sum + (item.amount || 0), 0),
+ taxAmount: formData.items.reduce((sum, item) => sum + (item.taxAmount || 0), 0),
+ totalAmount: formData.items.reduce((sum, item) => sum + (item.totalAmount || 0), 0),
+ status: "draft",
+ taxControlStatus: "pending",
+ createTime: new Date().toLocaleString()
+ };
+ tableData.value.unshift(newItem);
+ total.value++;
+ ElMessage.success("鏂板鎴愬姛");
+ }
+ dialogFormVisible.value = false;
+};
+
+// 琛ㄥ崟鎴愬姛鍥炶皟
+const handleSuccess = () => {
+ loadData();
+};
+
+// 鍚屾鎴愬姛鍥炶皟
+const handleSyncSuccess = () => {
+ loadData();
+ ElMessage.success("绋庢帶骞冲彴鍚屾鎴愬姛");
+};
+
+// 鍗曚釜涓嬭浇鎴愬姛鍥炶皟
+const handleDownloadSuccess = () => {
+ downloadDialogVisible.value = false;
+ ElMessage.success("鍙戠エ涓嬭浇鎴愬姛");
+};
+
+// 鎵归噺涓嬭浇鎴愬姛鍥炶皟
+const handleBatchDownloadSuccess = () => {
+ batchDownloadDialogVisible.value = false;
+ ElMessage.success("鎵归噺涓嬭浇鎴愬姛");
+};
+
+// 椤甸潰鍔犺浇
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<style scoped>
+.search-form {
+ margin-bottom: 20px;
+}
+
+.table-toolbar {
+ margin-bottom: 20px;
+}
+
+.el-card {
+ margin-bottom: 20px;
+}
+</style>
\ No newline at end of file
--
Gitblit v1.9.3