张诺
2 天以前 195770f92f7d739ffba6447fdbf3a3d5b9e009fa
src/views/equipmentManagement/ledger/index.vue
@@ -1,31 +1,61 @@
<template>
  <div class="app-container">
    <!-- <el-form :model="filters" :inline="true">
      <el-form-item label="搜索">
    <el-form :model="filters" :inline="true">
      <el-form-item label="设备名称">
        <el-input
          v-model="filters.searchText"
          v-model="filters.deviceName"
          style="width: 240px"
          placeholder="请输入"
          placeholder="请输入设备名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="规格型号">
        <el-input
            v-model="filters.deviceModel"
            style="width: 240px"
            placeholder="请输入规格型号"
            clearable
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="供应商">
        <el-input
            v-model="filters.supplierName"
            style="width: 240px"
            placeholder="请输入供应商"
            clearable
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="录入日期:">
        <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                        placeholder="请选择" clearable @change="changeDaterange" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form> -->
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> 新增 </el-button>
          <el-button @click="handleOut" icon="download">导出</el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="deleteRow(multipleList.map((item) => item.id))"
          >
            批量删除
          </el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
@@ -33,112 +63,136 @@
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button type="primary" text @click="edit(row.id)" icon="editPen">
            编辑
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            @click="deleteRow(row.id)"
          >
            删除
          </el-button>
        </template>
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <el-dialog v-model="qrDialogVisible" title="二维码" width="300px" draggable>
      <div class="qr-dialog-content">
        <img :src="qrCodeUrl" alt="二维码" style="width:220px;height:auto;" />
        <div style="margin:10px 0;">
          <el-button type="primary" @click="downloadQRCode">下载二维码图片</el-button>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { Search } from "@element-plus/icons-vue";
// import { Search } from "@element-plus/icons-vue";
import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger";
import { onMounted, getCurrentInstance } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import QRCode from "qrcode";
import { ref } from "vue";
defineOptions({
  name: "设备台账",
});
// 表格多选框选中项
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
const { filters, columns, dataList, pagination, getTableData, resetFilters } =
  usePaginationApi(
    getLedgerPage,
const qrDialogVisible = ref(false);
const qrCodeUrl = ref("");
const qrRowData = ref(null);
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  getLedgerPage,
  {
    deviceName: undefined,
    deviceModel: undefined,
    supplierName: undefined,
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
  [
    {
      searchText: undefined,
      label: "设备名称",
      prop: "deviceName",
    },
    [
      {
        label: "设备名称",
        align: "center",
        prop: "deviceName",
    {
      label: "规格型号",
      prop: "deviceModel",
    },
    {
      label: "设备品牌",
      prop: "deviceBrand",
    },
    {
      label: "设备类型",
      prop: "type",
    },
    {
      label: "供应商",
      prop: "supplierName",
    },
    {
      label: "存放位置",
      prop: "storageLocation",
    },
    {
      label: "数量",
      prop: "number",
    },
    {
      label: "录入人",
      prop: "createUser",
    },
    {
      label: "录入日期",
      prop: "createTime",
      formatData: (v) => {
        if (!v) return '';
        // 如果包含时分秒,只取日期部分
        if (v.includes(' ')) {
          return v.split(' ')[0];
        }
        return v;
      },
      {
        label: "规格型号",
        align: "center",
        prop: "deviceModel",
      },
      {
        label: "供应商",
        align: "center",
        prop: "supplierName",
      },
      {
        label: "单位",
        align: "center",
        prop: "unit",
      },
      {
        label: "数量",
        align: "center",
        prop: "number",
      },
      {
        label: "含税单价",
        align: "center",
        prop: "taxIncludingPriceUnit",
      },
      {
        label: "含税总价",
        align: "center",
        prop: "taxIncludingPriceTotal",
      },
      {
        label: "税率",
        align: "center",
        prop: "taxRate",
      },
      {
        label: "不含税总价",
        align: "center",
        prop: "unTaxIncludingPriceTotal",
      },
      {
        label: "录入人",
        align: "center",
        prop: "createUser",
      },
      {
        label: "录入日期",
        align: "center",
        prop: "createTime",
      },
      {
        fixed: "right",
        label: "操作",
        dataType: "slot",
        slot: "operation",
        align: "center",
        width: "200px",
      },
    ]
  );
    },
      {
         dataType: "action",
         label: "操作",
         align: "center",
         fixed: 'right',
         width: 150,
         operation: [
            {
               name: "编辑",
               clickFun: (row) => {
                  edit(row.id)
               },
            },
            {
               name: "生成二维码",
               clickFun: (row) => {
                  showQRCode(row)
               },
            },
         ],
      },
  ]
);
// 多选后做什么
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
const add = () => {
  modalRef.value.openModal();
@@ -146,7 +200,11 @@
const edit = (id) => {
  modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
   pagination.pageSize = limit;
  onCurrentChange(page);
};
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该文件, 是否继续?", "提示", {
    confirmButtonText: "确定",
@@ -164,6 +222,17 @@
  });
};
const changeDaterange = (value) => {
  if (value) {
    filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    filters.entryDateStart = undefined;
    filters.entryDateEnd = undefined;
  }
  getTableData();
};
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
@@ -176,6 +245,94 @@
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
const loadImage = (src) =>
  new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = (err) => reject(err);
    img.src = src;
  });
const wrapTextLines = (ctx, text, maxWidth) => {
  const safeText = String(text || "").trim();
  if (!safeText) return [];
  const lines = [];
  let currentLine = "";
  for (const char of safeText) {
    const nextLine = currentLine + char;
    if (ctx.measureText(nextLine).width > maxWidth && currentLine) {
      lines.push(currentLine);
      currentLine = char;
    } else {
      currentLine = nextLine;
    }
  }
  if (currentLine) {
    lines.push(currentLine);
  }
  return lines;
};
const buildQrImageWithTitle = async (qrContent, deviceName) => {
  const baseQrDataUrl = await QRCode.toDataURL(qrContent, {
    width: 220,
    margin: 2,
  });
  const qrImage = await loadImage(baseQrDataUrl);
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  if (!ctx) return baseQrDataUrl;
  const titleText = deviceName || "未命名设备";
  const fontSize = 30;
  const sidePadding = 16;
  const topPadding = 14;
  const titleBottomGap = 10;
  const bottomPadding = 14;
  ctx.font = `${fontSize}px sans-serif`;
  const textMaxWidth = qrImage.width + 40;
  const titleLines = wrapTextLines(ctx, titleText, textMaxWidth);
  const lineHeight = 22;
  const titleHeight = titleLines.length * lineHeight;
  canvas.width = Math.max(qrImage.width + sidePadding * 2, textMaxWidth + sidePadding * 2);
  canvas.height = topPadding + titleHeight + titleBottomGap + qrImage.height + bottomPadding;
  ctx.fillStyle = "#ffffff";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.font = `${fontSize}px sans-serif`;
  ctx.fillStyle = "#303133";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  titleLines.forEach((line, index) => {
    const y = topPadding + lineHeight * index + lineHeight / 2;
    ctx.fillText(line, canvas.width / 2, y);
  });
  const qrX = (canvas.width - qrImage.width) / 2;
  const qrY = topPadding + titleHeight + titleBottomGap;
  ctx.drawImage(qrImage, qrX, qrY, qrImage.width, qrImage.height);
  return canvas.toDataURL("image/png");
};
const showQRCode = async (row) => {
  // 直接使用URL,不要用JSON.stringify包装
  const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
  qrCodeUrl.value = await buildQrImageWithTitle(qrContent, row.deviceName);
  qrRowData.value = row;
  qrDialogVisible.value = true;
};
const downloadQRCode = () => {
  const a = document.createElement("a");
  a.href = qrCodeUrl.value;
  a.download = `${qrRowData.value.deviceName || "二维码"}.png`;
  a.click();
};
onMounted(() => {
@@ -192,4 +349,16 @@
  justify-content: space-between;
  margin-bottom: 10px;
}
.qr-dialog-content {
  text-align: center;
}
.qr-device-name {
  margin-bottom: 8px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  word-break: break-all;
}
</style>