<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 type="primary"
|
@click="generateBatchCodes">开始生成</el-button>
|
<el-button @click="batchDialogVisible = false">取消</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>
|