zouyu
7 天以前 1c0863efe062af3ebcdecb8c10568d779f5c8295
src/components/QRCodeGenerator/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,566 @@
<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>