zhangwencui
昨天 56751952bb8e1a404099d609fa6242a61f008998
产品二维码生成
已修改1个文件
267 ■■■■■ 文件已修改
src/views/basicData/product/index.vue 267 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue
@@ -2,26 +2,20 @@
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
          v-model="search"
        <el-input v-model="search"
          style="width: 210px"
          placeholder="输入关键字进行搜索"
          @input="searchFilter"
          @change="searchFilter"
          @clear="searchFilter"
          clearable
          prefix-icon="Search"
        />
        <el-button
          type="primary"
                  prefix-icon="Search" />
        <el-button type="primary"
          @click="openProDia('addOne')"
          style="margin-left: 10px"
          >新增产品大类</el-button
        >
                   style="margin-left: 10px">新增产品大类</el-button>
      </div>
      <div ref="containerRef">
        <el-tree
          ref="tree"
        <el-tree ref="tree"
          v-loading="treeLoad"
          :data="list"
          @node-click="handleNodeClick"
@@ -32,8 +26,7 @@
          highlight-current
          node-key="id"
          class="product-tree-scroll"
          style="height: calc(100vh - 190px); overflow-y: auto"
        >
                 style="height: calc(100vh - 190px); overflow-y: auto">
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span class="tree-node-content">
@@ -44,23 +37,22 @@
                <span class="tree-node-label">{{ data.label }}</span>
              </span>
              <div>
                <el-button
                  type="primary"
                <el-button type="primary"
                  link
                  @click="openProDia('edit', data)"
                >
                           @click="openProDia('edit', data)">
                  编辑
                </el-button>
                <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3">
                <el-button type="primary"
                           link
                           @click="openProDia('add', data)"
                           :disabled="node.level >= 3">
                  添加产品
                </el-button>
                <el-button
                  v-if="!node.childNodes.length"
                <el-button v-if="!node.childNodes.length"
                  style="margin-left: 4px"
                  type="danger"
                  link
                  @click="remove(node, data)"
                >
                           @click="remove(node, data)">
                  删除
                </el-button>
              </div>
@@ -70,117 +62,134 @@
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px" v-if="isShowButton">
        <el-button type="primary" @click="openModelDia('add')">
      <div style="margin-bottom: 10px"
           v-if="isShowButton">
        <el-button type="primary"
                   @click="openModelDia('add')">
          新增规格型号
        </el-button>
        <ImportExcel :product-id="currentId" @uploadSuccess="getModelList" />
        <el-button
          type="danger"
        <ImportExcel :product-id="currentId"
                     @uploadSuccess="getModelList" />
        <el-button type="danger"
          @click="handleDelete"
          style="margin-left: 10px"
          plain
        >
                   plain>
          删除
        </el-button>
      </div>
      <PIMTable
        rowKey="id"
      <PIMTable rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      ></PIMTable>
                @pagination="pagination"></PIMTable>
    </div>
    <el-dialog v-model="productDia" title="产品" width="400px" @keydown.enter.prevent>
      <el-form
        :model="form"
    <el-dialog v-model="productDia"
               title="产品"
               width="400px"
               @keydown.enter.prevent>
      <el-form :model="form"
        label-width="140px"
        label-position="top"
        :rules="rules"
        ref="formRef"
      >
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品名称:" prop="productName">
              <el-input
                v-model="form.productName"
            <el-form-item label="产品名称:"
                          prop="productName">
              <el-input v-model="form.productName"
                placeholder="请输入产品名称"
                maxlength="20"
                show-word-limit
                clearable
                @keydown.enter.prevent
              />
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeProDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog
      v-model="modelDia"
    <el-dialog v-model="modelDia"
      title="规格型号"
      width="400px"
      @close="closeModelDia"
      @keydown.enter.prevent
    >
      <el-form
        :model="modelForm"
               @keydown.enter.prevent>
      <el-form :model="modelForm"
        label-width="140px"
        label-position="top"
        :rules="modelRules"
        ref="modelFormRef"
      >
               ref="modelFormRef">
        <el-row>
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="model">
              <el-input
                v-model="modelForm.model"
            <el-form-item label="规格型号:"
                          prop="model">
              <el-input v-model="modelForm.model"
                placeholder="请输入规格型号"
                clearable
                @keydown.enter.prevent
              />
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="厚度:" prop="thickness">
              <el-input
                v-model="modelForm.thickness"
            <el-form-item label="厚度:"
                          prop="thickness">
              <el-input v-model="modelForm.thickness"
                placeholder="请输入厚度"
                clearable
                @keydown.enter.prevent
                @blur="modelForm.thickness = formatThicknessTo15(modelForm.thickness)"
              />
                        @blur="modelForm.thickness = formatThicknessTo15(modelForm.thickness)" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="单位:" prop="unit">
              <el-input
                v-model="modelForm.unit"
            <el-form-item label="单位:"
                          prop="unit">
              <el-input v-model="modelForm.unit"
                placeholder="请输入单位"
                clearable
                @keydown.enter.prevent
              />
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitModelForm">确认</el-button>
          <el-button type="primary"
                     @click="submitModelForm">确认</el-button>
          <el-button @click="closeModelDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 二维码对话框 -->
    <el-dialog v-model="qrCodeDialog"
               title="产品二维码"
               width="400px">
      <div class="qrcode-container">
        <img v-if="qrCodeUrl"
             :src="qrCodeUrl"
             class="qrcode-image" />
        <div v-else
             class="loading">生成中...</div>
      </div>
      <div style="text-align: center;">
        {{ qrCodeName }}
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="qrCodeDialog = false">关闭</el-button>
          <el-button type="primary"
                     @click="saveQrCodeAsImage"
                     :disabled="!qrCodeUrl">保存为图片</el-button>
        </div>
      </template>
    </el-dialog>
@@ -188,8 +197,10 @@
</template>
<script setup>
import { ref } from "vue";
  import { ref, getCurrentInstance, toRefs, reactive } from "vue";
import { ElMessageBox } from "element-plus";
  import QRCode from "qrcode";
  import { saveAs } from "file-saver";
import {
  addOrEditProduct,
  addOrEditProductModel,
@@ -206,6 +217,9 @@
const productDia = ref(false);
const modelDia = ref(false);
  const qrCodeDialog = ref(false);
  const qrCodeUrl = ref("");
  const currentProductId = ref("");
const modelOperationType = ref("");
const search = ref("");
const currentId = ref("");
@@ -223,7 +237,7 @@
    label: "厚度",
    prop: "thickness",
    // 列表展示时统一保留 15 位小数
    formatData: (val) => formatThicknessTo15(val),
      formatData: val => formatThicknessTo15(val),
  },
  {
    label: "单位",
@@ -237,8 +251,15 @@
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          clickFun: row => {
          openModelDia("edit", row);
          },
        },
        {
          name: "生成二维码",
          type: "text",
          clickFun: row => {
            generateQrcode(row);
        },
      },
    ],
@@ -275,7 +296,7 @@
const { form, rules, modelForm, modelRules } = toRefs(data);
// 把厚度格式化成固定 15 位小数(用于展示/提交)
const formatThicknessTo15 = (val) => {
  const formatThicknessTo15 = val => {
  if (val === null || val === undefined) return "";
  const s = String(val).trim();
  if (s === "") return "";
@@ -287,14 +308,14 @@
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList()
    .then((res) => {
      .then(res => {
      list.value = res;
      list.value.forEach((a) => {
        list.value.forEach(a => {
        expandedKeys.value.push(a.label);
      });
      treeLoad.value = false;
    })
    .catch((err) => {
      .catch(err => {
      treeLoad.value = false;
    });
};
@@ -324,7 +345,7 @@
};
// 提交产品名称修改
const submitForm = () => {
  proxy.$refs.formRef.validate((valid) => {
    proxy.$refs.formRef.validate(valid => {
    if (valid) {
      if (operationType.value === "add") {
        form.value.parentId = currentId.value;
@@ -336,7 +357,7 @@
        form.value.id = currentId.value;
        form.value.parentId = "";
      }
      addOrEditProduct(form.value).then((res) => {
        addOrEditProduct(form.value).then(res => {
        proxy.$modal.msgSuccess("提交成功");
        closeProDia();
        getProductTreeList();
@@ -362,7 +383,7 @@
    .then(() => {
      tableLoading.value = true;
      delProduct(ids)
        .then((res) => {
          .then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getProductTreeList();
        })
@@ -374,6 +395,7 @@
      proxy.$modal.msg("已取消");
    });
};
  const fatherName = ref("");
// 选择产品
const handleNodeClick = (val, node, el) => {
  // 判断是否为叶子节点
@@ -381,16 +403,19 @@
  // 只有叶子节点才执行以下逻辑
  currentId.value = val.id;
  currentParentId.value = val.parentId;
    fatherName.value = val.label;
  getModelList();
};
// 提交规格型号修改
const submitModelForm = () => {
  proxy.$refs.modelFormRef.validate((valid) => {
    proxy.$refs.modelFormRef.validate(valid => {
    if (valid) {
      modelForm.value.productId = currentId.value;
      modelForm.value.thickness = formatThicknessTo15(modelForm.value.thickness);
      addOrEditProductModel(modelForm.value).then((res) => {
        modelForm.value.thickness = formatThicknessTo15(
          modelForm.value.thickness
        );
        addOrEditProductModel(modelForm.value).then(res => {
        proxy.$modal.msgSuccess("提交成功");
        closeModelDia();
        getModelList();
@@ -404,12 +429,12 @@
  modelDia.value = false;
};
// 表格选择数据
const handleSelectionChange = (selection) => {
  const handleSelectionChange = selection => {
  selectedRows.value = selection;
};
// 查询规格型号
const pagination = (obj) => {
  const pagination = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
@@ -420,7 +445,7 @@
    id: currentId.value,
    current: page.current,
    size: page.size,
  }).then((res) => {
    }).then(res => {
    console.log("res", res);
    tableData.value = res.records;
    page.total = res.total;
@@ -431,7 +456,7 @@
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
      ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
@@ -444,7 +469,7 @@
    .then(() => {
      tableLoading.value = true;
      delProductModel(ids)
        .then((res) => {
          .then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getModelList();
        })
@@ -495,6 +520,57 @@
  // 没匹配到返回false
  return false;
};
  const qrCodeName = ref("");
  // 生成二维码
  const generateQrcode = async row => {
    try {
      currentProductId.value = row.id;
      qrCodeName.value = fatherName.value + "-" + row.model;
      // 使用row.id生成二维码
      const qrCodeData = row.id.toString();
      // 生成二维码URL
      qrCodeUrl.value = await QRCode.toDataURL(qrCodeData, {
        width: 300,
        margin: 1,
      });
      // 打开二维码对话框
      qrCodeDialog.value = true;
    } catch (error) {
      console.error("生成二维码失败:", error);
      proxy.$modal.msgError("生成二维码失败");
    }
  };
  // 保存二维码为图片
  const saveQrCodeAsImage = () => {
    if (!qrCodeUrl.value) return;
    try {
      // 从Data URL创建Blob
      const blob = dataURLToBlob(qrCodeUrl.value);
      // 使用file-saver保存图片
      saveAs(blob, `${qrCodeName.value}.png`);
      proxy.$modal.msgSuccess("保存成功");
    } catch (error) {
      console.error("保存图片失败:", error);
      proxy.$modal.msgError("保存图片失败");
    }
  };
  // 将Data URL转换为Blob
  const dataURLToBlob = dataURL => {
    const arr = dataURL.split(",");
    const mime = arr[0].match(/:(.*?);/)[1];
    const bstr = atob(arr[1]);
    let n = bstr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], { type: mime });
  };
getProductTreeList();
</script>
@@ -563,4 +639,23 @@
.product-tree-scroll::-webkit-scrollbar-thumb:hover {
  background: #909399;
}
  /* 二维码样式 */
  .qrcode-container {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px;
    min-height: 300px;
  }
  .qrcode-image {
    max-width: 100%;
    height: auto;
  }
  .loading {
    font-size: 16px;
    color: #606266;
  }
</style>