zhangwencui
6 天以前 5333935ae59999c47653122a669f4326f0173c1c
src/components/QRCodeGenerator/index.vue
@@ -1,70 +1,79 @@
<template>
  <div class="qr-code-generator">
    <!-- 二维码生成表单 -->
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="qr-form">
    <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-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"
          <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>
                      :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"
          <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>
                             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"
          <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>
                             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 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 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">
            <el-button type="primary"
                       @click="generateCode"
                       :loading="generating">
              生成{{ form.type === 'qrcode' ? '二维码' : '防伪码' }}
            </el-button>
            <el-button @click="resetForm">重置</el-button>
@@ -72,18 +81,17 @@
        </el-col>
      </el-row>
    </el-form>
    <!-- 生成的码显示区域 -->
    <div v-if="generatedCodeUrl" class="code-display">
    <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' ? '二维码' : '防伪码'" />
          <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>
@@ -91,60 +99,71 @@
          <p><strong>生成时间:</strong>{{ generateTime }}</p>
        </div>
      </div>
      <div class="code-actions">
        <el-button type="success" @click="downloadCode" icon="Download">
        <el-button type="success"
                   @click="downloadCode"
                   icon="Download">
          下载图片
        </el-button>
        <el-button type="primary" @click="copyToClipboard" icon="CopyDocument">
        <el-button type="primary"
                   @click="copyToClipboard"
                   icon="CopyDocument">
          复制内容
        </el-button>
        <el-button @click="printCode" icon="Printer">
        <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-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-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-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-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>
          <el-button type="primary" @click="generateBatchCodes">开始生成</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 批量生成结果 -->
    <div v-if="batchCodes.length > 0" class="batch-results">
    <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"
        <div v-for="(code, index) in batchCodes"
          :key="index" 
          class="batch-item"
        >
          <img :src="code.url" :alt="code.content" />
             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>
          <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 type="success"
                   @click="downloadAllCodes">下载全部</el-button>
        <el-button @click="clearBatchCodes">清空结果</el-button>
      </div>
    </div>
@@ -152,145 +171,146 @@
</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'
  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'
})
    name: "QRCodeGenerator",
  });
// 表单数据
const form = reactive({
  type: 'qrcode',
  content: '',
    type: "qrcode",
    content: "",
  size: 200,
  margin: 2,
  foregroundColor: '#000000',
  backgroundColor: '#FFFFFF'
})
    foregroundColor: "#000000",
    backgroundColor: "#FFFFFF",
  });
// 表单验证规则
const rules = {
  type: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
    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 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([])
    prefix: "",
    startNumber: 1,
  });
  const batchCodes = ref([]);
// 生成二维码或防伪码
const generateCode = async () => {
  try {
    await formRef.value.validate()
      await formRef.value.validate();
    
    if (!form.content.trim()) {
      ElMessage.warning('请输入要编码的内容')
      return
        ElMessage.warning("请输入要编码的内容");
        return;
    }
    
    generating.value = true
      generating.value = true;
    
    if (form.type === 'qrcode') {
      if (form.type === "qrcode") {
      // 生成二维码
      generatedCodeUrl.value = await QRCode.toDataURL(form.content, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
            light: form.backgroundColor,
        },
        errorCorrectionLevel: 'M'
      })
          errorCorrectionLevel: "M",
        });
    } else {
      // 生成防伪码(使用二维码技术,但内容格式不同)
      const securityContent = generateSecurityCode(form.content)
        const securityContent = generateSecurityCode(form.content);
      generatedCodeUrl.value = await QRCode.toDataURL(securityContent, {
        width: form.size,
        margin: form.margin,
        color: {
          dark: form.foregroundColor,
          light: form.backgroundColor
            light: form.backgroundColor,
        },
        errorCorrectionLevel: 'H' // 防伪码使用最高纠错级别
      })
          errorCorrectionLevel: "H", // 防伪码使用最高纠错级别
        });
    }
    
    generateTime.value = new Date().toLocaleString()
    ElMessage.success('生成成功!')
      generateTime.value = new Date().toLocaleString();
      ElMessage.success("生成成功!");
  } catch (error) {
    console.error('生成失败:', error)
    ElMessage.error('生成失败:' + error.message)
      console.error("生成失败:", error);
      ElMessage.error("生成失败:" + error.message);
  } finally {
    generating.value = false
      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 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
      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 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('内容已复制到剪贴板')
      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 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
      ElMessage.warning("请先生成码");
      return;
  }
  
  const printWindow = window.open('', '_blank')
    const printWindow = window.open("", "_blank");
  printWindow.document.write(`
    <html>
      <head>
        <title>打印${form.type === 'qrcode' ? '二维码' : '防伪码'}</title>
          <title>打印${form.type === "qrcode" ? "二维码" : "防伪码"}</title>
        <style>
          body { text-align: center; padding: 20px; }
          img { max-width: 100%; height: auto; }
@@ -298,144 +318,149 @@
        </style>
      </head>
      <body>
        <h2>${form.type === 'qrcode' ? '二维码' : '防伪码'}</h2>
        <img src="${generatedCodeUrl.value}" alt="${form.type === 'qrcode' ? '二维码' : '防伪码'}" />
          <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()
}
    `);
    printWindow.document.close();
    printWindow.print();
  };
// 重置表单
const resetForm = () => {
  formRef.value.resetFields()
  generatedCodeUrl.value = ''
  generateTime.value = ''
  batchCodes.value = []
}
    formRef.value.resetFields();
    generatedCodeUrl.value = "";
    generateTime.value = "";
    batchCodes.value = [];
  };
// 批量生成
const generateBatchCodes = async () => {
  if (!batchForm.prefix.trim()) {
    ElMessage.warning('请输入前缀')
    return
      ElMessage.warning("请输入前缀");
      return;
  }
  
  batchCodes.value = []
  generating.value = true
    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')}`
        const number = batchForm.startNumber + i;
        const content = `${batchForm.prefix}${number
          .toString()
          .padStart(6, "0")}`;
      
      let codeUrl
      if (form.type === 'qrcode') {
        let codeUrl;
        if (form.type === "qrcode") {
        codeUrl = await QRCode.toDataURL(content, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
              light: form.backgroundColor,
            },
          });
      } else {
        const securityContent = generateSecurityCode(content)
          const securityContent = generateSecurityCode(content);
        codeUrl = await QRCode.toDataURL(securityContent, {
          width: form.size,
          margin: form.margin,
          color: {
            dark: form.foregroundColor,
            light: form.backgroundColor
          }
        })
              light: form.backgroundColor,
            },
          });
      }
      
      batchCodes.value.push({
        content,
        url: codeUrl
      })
          url: codeUrl,
        });
    }
    
    ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} 个码`)
    batchDialogVisible.value = false
      ElMessage.success(`批量生成完成,共生成 ${batchForm.quantity} 个码`);
      batchDialogVisible.value = false;
  } catch (error) {
    console.error('批量生成失败:', error)
    ElMessage.error('批量生成失败:' + error.message)
      console.error("批量生成失败:", error);
      ElMessage.error("批量生成失败:" + error.message);
  } finally {
    generating.value = false
      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 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
      ElMessage.warning("没有可下载的码");
      return;
  }
  
  try {
    // 使用JSZip打包下载
    const JSZip = await import('jszip')
    const zip = new JSZip.default()
      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)
        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)
          byteNumbers[i] = byteCharacters.charCodeAt(i);
      }
      const byteArray = new Uint8Array(byteNumbers)
        const byteArray = new Uint8Array(byteNumbers);
      
      zip.file(`${code.content}.png`, byteArray)
    })
        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)
      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('批量下载完成!')
      ElMessage.success("批量下载完成!");
  } catch (error) {
    console.error('批量下载失败:', error)
    ElMessage.error('批量下载失败,请逐个下载')
      console.error("批量下载失败:", error);
      ElMessage.error("批量下载失败,请逐个下载");
  }
}
  };
// 清空批量生成结果
const clearBatchCodes = () => {
  batchCodes.value = []
}
    batchCodes.value = [];
  };
// 暴露方法给父组件
defineExpose({
  generateCode,
  downloadCode,
  resetForm,
  form
})
    form,
  });
</script>
<style scoped>