| | |
| | | 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, |
| | | salesContractNo: (row.salesContractNo ?? "").trim(), |
| | | type: "XS", |
| | | }); |
| | | 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(""); |
| | |
| | | const res = await productList({ salesLedgerId: id, type: 1 }); |
| | | stockProductList.value = []; |
| | | stockProductList.value = |
| | | res.data.filter(item => item.productStockStatus == 0) || []; |
| | | res.data.filter(item => item.productStockStatus == 0 || item.productStockStatus == 1) || []; |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("获取产品列表失败"); |
| | | } finally { |
| | |
| | | } else { |
| | | const res = await getProcessCard(selectedId); |
| | | const processCardData = res?.data ?? {}; |
| | | printFinishedProcessCard(processCardData); |
| | | // 补齐二维码所需的台账标识(后端数据有时不带 id) |
| | | if (processCardData && typeof processCardData === "object") { |
| | | processCardData.salesLedgerId = processCardData.salesLedgerId ?? selectedId; |
| | | processCardData.salesContractNo = |
| | | (processCardData.salesContractNo ?? "").trim() || |
| | | String(selectedRow?.salesContractNo ?? "").trim(); |
| | | } |
| | | const routeNodes = processCardData?.routeNodes; |
| | | const isProcessRouteEmpty = |
| | | !Array.isArray(routeNodes) || routeNodes.length === 0; |
| | | if (isProcessRouteEmpty) { |
| | | proxy.$modal.closeLoading(); |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | "当前订单未绑定工艺路线也没有设置默认的工艺路线,是否仍要打印?", |
| | | "提示", |
| | | { |
| | | confirmButtonText: "打印", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | } |
| | | ); |
| | | } catch { |
| | | return; |
| | | } |
| | | await printFinishedProcessCard(processCardData); |
| | | } else { |
| | | await printFinishedProcessCard(processCardData); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error( |
| | |
| | | return statusStr === "待发货" || statusStr === "审核拒绝"; |
| | | }; |
| | | |
| | | const getLedgerDisplayName = ledger => |
| | | String(ledger?.salesContractNo || "").trim() || |
| | | String(ledger?.projectName || "").trim() || |
| | | `ID:${ledger?.id ?? "-"}`; |
| | | |
| | | const validateLedgersStockedBeforeDelivery = async ledgers => { |
| | | const invalidLedgers = []; |
| | | for (const ledger of ledgers || []) { |
| | | const ledgerId = ledger?.id; |
| | | const ledgerName = getLedgerDisplayName(ledger); |
| | | if (!ledgerId) { |
| | | invalidLedgers.push(`${ledgerName}(缺少台账ID)`); |
| | | continue; |
| | | } |
| | | let products = []; |
| | | try { |
| | | const res = await productList({ salesLedgerId: ledgerId, type: 1 }); |
| | | products = Array.isArray(res?.data) ? res.data : []; |
| | | } catch (e) { |
| | | invalidLedgers.push(`${ledgerName}(明细加载失败)`); |
| | | continue; |
| | | } |
| | | const unstockedProducts = products.filter( |
| | | item => Number(item?.productStockStatus) !== 2 |
| | | ); |
| | | if (unstockedProducts.length > 0) { |
| | | invalidLedgers.push( |
| | | `${ledgerName}(未全部入库${unstockedProducts.length}条)` |
| | | ); |
| | | } |
| | | } |
| | | return invalidLedgers; |
| | | }; |
| | | |
| | | const handleBulkDelivery = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | |
| | | |
| | | // 只允许【未发货/审批失败】进入发货流程 |
| | | const statusItem = selectedRows.value[0].deliveryStatus; |
| | | const isTrue = true; |
| | | let isTrue = true; |
| | | selectedRows.value.forEach(row => { |
| | | if (row.deliveryStatus != 1 && row.deliveryStatus != 3) { |
| | | proxy.$modal.msgWarning("仅未发货或审批失败的台账可以发货"); |
| | |
| | | } |
| | | }); |
| | | if (!isTrue) { |
| | | return; |
| | | } |
| | | |
| | | proxy.$modal.loading("正在校验明细入库状态,请稍候..."); |
| | | const invalidLedgers = await validateLedgersStockedBeforeDelivery( |
| | | selectedRows.value |
| | | ); |
| | | proxy.$modal.closeLoading(); |
| | | if (invalidLedgers.length > 0) { |
| | | try { |
| | | await ElMessageBox.alert( |
| | | `以下销售台账存在未全部入库的明细,暂不可发货:\n${invalidLedgers.join( |
| | | "\n" |
| | | )}`, |
| | | "提示", |
| | | { |
| | | type: "warning", |
| | | confirmButtonText: "知道了", |
| | | } |
| | | ); |
| | | } catch { |
| | | /* 关闭弹窗 */ |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | }; |
| | | |
| | | // 打开发货弹框(单条) |
| | | const openDeliveryForm = row => { |
| | | const openDeliveryForm = async row => { |
| | | // 只允许【未发货/审批失败】发货;已发货/审批中不允许 |
| | | const status = Number(row.deliveryStatus); |
| | | if (status !== 1 && status !== 3) { |
| | | proxy.$modal.msgWarning("只有发货状态为未发货或审批失败的记录才可以发货"); |
| | | return; |
| | | } |
| | | |
| | | proxy.$modal.loading("正在校验明细入库状态,请稍候..."); |
| | | const invalidLedgers = await validateLedgersStockedBeforeDelivery([row]); |
| | | proxy.$modal.closeLoading(); |
| | | if (invalidLedgers.length > 0) { |
| | | try { |
| | | await ElMessageBox.alert( |
| | | `当前销售台账存在未全部入库的明细,暂不可发货:\n${invalidLedgers[0]}`, |
| | | "提示", |
| | | { |
| | | type: "warning", |
| | | confirmButtonText: "知道了", |
| | | } |
| | | ); |
| | | } catch { |
| | | /* 关闭弹窗 */ |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | 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> |