<template>
|
<div class="app-container">
|
<el-card class="box-card">
|
<template #header>
|
<div class="card-header">
|
<span>二维码与防伪码生成器</span>
|
<el-button type="primary" @click="showBatchDialog" icon="Plus">
|
批量生成
|
</el-button>
|
</div>
|
</template>
|
|
<!-- 集成二维码生成组件 -->
|
<QRCodeGenerator ref="qrGeneratorRef" />
|
</el-card>
|
|
<!-- 批量生成对话框 -->
|
<el-dialog v-model="batchDialogVisible" title="批量生成设置" width="800px">
|
<el-form :model="batchForm" :rules="batchRules" ref="batchFormRef" label-width="120px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="标识类型" prop="type">
|
<el-select v-model="batchForm.type" placeholder="请选择标识类型" style="width: 100%">
|
<el-option label="二维码" value="qrcode"></el-option>
|
<el-option label="防伪码" value="security"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="生成数量" prop="quantity">
|
<el-input-number
|
v-model="batchForm.quantity"
|
:min="1"
|
:max="1000"
|
:step="10"
|
style="width: 100%"
|
></el-input-number>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="前缀" prop="prefix">
|
<el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="起始编号" prop="startNumber">
|
<el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="尺寸" prop="size">
|
<el-input-number
|
v-model="batchForm.size"
|
:min="100"
|
:max="500"
|
:step="50"
|
style="width: 100%"
|
></el-input-number>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="边距" prop="margin">
|
<el-input-number
|
v-model="batchForm.margin"
|
:min="0"
|
:max="10"
|
:step="1"
|
style="width: 100%"
|
></el-input-number>
|
</el-col>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="前景色" prop="foregroundColor">
|
<el-color-picker v-model="batchForm.foregroundColor" style="width: 100%"></el-color-picker>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="背景色" prop="backgroundColor">
|
<el-color-picker v-model="batchForm.backgroundColor" style="width: 100%"></el-color-picker>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="24">
|
<el-form-item label="备注">
|
<el-input
|
v-model="batchForm.remark"
|
type="textarea"
|
:rows="3"
|
placeholder="请输入备注信息"
|
></el-input>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="batchDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="startBatchGeneration" :loading="generating">
|
开始生成
|
</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 批量生成进度 -->
|
<el-dialog v-model="progressDialogVisible" title="批量生成进度" width="500px" :close-on-click-modal="false">
|
<div class="progress-container">
|
<el-progress
|
:percentage="progressPercentage"
|
:status="progressStatus"
|
:stroke-width="20"
|
></el-progress>
|
<p class="progress-text">{{ progressText }}</p>
|
<div class="progress-details">
|
<p>已生成: {{ generatedCount }} / {{ totalCount }}</p>
|
<p>当前内容: {{ currentContent }}</p>
|
</div>
|
</div>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="cancelGeneration" :disabled="!canCancel">取消</el-button>
|
<el-button type="primary" @click="downloadBatchResults" v-if="generationCompleted">
|
下载结果
|
</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import QRCodeGenerator from '@/components/QRCodeGenerator/index.vue'
|
import QRCode from 'qrcode'
|
import JSZip from 'jszip'
|
|
defineOptions({
|
name: 'QRCodeGeneratorPage'
|
})
|
|
// 组件引用
|
const qrGeneratorRef = ref()
|
|
// 批量生成相关
|
const batchDialogVisible = ref(false)
|
const progressDialogVisible = ref(false)
|
const generating = ref(false)
|
const generationCompleted = ref(false)
|
const canCancel = ref(true)
|
|
const batchForm = reactive({
|
type: 'qrcode',
|
quantity: 100,
|
prefix: 'PROD_',
|
startNumber: 1,
|
size: 200,
|
margin: 2,
|
foregroundColor: '#000000',
|
backgroundColor: '#FFFFFF',
|
remark: ''
|
})
|
|
const batchRules = {
|
type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
|
quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }],
|
prefix: [{ required: true, message: '请输入前缀', trigger: 'blur' }],
|
startNumber: [{ required: true, message: '请输入起始编号', trigger: 'blur' }]
|
}
|
|
// 进度相关
|
const progressPercentage = ref(0)
|
const progressStatus = ref('')
|
const progressText = ref('准备中...')
|
const generatedCount = ref(0)
|
const totalCount = ref(0)
|
const currentContent = ref('')
|
|
// 生成结果
|
const batchResults = ref([])
|
|
// 显示批量生成对话框
|
const showBatchDialog = () => {
|
batchDialogVisible.value = true
|
// 重置表单
|
Object.assign(batchForm, {
|
type: 'qrcode',
|
quantity: 100,
|
prefix: 'PROD_',
|
startNumber: 1,
|
size: 200,
|
margin: 2,
|
foregroundColor: '#000000',
|
backgroundColor: '#FFFFFF',
|
remark: ''
|
})
|
}
|
|
// 开始批量生成
|
const startBatchGeneration = async () => {
|
try {
|
await batchFormRef.value.validate()
|
|
if (!batchForm.prefix.trim()) {
|
ElMessage.warning('请输入前缀')
|
return
|
}
|
|
batchDialogVisible.value = false
|
progressDialogVisible.value = true
|
generating.value = true
|
generationCompleted.value = false
|
canCancel.value = true
|
|
// 重置进度
|
progressPercentage.value = 0
|
progressStatus.value = ''
|
progressText.value = '开始生成...'
|
generatedCount.value = 0
|
totalCount.value = batchForm.quantity
|
batchResults.value = []
|
|
await generateBatchCodes()
|
|
} catch (error) {
|
console.error('批量生成失败:', error)
|
ElMessage.error('批量生成失败:' + error.message)
|
}
|
}
|
|
// 生成防伪码内容
|
const generateSecurityCode = (content) => {
|
const timestamp = Date.now()
|
const random = Math.random().toString(36).substr(2, 8)
|
return `SEC_${content}_${timestamp}_${random}`
|
}
|
|
// 批量生成码
|
const generateBatchCodes = async () => {
|
try {
|
for (let i = 0; i < batchForm.quantity; i++) {
|
if (!canCancel.value) {
|
progressText.value = '生成已取消'
|
progressStatus.value = 'exception'
|
break
|
}
|
|
const number = batchForm.startNumber + i
|
const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
|
currentContent.value = content
|
|
let codeUrl
|
if (batchForm.type === 'qrcode') {
|
codeUrl = await QRCode.toDataURL(content, {
|
width: batchForm.size,
|
margin: batchForm.margin,
|
color: {
|
dark: batchForm.foregroundColor,
|
light: batchForm.backgroundColor
|
},
|
errorCorrectionLevel: 'M'
|
})
|
} else {
|
const securityContent = generateSecurityCode(content)
|
codeUrl = await QRCode.toDataURL(securityContent, {
|
width: batchForm.size,
|
margin: batchForm.margin,
|
color: {
|
dark: batchForm.foregroundColor,
|
light: batchForm.backgroundColor
|
},
|
errorCorrectionLevel: 'H'
|
})
|
}
|
|
batchResults.value.push({
|
content,
|
url: codeUrl,
|
type: batchForm.type,
|
generateTime: new Date().toLocaleString()
|
})
|
|
generatedCount.value = i + 1
|
progressPercentage.value = Math.round(((i + 1) / batchForm.quantity) * 100)
|
progressText.value = `正在生成第 ${i + 1} 个码...`
|
|
// 添加小延迟,让用户看到进度
|
await new Promise(resolve => setTimeout(resolve, 50))
|
}
|
|
if (canCancel.value) {
|
progressText.value = '生成完成!'
|
progressStatus.value = 'success'
|
generationCompleted.value = true
|
ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} 个码`)
|
}
|
|
} catch (error) {
|
console.error('批量生成失败:', error)
|
progressText.value = '生成失败:' + error.message
|
progressStatus.value = 'exception'
|
ElMessage.error('批量生成失败:' + error.message)
|
} finally {
|
generating.value = false
|
}
|
}
|
|
// 取消生成
|
const cancelGeneration = () => {
|
canCancel.value = false
|
progressText.value = '正在取消...'
|
setTimeout(() => {
|
progressDialogVisible.value = false
|
ElMessage.info('生成已取消')
|
}, 1000)
|
}
|
|
// 下载批量生成结果
|
const downloadBatchResults = async () => {
|
if (batchResults.value.length === 0) {
|
ElMessage.warning('没有可下载的结果')
|
return
|
}
|
|
try {
|
const zip = new JSZip()
|
|
// 创建说明文件
|
const readme = `批量生成说明
|
类型: ${batchForm.type === 'qrcode' ? '二维码' : '防伪码'}
|
数量: ${batchResults.value.length}
|
前缀: ${batchForm.prefix}
|
起始编号: ${batchForm.startNumber}
|
尺寸: ${batchForm.size}x${batchForm.size}px
|
生成时间: ${new Date().toLocaleString()}
|
备注: ${batchForm.remark || '无'}
|
|
文件列表:
|
${batchResults.value.map((item, index) => `${index + 1}. ${item.content}.png`).join('\n')}
|
`
|
|
zip.file('README.txt', readme)
|
|
// 添加图片文件
|
batchResults.value.forEach((result, index) => {
|
const base64Data = result.url.split(',')[1]
|
const byteCharacters = atob(base64Data)
|
const byteNumbers = new Array(byteCharacters.length)
|
for (let i = 0; i < byteCharacters.length; i++) {
|
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
}
|
const byteArray = new Uint8Array(byteNumbers)
|
|
zip.file(`${result.content}.png`, byteArray)
|
})
|
|
const content = await zip.generateAsync({ type: 'blob' })
|
const a = document.createElement('a')
|
a.href = URL.createObjectURL(content)
|
a.download = `批量${batchForm.type === 'qrcode' ? '二维码' : '防伪码'}_${batchForm.prefix}_${new Date().getTime()}.zip`
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
URL.revokeObjectURL(a.href)
|
|
ElMessage.success('批量下载完成!')
|
progressDialogVisible.value = false
|
|
} catch (error) {
|
console.error('批量下载失败:', error)
|
ElMessage.error('批量下载失败:' + error.message)
|
}
|
}
|
|
// 表单引用
|
const batchFormRef = ref()
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
.box-card {
|
margin-bottom: 20px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.progress-container {
|
text-align: center;
|
padding: 20px;
|
}
|
|
.progress-text {
|
margin: 20px 0;
|
font-size: 16px;
|
font-weight: bold;
|
}
|
|
.progress-details {
|
margin-top: 20px;
|
text-align: left;
|
background: #f8f9fa;
|
padding: 15px;
|
border-radius: 8px;
|
}
|
|
.progress-details p {
|
margin: 8px 0;
|
color: #666;
|
}
|
|
.dialog-footer {
|
text-align: right;
|
}
|
</style>
|