<template>
|
<div class="app-container">
|
<el-row :gutter="20">
|
<!-- 左侧:生成表单 -->
|
<el-col :span="12">
|
<el-card class="box-card">
|
<template #header>
|
<div class="card-header">
|
<span>二维码生成</span>
|
</div>
|
</template>
|
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
<el-form-item label="内容" prop="content">
|
<el-input
|
v-model="form.content"
|
type="textarea"
|
:rows="4"
|
placeholder="请输入要生成二维码的内容,如:网址、文本、联系方式等"
|
></el-input>
|
</el-form-item>
|
|
<el-form-item label="尺寸">
|
<el-select v-model="form.size" style="width: 100%">
|
<el-option label="小尺寸 (128x128)" value="128"></el-option>
|
<el-option label="标准尺寸 (256x256)" value="256"></el-option>
|
<el-option label="大尺寸 (512x512)" value="512"></el-option>
|
<el-option label="超大尺寸 (1024x1024)" value="1024"></el-option>
|
</el-select>
|
</el-form-item>
|
|
<el-form-item label="前景色">
|
<el-color-picker v-model="form.foregroundColor"></el-color-picker>
|
</el-form-item>
|
|
<el-form-item label="背景色">
|
<el-color-picker v-model="form.backgroundColor"></el-color-picker>
|
</el-form-item>
|
|
<el-form-item>
|
<el-button type="primary" @click="generateQRCode" :loading="generating" size="large">
|
生成二维码
|
</el-button>
|
<el-button @click="resetForm">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
</el-col>
|
|
<!-- 右侧:预览和下载 -->
|
<el-col :span="12">
|
<el-card class="box-card">
|
<template #header>
|
<div class="card-header">
|
<span>预览与下载</span>
|
<el-button
|
v-if="qrCodeUrl"
|
type="success"
|
@click="downloadQRCode"
|
icon="Download"
|
size="small"
|
>
|
下载图片
|
</el-button>
|
</div>
|
</template>
|
|
<div v-if="qrCodeUrl" class="preview-container">
|
<div class="qr-preview">
|
<img :src="qrCodeUrl" alt="生成的二维码" />
|
</div>
|
<div class="qr-info">
|
<p><strong>内容:</strong>{{ form.content }}</p>
|
<p><strong>尺寸:</strong>{{ form.size }}x{{ form.size }}px</p>
|
<p><strong>生成时间:</strong>{{ generateTime }}</p>
|
</div>
|
</div>
|
|
<div v-else class="empty-preview">
|
<el-empty description="请先生成二维码" :image-size="100"></el-empty>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 防伪码生成区域 -->
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="24">
|
<el-card class="box-card">
|
<template #header>
|
<div class="card-header">
|
<span>防伪码生成</span>
|
<el-button type="warning" @click="showSecurityDialog" icon="Shield">
|
生成防伪码
|
</el-button>
|
</div>
|
</template>
|
|
<div class="security-info">
|
<p>防伪码特点:</p>
|
<ul>
|
<li>包含时间戳和随机数,确保唯一性</li>
|
<li>使用最高纠错级别,提高扫描成功率</li>
|
<li>支持批量生成和下载</li>
|
<li>适用于产品防伪、文档验证等场景</li>
|
</ul>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 防伪码生成对话框 -->
|
<el-dialog v-model="securityDialogVisible" title="防伪码生成" width="600px">
|
<el-form :model="securityForm" :rules="securityRules" ref="securityFormRef" label-width="100px">
|
<el-form-item label="产品名称" prop="productName">
|
<el-input v-model="securityForm.productName" placeholder="请输入产品名称"></el-input>
|
</el-form-item>
|
|
<el-form-item label="产品编码" prop="productCode">
|
<el-input v-model="securityForm.productCode" placeholder="请输入产品编码"></el-input>
|
</el-form-item>
|
|
<el-form-item label="批次号" prop="batchNo">
|
<el-input v-model="securityForm.batchNo" placeholder="请输入批次号"></el-input>
|
</el-form-item>
|
|
<el-form-item label="生成数量" prop="quantity">
|
<el-input-number
|
v-model="securityForm.quantity"
|
:min="1"
|
:max="100"
|
style="width: 100%"
|
></el-input-number>
|
</el-form-item>
|
|
<el-form-item label="尺寸">
|
<el-select v-model="securityForm.size" style="width: 100%">
|
<el-option label="标准尺寸 (256x256)" value="256"></el-option>
|
<el-option label="大尺寸 (512x512)" value="512"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="securityDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="generateSecurityCodes" :loading="generatingSecurity">
|
开始生成
|
</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 防伪码结果展示 -->
|
<el-dialog v-model="securityResultVisible" title="防伪码生成结果" width="80%" top="5vh">
|
<div v-if="securityResults.length > 0" class="security-results">
|
<div class="results-header">
|
<p>共生成 {{ securityResults.length }} 个防伪码</p>
|
<el-button type="success" @click="downloadAllSecurityCodes" icon="Download">
|
下载全部
|
</el-button>
|
</div>
|
|
<div class="results-grid">
|
<div
|
v-for="(result, index) in securityResults"
|
:key="index"
|
class="result-item"
|
>
|
<img :src="result.url" :alt="result.content" />
|
<p class="result-content">{{ result.content }}</p>
|
<el-button size="small" @click="downloadSingleSecurityCode(result)">
|
下载
|
</el-button>
|
</div>
|
</div>
|
</div>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import QRCode from 'qrcode'
|
import JSZip from 'jszip'
|
|
defineOptions({
|
name: 'QRCodeSimple'
|
})
|
|
// 二维码生成表单
|
const form = reactive({
|
content: '',
|
size: '256',
|
foregroundColor: '#000000',
|
backgroundColor: '#FFFFFF'
|
})
|
|
const rules = {
|
content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
|
}
|
|
// 防伪码生成表单
|
const securityForm = reactive({
|
productName: '',
|
productCode: '',
|
batchNo: '',
|
quantity: 10,
|
size: '256'
|
})
|
|
const securityRules = {
|
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
|
productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
|
batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }],
|
quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }]
|
}
|
|
// 响应式数据
|
const formRef = ref()
|
const securityFormRef = ref()
|
const generating = ref(false)
|
const generatingSecurity = ref(false)
|
const qrCodeUrl = ref('')
|
const generateTime = ref('')
|
const securityDialogVisible = ref(false)
|
const securityResultVisible = ref(false)
|
const securityResults = ref([])
|
|
// 生成二维码
|
const generateQRCode = async () => {
|
try {
|
await formRef.value.validate()
|
|
if (!form.content.trim()) {
|
ElMessage.warning('请输入要生成二维码的内容')
|
return
|
}
|
|
generating.value = true
|
|
qrCodeUrl.value = await QRCode.toDataURL(form.content, {
|
width: parseInt(form.size),
|
margin: 2,
|
color: {
|
dark: form.foregroundColor,
|
light: form.backgroundColor
|
},
|
errorCorrectionLevel: 'M'
|
})
|
|
generateTime.value = new Date().toLocaleString()
|
ElMessage.success('二维码生成成功!')
|
|
} catch (error) {
|
console.error('生成二维码失败:', error)
|
ElMessage.error('生成二维码失败:' + error.message)
|
} finally {
|
generating.value = false
|
}
|
}
|
|
// 下载二维码
|
const downloadQRCode = () => {
|
if (!qrCodeUrl.value) {
|
ElMessage.warning('请先生成二维码')
|
return
|
}
|
|
const a = document.createElement('a')
|
a.href = qrCodeUrl.value
|
a.download = `二维码_${new Date().getTime()}.png`
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
ElMessage.success('下载成功!')
|
}
|
|
// 重置表单
|
const resetForm = () => {
|
formRef.value.resetFields()
|
qrCodeUrl.value = ''
|
generateTime.value = ''
|
}
|
|
// 显示防伪码对话框
|
const showSecurityDialog = () => {
|
securityDialogVisible.value = true
|
// 重置表单
|
Object.assign(securityForm, {
|
productName: '',
|
productCode: '',
|
batchNo: '',
|
quantity: 10,
|
size: '256'
|
})
|
}
|
|
// 生成防伪码
|
const generateSecurityCodes = async () => {
|
try {
|
await securityFormRef.value.validate()
|
|
generatingSecurity.value = true
|
securityResults.value = []
|
|
for (let i = 1; i <= securityForm.quantity; i++) {
|
const timestamp = Date.now() + i // 确保每个码都有不同的时间戳
|
const random = Math.random().toString(36).substr(2, 8)
|
const content = `SEC_${securityForm.productCode}_${securityForm.batchNo}_${timestamp}_${random}`
|
|
const codeUrl = await QRCode.toDataURL(content, {
|
width: parseInt(securityForm.size),
|
margin: 2,
|
color: {
|
dark: '#000000',
|
light: '#FFFFFF'
|
},
|
errorCorrectionLevel: 'H' // 最高纠错级别
|
})
|
|
securityResults.value.push({
|
content,
|
url: codeUrl,
|
productName: securityForm.productName,
|
productCode: securityForm.productCode,
|
batchNo: securityForm.batchNo,
|
generateTime: new Date().toLocaleString()
|
})
|
}
|
|
securityDialogVisible.value = false
|
securityResultVisible.value = true
|
ElMessage.success(`防伪码生成完成,共生成 ${securityForm.quantity} 个`)
|
|
} catch (error) {
|
console.error('生成防伪码失败:', error)
|
ElMessage.error('生成防伪码失败:' + error.message)
|
} finally {
|
generatingSecurity.value = false
|
}
|
}
|
|
// 下载单个防伪码
|
const downloadSingleSecurityCode = (result) => {
|
const a = document.createElement('a')
|
a.href = result.url
|
a.download = `${result.content}.png`
|
document.body.appendChild(a)
|
a.click()
|
document.body.removeChild(a)
|
ElMessage.success('下载成功!')
|
}
|
|
// 下载所有防伪码
|
const downloadAllSecurityCodes = async () => {
|
if (securityResults.value.length === 0) {
|
ElMessage.warning('没有可下载的防伪码')
|
return
|
}
|
|
try {
|
const zip = new JSZip()
|
|
// 创建说明文件
|
const readme = `防伪码生成说明
|
产品名称: ${securityForm.productName}
|
产品编码: ${securityForm.productCode}
|
批次号: ${securityForm.batchNo}
|
数量: ${securityResults.value.length}
|
尺寸: ${securityForm.size}x${securityForm.size}px
|
生成时间: ${new Date().toLocaleString()}
|
|
文件列表:
|
${securityResults.value.map((item, index) => `${index + 1}. ${item.content}.png`).join('\n')}
|
`
|
|
zip.file('README.txt', readme)
|
|
// 添加图片文件
|
securityResults.value.forEach((result) => {
|
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 = `防伪码_${securityForm.productCode}_${securityForm.batchNo}_${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('批量下载失败:' + error.message)
|
}
|
}
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
.box-card {
|
margin-bottom: 20px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.preview-container {
|
text-align: center;
|
}
|
|
.qr-preview img {
|
max-width: 100%;
|
height: auto;
|
border: 2px solid #e0e0e0;
|
border-radius: 8px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
}
|
|
.qr-info {
|
margin-top: 20px;
|
text-align: left;
|
background: #f8f9fa;
|
padding: 15px;
|
border-radius: 8px;
|
}
|
|
.qr-info p {
|
margin: 8px 0;
|
color: #666;
|
}
|
|
.empty-preview {
|
padding: 40px 0;
|
}
|
|
.security-info {
|
padding: 20px;
|
background: #f8f9fa;
|
border-radius: 8px;
|
}
|
|
.security-info p {
|
font-weight: bold;
|
margin-bottom: 10px;
|
}
|
|
.security-info ul {
|
margin: 0;
|
padding-left: 20px;
|
}
|
|
.security-info li {
|
margin: 8px 0;
|
color: #666;
|
}
|
|
.security-results {
|
padding: 20px;
|
}
|
|
.results-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
padding-bottom: 15px;
|
border-bottom: 1px solid #e0e0e0;
|
}
|
|
.results-header p {
|
font-size: 16px;
|
font-weight: bold;
|
margin: 0;
|
}
|
|
.results-grid {
|
display: grid;
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
gap: 20px;
|
}
|
|
.result-item {
|
text-align: center;
|
padding: 15px;
|
border: 1px solid #e0e0e0;
|
border-radius: 8px;
|
background: #fff;
|
}
|
|
.result-item img {
|
width: 100px;
|
height: 100px;
|
margin-bottom: 10px;
|
}
|
|
.result-content {
|
font-size: 12px;
|
color: #666;
|
margin: 10px 0;
|
word-break: break-all;
|
}
|
|
.dialog-footer {
|
text-align: right;
|
}
|
</style>
|