<template>
|
<div class="qr-code-generator">
|
<!-- 二维码生成表单 -->
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="标识类型" prop="type">
|
<el-select v-model="form.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="content">
|
<el-input
|
v-model="form.content"
|
placeholder="请输入要编码的内容"
|
:type="form.type === 'security' ? 'textarea' : 'text'"
|
:rows="form.type === 'security' ? 3 : 1"
|
></el-input>
|
</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="form.size"
|
:min="100"
|
:max="500"
|
:step="50"
|
style="width: 100%"
|
></el-input-number>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="边距" prop="margin">
|
<el-input-number
|
v-model="form.margin"
|
:min="0"
|
:max="10"
|
:step="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="foregroundColor">
|
<el-color-picker v-model="form.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="form.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>
|
<el-button type="primary" @click="generateCode" :loading="generating">
|
生成{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}
|
</el-button>
|
<el-button @click="resetForm">重置</el-button>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
|
<!-- 生成的码显示区域 -->
|
<div v-if="generatedCodeUrl" class="code-display">
|
<el-divider content-position="center">
|
{{ form.type === 'qrcode' ? '生成的二维码' : '生成的防伪码' }}
|
</el-divider>
|
|
<div class="code-container">
|
<div class="code-image">
|
<img :src="generatedCodeUrl" :alt="form.type === 'qrcode' ? '二维码' : '防伪码'" />
|
</div>
|
|
<div class="code-info">
|
<p><strong>内容:</strong>{{ form.content }}</p>
|
<p><strong>类型:</strong>{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}</p>
|
<p><strong>尺寸:</strong>{{ form.size }}x{{ form.size }}px</p>
|
<p><strong>生成时间:</strong>{{ generateTime }}</p>
|
</div>
|
</div>
|
|
<div class="code-actions">
|
<el-button type="success" @click="downloadCode" icon="Download">
|
下载图片
|
</el-button>
|
<el-button type="primary" @click="copyToClipboard" icon="CopyDocument">
|
复制内容
|
</el-button>
|
<el-button @click="printCode" icon="Printer">
|
打印
|
</el-button>
|
</div>
|
</div>
|
|
<!-- 批量生成对话框 -->
|
<el-dialog v-model="batchDialogVisible" title="批量生成" width="600px">
|
<el-form :model="batchForm" label-width="120px">
|
<el-form-item label="生成数量">
|
<el-input-number v-model="batchForm.quantity" :min="1" :max="100" style="width: 100%"></el-input-number>
|
</el-form-item>
|
<el-form-item label="前缀">
|
<el-input v-model="batchForm.prefix" placeholder="请输入前缀,如:PROD_"></el-input>
|
</el-form-item>
|
<el-form-item label="起始编号">
|
<el-input-number v-model="batchForm.startNumber" :min="1" style="width: 100%"></el-input-number>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="batchDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="generateBatchCodes">开始生成</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 批量生成结果 -->
|
<div v-if="batchCodes.length > 0" class="batch-results">
|
<el-divider content-position="center">批量生成结果</el-divider>
|
|
<div class="batch-grid">
|
<div
|
v-for="(code, index) in batchCodes"
|
:key="index"
|
class="batch-item"
|
>
|
<img :src="code.url" :alt="code.content" />
|
<p class="batch-content">{{ code.content }}</p>
|
<el-button size="small" @click="downloadSingleCode(code)">下载</el-button>
|
</div>
|
</div>
|
|
<div class="batch-actions">
|
<el-button type="success" @click="downloadAllCodes">下载全部</el-button>
|
<el-button @click="clearBatchCodes">清空结果</el-button>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, onMounted } from 'vue'
|
import QRCode from 'qrcode'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { Download, CopyDocument, Printer } from '@element-plus/icons-vue'
|
|
// 定义组件名称
|
defineOptions({
|
name: 'QRCodeGenerator'
|
})
|
|
// 表单数据
|
const form = reactive({
|
type: 'qrcode',
|
content: '',
|
size: 200,
|
margin: 2,
|
foregroundColor: '#000000',
|
backgroundColor: '#FFFFFF'
|
})
|
|
// 表单验证规则
|
const rules = {
|
type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
|
content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
|
}
|
|
// 响应式数据
|
const formRef = ref()
|
const generating = ref(false)
|
const generatedCodeUrl = ref('')
|
const generateTime = ref('')
|
const batchDialogVisible = ref(false)
|
const batchForm = reactive({
|
quantity: 10,
|
prefix: '',
|
startNumber: 1
|
})
|
const batchCodes = ref([])
|
|
// 生成二维码或防伪码
|
const generateCode = async () => {
|
try {
|
await formRef.value.validate()
|
|
if (!form.content.trim()) {
|
ElMessage.warning('请输入要编码的内容')
|
return
|
}
|
|
generating.value = true
|
|
if (form.type === 'qrcode') {
|
// 生成二维码
|
generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
|
width: form.size,
|
margin: form.margin,
|
color: {
|
dark: form.foregroundColor,
|
light: form.backgroundColor
|
},
|
errorCorrectionLevel: 'M'
|
})
|
} else {
|
// 生成防伪码(使用二维码技术,但内容格式不同)
|
const securityContent = generateSecurityCode(form.content)
|
generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
|
width: form.size,
|
margin: form.margin,
|
color: {
|
dark: form.foregroundColor,
|
light: form.backgroundColor
|
},
|
errorCorrectionLevel: 'H' // 防伪码使用最高纠错级别
|
})
|
}
|
|
generateTime.value = new Date().toLocaleString()
|
ElMessage.success('生成成功!')
|
|
} catch (error) {
|
console.error('生成失败:', error)
|
ElMessage.error('生成失败:' + error.message)
|
} finally {
|
generating.value = false
|
}
|
}
|
|
// 生成防伪码内容
|
const generateSecurityCode = (content) => {
|
const timestamp = Date.now()
|
const random = Math.random().toString(36).substr(2, 8)
|
return `SEC_${content}_${timestamp}_${random}`
|
}
|
|
// 下载生成的码
|
const downloadCode = () => {
|
if (!generatedCodeUrl.value) {
|
ElMessage.warning('请先生成码')
|
return
|
}
|
|
const a = document.createElement('a')
|
a.href = generatedCodeUrl.value
|
a.download = `${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.png`
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
ElMessage.success('下载成功!')
|
}
|
|
// 复制内容到剪贴板
|
const copyToClipboard = async () => {
|
try {
|
await navigator.clipboard.writeText(form.content)
|
ElMessage.success('内容已复制到剪贴板')
|
} catch (error) {
|
// 降级方案
|
const textArea = document.createElement('textarea')
|
textArea.value = form.content
|
document.body.appendChild(textArea)
|
textArea.select()
|
document.execCommand('copy')
|
document.body.removeChild(textArea)
|
ElMessage.success('内容已复制到剪贴板')
|
}
|
}
|
|
// 打印码
|
const printCode = () => {
|
if (!generatedCodeUrl.value) {
|
ElMessage.warning('请先生成码')
|
return
|
}
|
|
const printWindow = window.open('', '_blank')
|
printWindow.document.write(`
|
<html>
|
<head>
|
<title>打印${form.type === 'qrcode' ? '二维码' : '防伪码'}</title>
|
<style>
|
body { text-align: center; padding: 20px; }
|
img { max-width: 100%; height: auto; }
|
.info { margin: 20px 0; }
|
</style>
|
</head>
|
<body>
|
<h2>${form.type === 'qrcode' ? '二维码' : '防伪码'}</h2>
|
<img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '二维码' : '防伪码'}" />
|
<div class="info">
|
<p><strong>内容:</strong>${form.content}</p>
|
<p><strong>生成时间:</strong>${generateTime.value}</p>
|
</div>
|
</body>
|
</html>
|
`)
|
printWindow.document.close()
|
printWindow.print()
|
}
|
|
// 重置表单
|
const resetForm = () => {
|
formRef.value.resetFields()
|
generatedCodeUrl.value = ''
|
generateTime.value = ''
|
batchCodes.value = []
|
}
|
|
// 批量生成
|
const generateBatchCodes = async () => {
|
if (!batchForm.prefix.trim()) {
|
ElMessage.warning('请输入前缀')
|
return
|
}
|
|
batchCodes.value = []
|
generating.value = true
|
|
try {
|
for (let i = 0; i < batchForm.quantity; i++) {
|
const number = batchForm.startNumber + i
|
const content = `${batchForm.prefix}${number.toString().padStart(6, '0')}`
|
|
let codeUrl
|
if (form.type === 'qrcode') {
|
codeUrl = await QRCode.toDataURL(content, {
|
width: form.size,
|
margin: form.margin,
|
color: {
|
dark: form.foregroundColor,
|
light: form.backgroundColor
|
}
|
})
|
} else {
|
const securityContent = generateSecurityCode(content)
|
codeUrl = await QRCode.toDataURL(securityContent, {
|
width: form.size,
|
margin: form.margin,
|
color: {
|
dark: form.foregroundColor,
|
light: form.backgroundColor
|
}
|
})
|
}
|
|
batchCodes.value.push({
|
content,
|
url: codeUrl
|
})
|
}
|
|
ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} 个码`)
|
batchDialogVisible.value = false
|
|
} catch (error) {
|
console.error('批量生成失败:', error)
|
ElMessage.error('批量生成失败:' + error.message)
|
} finally {
|
generating.value = false
|
}
|
}
|
|
// 下载单个批量生成的码
|
const downloadSingleCode = (code) => {
|
const a = document.createElement('a')
|
a.href = code.url
|
a.download = `${code.content}.png`
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
}
|
|
// 下载所有批量生成的码
|
const downloadAllCodes = async () => {
|
if (batchCodes.value.length === 0) {
|
ElMessage.warning('没有可下载的码')
|
return
|
}
|
|
try {
|
// 使用JSZip打包下载
|
const JSZip = await import('jszip')
|
const zip = new JSZip.default()
|
|
batchCodes.value.forEach((code, index) => {
|
// 将base64转换为blob
|
const base64Data = code.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(`${code.content}.png`, byteArray)
|
})
|
|
const content = await zip.generateAsync({ type: 'blob' })
|
const a = document.createElement('a')
|
a.href = URL.createObjectURL(content)
|
a.download = `批量${form.type === 'qrcode' ? '二维码' : '防伪码'}_${new Date().getTime()}.zip`
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
URL.revokeObjectURL(a.href)
|
|
ElMessage.success('批量下载完成!')
|
} catch (error) {
|
console.error('批量下载失败:', error)
|
ElMessage.error('批量下载失败,请逐个下载')
|
}
|
}
|
|
// 清空批量生成结果
|
const clearBatchCodes = () => {
|
batchCodes.value = []
|
}
|
|
// 暴露方法给父组件
|
defineExpose({
|
generateCode,
|
downloadCode,
|
resetForm,
|
form
|
})
|
</script>
|
|
<style scoped>
|
.qr-code-generator {
|
padding: 20px;
|
}
|
|
.qr-form {
|
background: #f8f9fa;
|
padding: 20px;
|
border-radius: 8px;
|
margin-bottom: 20px;
|
}
|
|
.code-display {
|
margin-top: 30px;
|
}
|
|
.code-container {
|
display: flex;
|
justify-content: center;
|
align-items: flex-start;
|
gap: 40px;
|
margin: 20px 0;
|
}
|
|
.code-image img {
|
border: 2px solid #e0e0e0;
|
border-radius: 8px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
}
|
|
.code-info {
|
text-align: left;
|
min-width: 200px;
|
}
|
|
.code-info p {
|
margin: 8px 0;
|
color: #666;
|
}
|
|
.code-actions {
|
text-align: center;
|
margin: 20px 0;
|
}
|
|
.code-actions .el-button {
|
margin: 0 10px;
|
}
|
|
.batch-results {
|
margin-top: 30px;
|
}
|
|
.batch-grid {
|
display: grid;
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
gap: 20px;
|
margin: 20px 0;
|
}
|
|
.batch-item {
|
text-align: center;
|
padding: 15px;
|
border: 1px solid #e0e0e0;
|
border-radius: 8px;
|
background: #fff;
|
}
|
|
.batch-item img {
|
width: 100px;
|
height: 100px;
|
margin-bottom: 10px;
|
}
|
|
.batch-content {
|
font-size: 12px;
|
color: #666;
|
margin: 10px 0;
|
word-break: break-all;
|
}
|
|
.batch-actions {
|
text-align: center;
|
margin: 20px 0;
|
}
|
|
.batch-actions .el-button {
|
margin: 0 10px;
|
}
|
|
@media (max-width: 768px) {
|
.code-container {
|
flex-direction: column;
|
align-items: center;
|
}
|
|
.batch-grid {
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
}
|
}
|
</style>
|