| | |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.productStockStatus == 1" |
| | | type="warning">部分入库</el-tag> |
| | | <el-tag v-else-if="scope.row.productStockStatus == 2" |
| | | type="success">已入库</el-tag> |
| | | <el-tag v-else-if="scope.row.productStockStatus == 0" |
| | | type="info">未出库</el-tag> |
| | |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | width="200" |
| | | width="280" |
| | | 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="openLedgerQrDialog(scope.row)">二维码</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <el-table-column prop="quantity" |
| | | label="数量" |
| | | width="100" /> |
| | | <el-table-column prop="stockedQuantity" |
| | | label="已入库数量" |
| | | width="120" |
| | | align="center" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="floorCode" |
| | | label="楼层编号" |
| | | show-overflow-tooltip /> |
| | |
| | | 确认入库 |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="ledgerQrDialogVisible" |
| | | title="销售订单二维码" |
| | | width="360px" |
| | | draggable |
| | | :close-on-click-modal="false"> |
| | | <div class="ledger-qr-dialog"> |
| | | <img v-if="ledgerQrCompositeUrl" |
| | | :src="ledgerQrCompositeUrl" |
| | | alt="销售订单二维码" |
| | | class="ledger-qr-composite-img" /> |
| | | <el-button type="primary" |
| | | class="ledger-qr-save-btn" |
| | | :disabled="!ledgerQrCompositeUrl" |
| | | @click="downloadLedgerQrCode"> |
| | | 保存图片 |
| | | </el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | import { printSalesOrder } from "./components/salesOrderPrint.js"; |
| | | import { printSalesDeliveryNote } from "./components/salesDeliveryPrint.js"; |
| | | import { printSalesLabel } from "./components/salesLabelPrint.js"; |
| | | import QRCode from "qrcode"; |
| | | // import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | |
| | | const selectedStockProductIds = ref([]); |
| | | const stockLoading = ref(false); |
| | | const currentStockLedgerId = ref(null); |
| | | |
| | | const ledgerQrDialogVisible = ref(false); |
| | | const ledgerQrCompositeUrl = ref(""); |
| | | const ledgerQrDownloadBaseName = ref(""); |
| | | |
| | | const sanitizeLedgerQrFilename = s => |
| | | String(s) |
| | | .replace(/[\\/:*?"<>|]/g, "_") |
| | | .trim() |
| | | .slice(0, 80) || "ledger"; |
| | | |
| | | const wrapLedgerQrTextLines = (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 buildLedgerQrCompositeDataUrl = row => |
| | | new Promise((resolve, reject) => { |
| | | const payload = JSON.stringify({ id: row.id }); |
| | | QRCode.toDataURL(payload, { width: 220, margin: 2 }) |
| | | .then(qrDataUrl => { |
| | | const contract = (row.salesContractNo ?? "").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 = wrapLedgerQrTextLines(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 = sanitizeLedgerQrFilename( |
| | | contract !== "—" ? contract : String(row.id) |
| | | ); |
| | | resolve({ dataUrl: canvas.toDataURL("image/png"), baseName }); |
| | | }; |
| | | img.onerror = () => reject(new Error("二维码图片加载失败")); |
| | | img.src = qrDataUrl; |
| | | }) |
| | | .catch(reject); |
| | | }); |
| | | |
| | | const openLedgerQrDialog = async row => { |
| | | if (row?.id === undefined || row?.id === null || row?.id === "") { |
| | | ElMessage.warning("无法生成二维码:缺少台账 ID"); |
| | | return; |
| | | } |
| | | ledgerQrCompositeUrl.value = ""; |
| | | ledgerQrDownloadBaseName.value = ""; |
| | | try { |
| | | const { dataUrl, baseName } = await buildLedgerQrCompositeDataUrl(row); |
| | | ledgerQrCompositeUrl.value = dataUrl; |
| | | ledgerQrDownloadBaseName.value = baseName; |
| | | ledgerQrDialogVisible.value = true; |
| | | } catch { |
| | | ElMessage.error("二维码生成失败"); |
| | | } |
| | | }; |
| | | |
| | | const downloadLedgerQrCode = () => { |
| | | if (!ledgerQrCompositeUrl.value) return; |
| | | const a = document.createElement("a"); |
| | | a.href = ledgerQrCompositeUrl.value; |
| | | a.download = `销售销售订单二维码-${ledgerQrDownloadBaseName.value}.png`; |
| | | a.click(); |
| | | }; |
| | | |
| | | // 用户信息表单弹框数据 |
| | | const operationType = ref(""); |
| | |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .ledger-qr-dialog { |
| | | text-align: center; |
| | | padding-bottom: 8px; |
| | | } |
| | | |
| | | .ledger-qr-composite-img { |
| | | max-width: 100%; |
| | | height: auto; |
| | | display: block; |
| | | margin: 0 auto 28px; |
| | | } |
| | | |
| | | .ledger-qr-save-btn { |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |