| | |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="入库状态:"> |
| | | <el-select v-model="searchForm.stockStatus" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 140px" |
| | | @change="handleQuery"> |
| | | <el-option label="未入库" |
| | | :value="0" /> |
| | | <el-option label="部分入库" |
| | | :value="1" /> |
| | | <el-option label="已入库" |
| | | :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="录入日期:"> |
| | | <el-date-picker v-model="searchForm.entryDate" |
| | | value-format="YYYY-MM-DD" |
| | |
| | | prop="availableQuality" /> |
| | | <el-table-column label="退货数量" |
| | | prop="returnQuality" /> |
| | | <el-table-column label="入库状态" |
| | | width="100px" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getProductStockStatusType(scope.row.productStockStatus)" |
| | | size="small"> |
| | | {{ stockStatusText[scope.row.productStockStatus] || '未入库' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" /> |
| | | <el-table-column label="含税单价(元)" |
| | |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="入库状态" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStockStatusType(scope.row.stockStatus)" |
| | | size="small"> |
| | | {{ stockStatusText[scope.row.stockStatus] || '未入库' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="录入人" |
| | | prop="recorderName" |
| | | width="120" |
| | |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | width="120" |
| | | width="200" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | |
| | | <el-button link |
| | | type="primary" |
| | | @click="downLoadFile(scope.row)">附件</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openProcurementQrDialog(scope.row)">二维码</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <FileListDialog ref="fileListRef" |
| | | v-model="fileListDialogVisible" |
| | | title="附件列表" /> |
| | | <el-dialog v-model="procurementQrDialogVisible" |
| | | title="采购台账二维码" |
| | | width="360px" |
| | | draggable |
| | | :close-on-click-modal="false"> |
| | | <div class="procurement-qr-dialog"> |
| | | <img v-if="procurementQrCompositeUrl" |
| | | :src="procurementQrCompositeUrl" |
| | | alt="采购台账二维码" |
| | | class="procurement-qr-composite-img" /> |
| | | <el-button type="primary" |
| | | class="procurement-qr-save-btn" |
| | | :disabled="!procurementQrCompositeUrl" |
| | | @click="downloadProcurementQrCode"> |
| | | 保存图片 |
| | | </el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | delPurchaseTemplate, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import QRCode from "qrcode"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | |
| | | 4: "danger", // 审批失败 - 红色 |
| | | }; |
| | | return typeMap[status] || ""; |
| | | }; |
| | | |
| | | // 入库状态显示文本 |
| | | const stockStatusText = { |
| | | 0: "未入库", |
| | | 1: "部分入库", |
| | | 2: "已入库", |
| | | }; |
| | | |
| | | // 获取主表入库状态标签类型 |
| | | const getStockStatusType = status => { |
| | | const typeMap = { |
| | | 0: "info", |
| | | 1: "success", |
| | | 2: "success", |
| | | }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | // 获取产品入库状态标签类型 |
| | | const getProductStockStatusType = status => { |
| | | const typeMap = { |
| | | 0: "info", |
| | | 1: "warning", |
| | | 2: "success", |
| | | }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | const templateName = ref(""); |
| | |
| | | purchaseContractNumber: "", // 采购合同编号 |
| | | salesContractNo: "", // 销售合同编号 |
| | | projectName: "", // 项目名称 |
| | | stockStatus: undefined, // 入库状态 |
| | | entryDate: null, // 录入日期 |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | |
| | | } |
| | | }; |
| | | |
| | | const procurementQrDialogVisible = ref(false); |
| | | const procurementQrCompositeUrl = ref(""); |
| | | const procurementQrDownloadBaseName = ref(""); |
| | | |
| | | const sanitizeProcurementQrFilename = s => |
| | | String(s) |
| | | .replace(/[\\/:*?"<>|]/g, "_") |
| | | .trim() |
| | | .slice(0, 80) || "ledger"; |
| | | |
| | | const wrapProcurementQrTextLines = (ctx, text, maxWidth) => { |
| | | const chars = [...text]; |
| | | const lines = []; |
| | | let line = ""; |
| | | for (const ch of chars) { |
| | | const test = line + ch; |
| | | if (ctx.measureText(test).width > maxWidth && line.length) { |
| | | lines.push(line); |
| | | line = ch; |
| | | } else { |
| | | line = test; |
| | | } |
| | | } |
| | | if (line) lines.push(line); |
| | | return lines; |
| | | }; |
| | | |
| | | const buildProcurementQrCompositeDataUrl = row => |
| | | new Promise((resolve, reject) => { |
| | | const payload = JSON.stringify({ |
| | | id: row.id, |
| | | purchaseContractNumber: (row.purchaseContractNumber ?? "").trim(), |
| | | type: "CG", |
| | | }); |
| | | QRCode.toDataURL(payload, { width: 220, margin: 2 }) |
| | | .then(qrDataUrl => { |
| | | const contract = (row.purchaseContractNumber ?? "").trim() || "—"; |
| | | const img = new Image(); |
| | | img.onload = () => { |
| | | const QR_SIZE = 220; |
| | | const padTop = 16; |
| | | const gapAfterQr = 14; |
| | | const bottomPad = 48; |
| | | const horizontalPad = 20; |
| | | const lineHeight = 20; |
| | | const fontSize = 14; |
| | | const label = `采购合同号:${contract}`; |
| | | |
| | | const canvas = document.createElement("canvas"); |
| | | const ctx = canvas.getContext("2d"); |
| | | canvas.width = Math.max(QR_SIZE + horizontalPad * 2, 280); |
| | | ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`; |
| | | const lines = wrapProcurementQrTextLines( |
| | | ctx, |
| | | label, |
| | | canvas.width - horizontalPad * 2 |
| | | ); |
| | | const textBlockHeight = lines.length * lineHeight; |
| | | canvas.height = padTop + QR_SIZE + gapAfterQr + textBlockHeight + bottomPad; |
| | | |
| | | ctx.fillStyle = "#ffffff"; |
| | | ctx.fillRect(0, 0, canvas.width, canvas.height); |
| | | |
| | | const qrX = (canvas.width - QR_SIZE) / 2; |
| | | ctx.drawImage(img, qrX, padTop, QR_SIZE, QR_SIZE); |
| | | |
| | | ctx.fillStyle = "#606266"; |
| | | ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`; |
| | | ctx.textAlign = "center"; |
| | | ctx.textBaseline = "top"; |
| | | const textY0 = padTop + QR_SIZE + gapAfterQr; |
| | | lines.forEach((ln, i) => { |
| | | ctx.fillText(ln, canvas.width / 2, textY0 + i * lineHeight); |
| | | }); |
| | | |
| | | const baseName = sanitizeProcurementQrFilename( |
| | | contract !== "—" ? contract : String(row.id) |
| | | ); |
| | | resolve({ dataUrl: canvas.toDataURL("image/png"), baseName }); |
| | | }; |
| | | img.onerror = () => reject(new Error("二维码图片加载失败")); |
| | | img.src = qrDataUrl; |
| | | }) |
| | | .catch(reject); |
| | | }); |
| | | |
| | | const openProcurementQrDialog = async row => { |
| | | if (row?.id === undefined || row?.id === null || row?.id === "") { |
| | | ElMessage.warning("无法生成二维码:缺少台账 ID"); |
| | | return; |
| | | } |
| | | procurementQrCompositeUrl.value = ""; |
| | | procurementQrDownloadBaseName.value = ""; |
| | | try { |
| | | const { dataUrl, baseName } = await buildProcurementQrCompositeDataUrl(row); |
| | | procurementQrCompositeUrl.value = dataUrl; |
| | | procurementQrDownloadBaseName.value = baseName; |
| | | procurementQrDialogVisible.value = true; |
| | | } catch { |
| | | ElMessage.error("二维码生成失败"); |
| | | } |
| | | }; |
| | | |
| | | const downloadProcurementQrCode = () => { |
| | | if (!procurementQrCompositeUrl.value) return; |
| | | const a = document.createElement("a"); |
| | | a.href = procurementQrCompositeUrl.value; |
| | | a.download = `采购台账二维码-${procurementQrDownloadBaseName.value}.png`; |
| | | a.click(); |
| | | }; |
| | | |
| | | // 获取模板信息 |
| | | const getTemplateList = async () => { |
| | | let res = await getPurchaseTemplateList(); |
| | |
| | | transform: scale(1.2); |
| | | } |
| | | } |
| | | |
| | | .procurement-qr-dialog { |
| | | text-align: center; |
| | | padding-bottom: 8px; |
| | | } |
| | | |
| | | .procurement-qr-composite-img { |
| | | max-width: 100%; |
| | | height: auto; |
| | | display: block; |
| | | margin: 0 auto 28px; |
| | | } |
| | | |
| | | .procurement-qr-save-btn { |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |