zouyu
2026-01-26 1c0863efe062af3ebcdecb8c10568d779f5c8295
src/views/productManagement/productIdentifier/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,819 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <!-- æœç´¢åŒºåŸŸ -->
      <el-row :gutter="20"
              class="search-row">
        <el-col :span="6">
          <el-input v-model="searchForm.productName"
                    placeholder="请输入产品名称"
                    clearable
                    @keyup.enter="handleSearch">
            <template #prefix>
              <el-icon>
                <Search />
              </el-icon>
            </template>
          </el-input>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.identifierType"
                     placeholder="请选择标识类型"
                     clearable>
            <el-option label="二维码"
                       value="二维码"></el-option>
            <el-option label="防伪码"
                       value="防伪码"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="searchForm.status"
                     placeholder="请选择状态"
                     clearable>
            <el-option label="已生成"
                       value="已生成"></el-option>
            <el-option label="已分配"
                       value="已分配"></el-option>
            <el-option label="已使用"
                       value="已使用"></el-option>
            <el-option label="已作废"
                       value="已作废"></el-option>
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-button type="primary"
                     @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button style="float: right;"
                     type="primary"
                     @click="handleAdd">
            æ–°å¢žæ ‡è¯†
          </el-button>
        </el-col>
      </el-row>
      <!-- äº§å“æ ‡è¯†åˆ—表 -->
      <el-table :data="filteredList"
                style="width: 100%"
                v-loading="loading"
                border
                stripe
                height="calc(100vh - 22em)">
        <el-table-column prop="id"
                         label="ID"
                         width="80"
                         align="center" />
        <el-table-column prop="productName"
                         label="产品名称"
                         width="150" />
        <el-table-column prop="productCode"
                         label="产品编码"
                         width="120" />
        <el-table-column prop="batchNo"
                         label="批次号"
                         width="120" />
        <el-table-column prop="identifierType"
                         label="标识类型"
                         width="100">
          <template #default="scope">
            <el-tag :type="getIdentifierTypeType(scope.row.identifierType)">
              {{ scope.row.identifierType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="identifierCode"
                         label="标识码" />
        <el-table-column prop="status"
                         label="状态"
                         width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="generateTime"
                         label="生成时间"
                         width="160" />
        <el-table-column label="操作"
                         fixed="right"
                         align="center"
                         width="280">
          <template #default="scope">
            <el-button link
                       type="primary"
                       @click="handleView(scope.row)">查看</el-button>
            <el-button link
                       type="primary"
                       @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link
                       type="success"
                       @click="generateQRCode(scope.row)">生成二维码</el-button>
            <el-button link
                       type="primary"
                       @click="handleExport(scope.row)">导出</el-button>
            <el-button link
                       type="primary"
                       @click="handleReassign(scope.row)"
                       v-if="scope.row.status === '已分配'">重新分配</el-button>
            <el-button link
                       type="danger"
                       @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination :total="pagination.total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="pagination.currentPage"
                  :limit="pagination.pageSize"
                  @pagination="handleCurrentChange" />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible"
               :title="dialogTitle"
               width="700px">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="产品名称"
                          prop="productName">
              <el-input v-model="form.productName"
                        placeholder="请输入产品名称"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品编码"
                          prop="productCode">
              <el-input v-model="form.productCode"
                        placeholder="请输入产品编码"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="批次号"
                          prop="batchNo">
              <el-input v-model="form.batchNo"
                        placeholder="请输入批次号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="标识类型"
                          prop="identifierType">
              <el-select v-model="form.identifierType"
                         placeholder="请选择标识类型"
                         style="width: 100%">
                <el-option label="二维码"
                           value="二维码"></el-option>
                <el-option label="防伪码"
                           value="防伪码"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="生成数量"
                          prop="quantity">
              <el-input-number v-model="form.quantity"
                               :min="1"
                               :max="10000"
                               style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态"
                          prop="status">
              <el-select v-model="form.status"
                         placeholder="请选择状态"
                         style="width: 100%">
                <el-option label="已生成"
                           value="已生成"></el-option>
                <el-option label="已分配"
                           value="已分配"></el-option>
                <el-option label="已使用"
                           value="已使用"></el-option>
                <el-option label="已作废"
                           value="已作废"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注"
                          prop="remark">
              <el-input type="textarea"
                        v-model="form.remark"
                        placeholder="请输入备注信息"
                        rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="handleSubmit">ç¡® å®š</el-button>
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ ‡è¯†ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="generateDialogVisible"
               title="标识生成"
               width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
        </el-form-item>
        <el-form-item label="产品编码">
          <span>{{ currentProduct.productCode }}</span>
        </el-form-item>
        <el-form-item label="批次号">
          <span>{{ currentProduct.batchNo }}</span>
        </el-form-item>
        <el-form-item label="标识类型">
          <span>{{ currentProduct.identifierType }}</span>
        </el-form-item>
        <el-form-item label="生成数量"
                      prop="generateQuantity">
          <el-input-number v-model="generateQuantity"
                           :min="1"
                           :max="10000"
                           style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="编码规则"
                      prop="codeRule">
          <el-select v-model="codeRule"
                     placeholder="请选择编码规则"
                     style="width: 100%">
            <el-option label="产品编码+批次号+序号"
                       value="产品编码+批次号+序号"></el-option>
            <el-option label="时间戳+随机数"
                       value="时间戳+随机数"></el-option>
            <el-option label="自定义规则"
                       value="自定义规则"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="自定义前缀"
                      prop="customPrefix"
                      v-if="codeRule === '自定义规则'">
          <el-input v-model="customPrefix"
                    placeholder="请输入自定义前缀"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="generateIdentifiers">生 æˆ</el-button>
          <el-button @click="generateDialogVisible = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- é‡æ–°åˆ†é…å¯¹è¯æ¡† -->
    <el-dialog v-model="reassignDialogVisible"
               title="重新分配标识"
               width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
        </el-form-item>
        <el-form-item label="标识码">
          <span>{{ currentProduct.identifierCode }}</span>
        </el-form-item>
        <el-form-item label="新批次号"
                      prop="newBatchNo">
          <el-input v-model="newBatchNo"
                    placeholder="请输入新批次号"></el-input>
        </el-form-item>
        <el-form-item label="分配原因"
                      prop="reassignReason">
          <el-input type="textarea"
                    v-model="reassignReason"
                    rows="3"
                    placeholder="请输入重新分配原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="saveReassign">ç¡® å®š</el-button>
          <el-button @click="reassignDialogVisible = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äºŒç»´ç é¢„览对话框 -->
    <el-dialog v-model="qrCodeDialogVisible"
               title="二维码预览"
               width="500px"
               center>
      <div class="qr-preview-container">
        <div v-if="qrCodeUrl"
             class="qr-image-container">
          <img :src="qrCodeUrl"
               alt="二维码"
               class="qr-image" />
          <div class="qr-info">
            <p><strong>产品名称:</strong>{{ currentQRProduct.productName }}</p>
            <p><strong>产品编码:</strong>{{ currentQRProduct.productCode }}</p>
            <p><strong>批次号:</strong>{{ currentQRProduct.batchNo }}</p>
            <p><strong>标识码:</strong>{{ currentQRProduct.identifierCode }}</p>
            <p><strong>标识类型:</strong>{{ currentQRProduct.identifierType }}</p>
          </div>
        </div>
        <div v-else
             class="qr-loading">
          <el-icon class="is-loading">
            <Loading />
          </el-icon>
          <p>正在生成二维码...</p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="qrCodeDialogVisible = false">关闭</el-button>
          <el-button v-if="qrCodeUrl"
                     type="primary"
                     @click="copyQRContent"
                     icon="CopyDocument">
            å¤åˆ¶å†…容
          </el-button>
          <el-button v-if="qrCodeUrl"
                     type="success"
                     @click="downloadQRCode"
                     icon="Download">
            ä¸‹è½½äºŒç»´ç 
          </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { ref, reactive, computed } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Plus, Search, Loading, Download } from "@element-plus/icons-vue";
  import Pagination from "@/components/PIMTable/Pagination.vue";
  import QRCode from "qrcode";
  // å“åº”式数据
  const loading = ref(false);
  const searchForm = reactive({
    productName: "",
    identifierType: "",
    status: "",
  });
  const identifierList = ref([
    {
      id: 1,
      productName: "工业传感器A型",
      productCode: "SENSOR001",
      batchNo: "B202312001",
      identifierType: "二维码",
      identifierCode: "QR_SENSOR001_B202312001_001",
      status: "已分配",
      generateTime: "2023-12-01 10:00:00",
      remark: "重要产品标识",
    },
    {
      id: 2,
      productName: "控制面板B型",
      productCode: "PANEL002",
      batchNo: "B202312002",
      identifierType: "防伪码",
      identifierCode: "SEC_PANEL002_B202312002_001",
      status: "已生成",
      generateTime: "2023-12-02 14:30:00",
      remark: "常规产品标识",
    },
    {
      id: 3,
      productName: "数据采集器C型",
      productCode: "COLLECTOR003",
      batchNo: "B202312003",
      identifierType: "防伪码",
      identifierCode: "SEC_COLLECTOR003_B202312003_001",
      status: "已使用",
      generateTime: "2023-12-03 09:15:00",
      remark: "测试产品标识",
    },
  ]);
  const pagination = reactive({
    total: 3,
    currentPage: 1,
    pageSize: 10,
  });
  const dialogVisible = ref(false);
  const dialogTitle = ref("新增标识");
  const form = reactive({
    productName: "",
    productCode: "",
    batchNo: "",
    identifierType: "",
    quantity: 1,
    status: "已生成",
    remark: "",
  });
  const rules = {
    productName: [{ required: true, message: "请输入产品名称", trigger: "blur" }],
    productCode: [{ required: true, message: "请输入产品编码", trigger: "blur" }],
    batchNo: [{ required: true, message: "请输入批次号", trigger: "blur" }],
    identifierType: [
      { required: true, message: "请选择标识类型", trigger: "change" },
    ],
    quantity: [{ required: true, message: "请输入生成数量", trigger: "blur" }],
    status: [{ required: true, message: "请选择状态", trigger: "change" }],
  };
  const isEdit = ref(false);
  const editId = ref(null);
  const generateDialogVisible = ref(false);
  const reassignDialogVisible = ref(false);
  const currentProduct = ref({});
  const generateQuantity = ref(1);
  const codeRule = ref("");
  const customPrefix = ref("");
  const newBatchNo = ref("");
  const reassignReason = ref("");
  const formRef = ref();
  // äºŒç»´ç ç›¸å…³å˜é‡
  const qrCodeDialogVisible = ref(false);
  const qrCodeUrl = ref("");
  const currentQRProduct = ref({});
  // è®¡ç®—属性
  const filteredList = computed(() => {
    let list = identifierList.value;
    if (searchForm.productName) {
      list = list.filter(item =>
        item.productName.includes(searchForm.productName)
      );
    }
    if (searchForm.identifierType) {
      list = list.filter(
        item => item.identifierType === searchForm.identifierType
      );
    }
    if (searchForm.status) {
      list = list.filter(item => item.status === searchForm.status);
    }
    return list;
  });
  // æ–¹æ³•
  const getIdentifierTypeType = type => {
    const typeMap = {
      äºŒç»´ç : "success",
      é˜²ä¼ªç : "warning",
    };
    return typeMap[type] || "info";
  };
  const getStatusType = status => {
    const statusMap = {
      å·²ç”Ÿæˆ: "info",
      å·²åˆ†é…: "primary",
      å·²ä½¿ç”¨: "success",
      å·²ä½œåºŸ: "danger",
    };
    return statusMap[status] || "info";
  };
  const handleSearch = () => {
    // æœç´¢é€»è¾‘已在computed中处理
  };
  const resetSearch = () => {
    searchForm.productName = "";
    searchForm.identifierType = "";
    searchForm.status = "";
  };
  const handleAdd = () => {
    dialogTitle.value = "新增标识";
    isEdit.value = false;
    form.productName = "";
    form.productCode = "";
    form.batchNo = "";
    form.identifierType = "";
    form.quantity = 1;
    form.status = "已生成";
    form.remark = "";
    dialogVisible.value = true;
  };
  const handleView = row => {
    // æŸ¥çœ‹æ ‡è¯†è¯¦æƒ…
    ElMessage.info("查看标识详情功能待实现");
  };
  const handleEdit = row => {
    dialogTitle.value = "编辑标识";
    isEdit.value = true;
    editId.value = row.id;
    Object.assign(form, row);
    dialogVisible.value = true;
  };
  const handleExport = row => {
    // å¯¼å‡ºæ ‡è¯†
    ElMessage.success(`已导出标识: ${row.identifierCode}`);
  };
  const handleReassign = row => {
    currentProduct.value = row;
    newBatchNo.value = "";
    reassignReason.value = "";
    reassignDialogVisible.value = true;
  };
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该标识吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      const index = identifierList.value.findIndex(item => item.id === row.id);
      if (index > -1) {
        identifierList.value.splice(index, 1);
        pagination.total--;
        ElMessage.success("删除成功");
      }
    });
  };
  // ç”ŸæˆäºŒç»´ç 
  const generateQRCode = async row => {
    try {
      // æ£€æŸ¥å¿…要字段
      if (!row.productName || !row.productCode || !row.batchNo) {
        ElMessage.warning("产品信息不完整,无法生成二维码");
        return;
      }
      currentQRProduct.value = row;
      qrCodeUrl.value = "";
      qrCodeDialogVisible.value = true;
      // æž„建二维码内容
      let qrContent = "";
      if (row.identifierType === "二维码") {
        qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`;
      } else if (row.identifierType === "防伪码") {
        // é˜²ä¼ªç æ ¼å¼ï¼šSEC_产品编码_批次号_时间戳_随机数
        const timestamp = Date.now();
        const random = Math.random().toString(36).substr(2, 8);
        qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`;
      }
      // ç”ŸæˆäºŒç»´ç 
      qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
        width: 256,
        margin: 2,
        color: {
          dark: "#000000",
          light: "#FFFFFF",
        },
        errorCorrectionLevel: row.identifierType === "防伪码" ? "H" : "M",
      });
      ElMessage.success("二维码生成成功!");
    } catch (error) {
      console.error("生成二维码失败:", error);
      ElMessage.error("生成二维码失败:" + error.message);
      qrCodeDialogVisible.value = false;
    }
  };
  // ä¸‹è½½äºŒç»´ç 
  const downloadQRCode = () => {
    if (!qrCodeUrl.value) {
      ElMessage.warning("请先生成二维码");
      return;
    }
    const a = document.createElement("a");
    a.href = qrCodeUrl.value;
    a.download = `${currentQRProduct.value.productName}_${
      currentQRProduct.value.identifierType
    }_${new Date().getTime()}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    ElMessage.success("下载成功!");
  };
  // å¤åˆ¶äºŒç»´ç å†…容
  const copyQRContent = async () => {
    if (!currentQRProduct.value) {
      ElMessage.warning("没有可复制的内容");
      return;
    }
    try {
      let content = "";
      if (currentQRProduct.value.identifierType === "二维码") {
        content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`;
      } else if (currentQRProduct.value.identifierType === "防伪码") {
        const timestamp = Date.now();
        const random = Math.random().toString(36).substr(2, 8);
        content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`;
      }
      await navigator.clipboard.writeText(content);
      ElMessage.success("内容已复制到剪贴板");
    } catch (error) {
      // é™çº§æ–¹æ¡ˆ
      const textArea = document.createElement("textarea");
      textArea.value = content;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
      ElMessage.success("内容已复制到剪贴板");
    }
  };
  const generateIdentifiers = () => {
    if (!codeRule.value) {
      ElMessage.warning("请选择编码规则");
      return;
    }
    // ç”Ÿæˆæ ‡è¯†çš„逻辑
    const newIdentifiers = [];
    for (let i = 1; i <= generateQuantity.value; i++) {
      let identifierCode = "";
      if (codeRule.value === "产品编码+批次号+序号") {
        identifierCode = `${currentProduct.value.productCode}_${
          currentProduct.value.batchNo
        }_${String(i).padStart(3, "0")}`;
      } else if (codeRule.value === "时间戳+随机数") {
        identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
      } else if (codeRule.value === "自定义规则") {
        identifierCode = `${customPrefix.value || "CUSTOM"}_${Date.now()}_${i}`;
      }
      newIdentifiers.push({
        id: Math.max(...identifierList.value.map(item => item.id)) + i,
        productName: currentProduct.value.productName,
        productCode: currentProduct.value.productCode,
        batchNo: currentProduct.value.batchNo,
        identifierType: currentProduct.value.identifierType,
        identifierCode: identifierCode,
        status: "已生成",
        generateTime: new Date().toLocaleString(),
        remark: "批量生成",
      });
    }
    identifierList.value.push(...newIdentifiers);
    pagination.total += newIdentifiers.length;
    ElMessage.success(`成功生成 ${newIdentifiers.length} ä¸ªæ ‡è¯†`);
    generateDialogVisible.value = false;
  };
  const saveReassign = () => {
    if (!newBatchNo.value) {
      ElMessage.warning("请输入新批次号");
      return;
    }
    const index = identifierList.value.findIndex(
      item => item.id === currentProduct.value.id
    );
    if (index > -1) {
      identifierList.value[index].batchNo = newBatchNo.value;
      identifierList.value[index].status = "已分配";
      ElMessage.success("标识重新分配成功");
      reassignDialogVisible.value = false;
    }
  };
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        if (isEdit.value) {
          // ç¼–辑
          const index = identifierList.value.findIndex(
            item => item.id === editId.value
          );
          if (index > -1) {
            identifierList.value[index] = { ...form, id: editId.value };
            ElMessage.success("编辑成功");
          }
        } else {
          // æ–°å¢ž
          const newId =
            Math.max(...identifierList.value.map(item => item.id)) + 1;
          // æ ¹æ®æ ‡è¯†ç±»åž‹ç”Ÿæˆä¸åŒçš„æ ‡è¯†ç 
          let identifierCode = "";
          if (form.identifierType === "二维码") {
            identifierCode = `QR_${form.productCode}_${form.batchNo}_001`;
          } else if (form.identifierType === "防伪码") {
            identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`;
          }
          identifierList.value.push({
            ...form,
            id: newId,
            identifierCode: identifierCode,
            generateTime: new Date().toLocaleString(),
          });
          pagination.total++;
          ElMessage.success("新增成功");
        }
        dialogVisible.value = false;
      }
    });
  };
  const handleCurrentChange = val => {
    pagination.currentPage = val.page;
    pagination.pageSize = val.limit;
  };
</script>
<style scoped>
  .search-row {
    margin-bottom: 20px;
  }
  .quick-actions-row {
    margin-bottom: 20px;
  }
  .quick-actions-row .el-alert {
    margin-bottom: 0;
  }
  .quick-actions-row .el-alert p {
    margin: 5px 0;
    font-size: 14px;
    line-height: 1.5;
  }
  /* äºŒç»´ç é¢„览样式 */
  .qr-preview-container {
    text-align: center;
    padding: 20px;
  }
  .qr-image-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
  }
  .qr-image {
    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 {
    text-align: left;
    background: #f8f9fa;
    padding: 15px;
    border-radius: 8px;
    min-width: 300px;
  }
  .qr-info p {
    margin: 8px 0;
    color: #666;
    font-size: 14px;
  }
  .qr-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 15px;
    padding: 40px 0;
  }
  .qr-loading .el-icon {
    font-size: 32px;
    color: #409eff;
  }
  .qr-loading p {
    color: #666;
    margin: 0;
  }
</style>