<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="发票代码">{{ 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 ? '下载完成' : `正在下载... ${downloadProgress}%` }}
|
</div>
|
<div class="progress-detail" v-if="downloadProgress < 100">
|
<span>正在生成{{ 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: "正在验证发票信息..." },
|
{ progress: 40, message: "正在生成文件内容..." },
|
{ progress: 60, message: "正在应用格式设置..." },
|
{ progress: 80, message: "正在生成文件..." },
|
{ 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内容(CSV格式,兼容性更好)
|
fileContent = generateExcelContent();
|
fileName = `${downloadOptions.fileName}.csv`;
|
mimeType = 'text/csv';
|
} else if (downloadOptions.format === 'image') {
|
// 生成图片内容(SVG格式)
|
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 {
|
// 动态导入JSZip库
|
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内容(CSV格式)
|
const generateExcelContent = () => {
|
const invoice = props.invoice;
|
const 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'}`;
|
return content;
|
};
|
|
// 生成图片内容(SVG格式)
|
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'}
|
发票代码,${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>
|