From 52459bcaba196c47a0f28079729b48ce012eaef1 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期六, 18 四月 2026 13:35:47 +0800
Subject: [PATCH] feat: 添加销售订单二维码功能及相关样式,更新操作列宽度
---
src/views/salesManagement/salesLedger/index.vue | 352 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 321 insertions(+), 31 deletions(-)
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 9455101..61251e2 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -63,8 +63,10 @@
style="width: 140px">
<el-option label="鏈叆搴�"
:value="0" />
- <el-option label="宸插叆搴�"
+ <el-option label="閮ㄥ垎鍏ュ簱"
:value="1" />
+ <el-option label="宸插叆搴�"
+ :value="2" />
</el-select>
</el-form-item>
<el-form-item>
@@ -214,6 +216,20 @@
type="danger">涓嶈冻</el-tag>
</template>
</el-table-column>
+ <el-table-column label="鍏ュ簱鐘舵��"
+ width="100px"
+ 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>
+ <el-tag v-else
+ type="danger">涓嶈冻</el-tag>
+ </template>
+ </el-table-column>
<!-- <el-table-column label="鍙戣揣鐘舵��" width="140" align="center">
<template #default="scope">
<el-tag :type="getShippingStatusType(scope.row)" size="small">
@@ -328,6 +344,8 @@
<el-tag v-if="Number(scope.row.stockStatus) === 0"
type="info">鏈叆搴�</el-tag>
<el-tag v-else-if="Number(scope.row.stockStatus) === 1"
+ type="success">閮ㄥ垎鍏ュ簱</el-tag>
+ <el-tag v-else-if="Number(scope.row.stockStatus) === 2"
type="success">宸插叆搴�</el-tag>
<el-tag v-else
type="info">-</el-tag>
@@ -359,7 +377,7 @@
show-overflow-tooltip />
<el-table-column fixed="right"
label="鎿嶄綔"
- width="200"
+ width="280"
align="center">
<template #default="scope">
<el-button link
@@ -373,6 +391,9 @@
<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>
@@ -445,9 +466,7 @@
:key="item.id"
:label="item.customerName"
:value="item.id">
- {{
- item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
- }}
+ {{ item.customerName + (item.taxpayerIdentificationNumber ? "鈥斺��" + item.taxpayerIdentificationNumber : "") }}
</el-option>
</el-select>
</el-form-item>
@@ -569,7 +588,7 @@
</el-table-column>
<el-table-column label="瑙勬牸鍨嬪彿"
prop="specificationModel"
- min-width="160">
+ min-width="200">
<template #default="scope">
<el-select v-if="scope.row.__editing"
v-model="scope.row.productModelId"
@@ -591,10 +610,11 @@
min-width="160">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
+ controls-position="right"
v-model="scope.row.thickness"
:min="0"
- :step="0.000000000000001"
- :precision="15"
+ :step="1"
+ :precision="2"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable />
@@ -606,6 +626,7 @@
min-width="160">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
+ controls-position="right"
v-model="scope.row.width"
:min="0"
:step="1"
@@ -623,6 +644,7 @@
min-width="160">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
+ controls-position="right"
v-model="scope.row.height"
:min="0"
:step="1"
@@ -637,13 +659,14 @@
</el-table-column>
<el-table-column label="缁撶畻鍗曠墖闈㈢Н(銕�)"
prop="settlePieceArea"
- min-width="160">
+ min-width="200">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
+ controls-position="right"
v-model="scope.row.settlePieceArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="1"
+ :precision="10"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable
@@ -656,10 +679,11 @@
min-width="150">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
+ controls-position="right"
v-model="scope.row.quantity"
- :step="0.1"
+ :step="1"
:min="0"
- :precision="2"
+ :precision="0"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable
@@ -670,13 +694,14 @@
</el-table-column>
<el-table-column label="闈㈢Н(m虏)"
prop="actualTotalArea"
- min-width="160">
+ min-width="200">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
+ controls-position="right"
v-model="scope.row.actualTotalArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="1"
+ :precision="10"
style="width: 100%"
placeholder="鑷姩璁$畻" />
<span v-else>{{ scope.row.actualTotalArea ?? "" }}</span>
@@ -684,7 +709,7 @@
</el-table-column>
<el-table-column label="鍚◣鍗曚环(鍏�)"
prop="taxInclusiveUnitPrice"
- min-width="140">
+ min-width="160">
<template #default="scope">
<el-input-number v-if="scope.row.__editing"
:step="0.01"
@@ -776,7 +801,7 @@
</el-table-column>
<el-table-column label="妤煎眰缂栧彿"
prop="floorCode"
- min-width="140"
+ min-width="250"
show-overflow-tooltip>
<template #default="scope">
<el-input v-if="scope.row.__editing"
@@ -1509,9 +1534,10 @@
<FormDialog v-model="importUpload.open"
:title="importUpload.title"
:width="'600px'"
- @close="importUpload.open = false"
+ :loading="importUpload.isUploading"
+ @close="onClose"
@confirm="submitImportFile"
- @cancel="importUpload.open = false">
+ @cancel="onClose">
<el-upload ref="importUploadRef"
:limit="1"
accept=".xlsx,.xls"
@@ -1522,6 +1548,7 @@
:on-error="importUpload.onError"
:on-progress="importUpload.onProgress"
:on-change="importUpload.onChange"
+ :on-exceed="importUpload.onExceed"
:auto-upload="false"
drag>
<i class="el-icon-upload"></i>
@@ -1613,6 +1640,69 @@
</div>
</template>
</el-dialog>
+ <!-- 鍏ュ簱浜у搧閫夋嫨寮圭獥 -->
+ <el-dialog v-model="stockDialogVisible"
+ title="閫夋嫨鍏ュ簱浜у搧"
+ width="60%"
+ :close-on-click-modal="false">
+ <el-table :data="stockProductList"
+ border
+ stripe
+ v-loading="stockLoading"
+ height="400px"
+ @selection-change="val => selectedStockProductIds = val.map(item => item.id)">
+ <el-table-column type="selection"
+ width="55"
+ align="center" />
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column prop="productCategory"
+ label="浜у搧澶х被"
+ show-overflow-tooltip />
+ <el-table-column prop="specificationModel"
+ label="瑙勬牸鍨嬪彿"
+ show-overflow-tooltip />
+ <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-table>
+ <template #footer>
+ <el-button @click="stockDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="submitStock"
+ :disabled="selectedStockProductIds.length === 0">
+ 纭鍏ュ簱
+ </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>
@@ -1660,6 +1750,7 @@
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();
@@ -1688,6 +1779,116 @@
const processFlowSelectBoundRouteId = ref(null);
const processFlowSelectBoundRouteName = ref("");
+ // 鍏ュ簱寮圭獥鐩稿叧
+ const stockDialogVisible = ref(false);
+ const stockProductList = ref([]);
+ 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("");
const dialogFormVisible = ref(false);
@@ -1700,7 +1901,7 @@
entryDateStart: undefined,
entryDateEnd: undefined,
deliveryStatus: undefined, // 鍙戣揣鐘舵�侊細1鏈彂璐� 2瀹℃壒涓� 3瀹℃壒澶辫触 4宸插彂璐�
- stockStatus: undefined, // 鍏ュ簱鐘舵�侊細0鏈叆搴� 1宸插叆搴�
+ stockStatus: undefined, // 鍏ュ簱鐘舵�侊細0鏈叆搴� 1閮ㄥ垎鍏ュ簱 2宸插叆搴�
},
form: {
salesContractNo: "",
@@ -2513,6 +2714,13 @@
onChange: (file, fileList) => {
console.log("鏂囦欢鐘舵�佹敼鍙�", file, fileList);
},
+ onExceed: (files, fileList) => {
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ const file = files[0];
+ importUploadRef.value.handleStart(file);
+ }
+ },
onProgress: (event, file, fileList) => {
console.log("涓婁紶涓�...", event.percent);
},
@@ -2614,12 +2822,37 @@
ElMessage.warning("鎵�閫夋暟鎹己灏慽d锛屾棤娉曞叆搴�");
return;
}
- if (Number(row.stockStatus) === 1) {
- ElMessage.info("璇ュ彴璐﹀凡鍏ュ簱锛屾棤闇�閲嶅鎿嶄綔");
+ if (Number(row.stockStatus) === 2) {
+ ElMessage.info("璇ュ彴璐﹀凡鍏ㄩ儴鍏ュ簱锛屾棤闇�閲嶅鎿嶄綔");
return;
}
+
+ currentStockLedgerId.value = id;
+ selectedStockProductIds.value = [];
+ stockProductList.value = [];
+ stockDialogVisible.value = true;
+ stockLoading.value = true;
+
try {
- await ElMessageBox.confirm("纭瀵规墍閫夊彴璐︽墽琛屽叆搴擄紵", "鎻愮ず", {
+ const res = await productList({ salesLedgerId: id, type: 1 });
+ stockProductList.value = [];
+ stockProductList.value =
+ res.data.filter(item => item.productStockStatus == 0) || [];
+ } catch (e) {
+ proxy?.$modal?.msgError?.("鑾峰彇浜у搧鍒楄〃澶辫触");
+ } finally {
+ stockLoading.value = false;
+ }
+ };
+
+ const submitStock = async () => {
+ if (selectedStockProductIds.value.length === 0) {
+ ElMessage.warning("璇烽�夋嫨鑷冲皯涓�涓骇鍝佽繘琛屽叆搴�");
+ return;
+ }
+
+ try {
+ await ElMessageBox.confirm("纭瀵规墍閫変骇鍝佹墽琛屽叆搴擄紵", "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
@@ -2627,10 +2860,15 @@
} catch {
return;
}
+
proxy?.$modal?.loading?.("姝e湪鍏ュ簱锛岃绋嶅��...");
try {
- await salesStock({ id });
+ await salesStock({
+ salesLedgerId: currentStockLedgerId.value,
+ salesLedgerProducts: selectedStockProductIds.value,
+ });
proxy?.$modal?.msgSuccess?.("鍏ュ簱鎴愬姛");
+ stockDialogVisible.value = false;
await getList();
} catch (e) {
proxy?.$modal?.msgError?.("鍏ュ簱澶辫触锛岃绋嶅悗閲嶈瘯");
@@ -3366,6 +3604,12 @@
const downloadTemplate = () => {
proxy.download("/sales/ledger/exportTemplate", {}, "閿�鍞彴璐﹀鍏ユā鏉�.xlsx");
};
+ const onClose = () => {
+ importUpload.open = false;
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ }
+ };
// 鎻愪氦瀵煎叆鏂囦欢
const submitImportFile = () => {
@@ -3532,7 +3776,28 @@
} else {
const res = await getProcessCard(selectedId);
const processCardData = res?.data ?? {};
- printFinishedProcessCard(processCardData);
+ 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;
+ }
+ printFinishedProcessCard(processCardData);
+ } else {
+ printFinishedProcessCard(processCardData);
+ }
}
} catch (error) {
console.error(
@@ -3955,12 +4220,21 @@
}
// 鍙厑璁搞�愭湭鍙戣揣/瀹℃壒澶辫触銆戣繘鍏ュ彂璐ф祦绋�
- const canDeliveryLedgers = selectedRows.value.filter(r => {
- const status = Number(r.deliveryStatus);
- return status === 1 || status === 3;
+ const statusItem = selectedRows.value[0].deliveryStatus;
+ const isTrue = true;
+ selectedRows.value.forEach(row => {
+ if (row.deliveryStatus != 1 && row.deliveryStatus != 3) {
+ proxy.$modal.msgWarning("浠呮湭鍙戣揣鎴栧鎵瑰け璐ョ殑鍙拌处鍙互鍙戣揣");
+ isTrue = false;
+ return;
+ }
+ if (row.deliveryStatus !== statusItem) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鐩稿悓鐘舵�佺殑閿�鍞彴璐�");
+ isTrue = false;
+ return;
+ }
});
- if (canDeliveryLedgers.length === 0) {
- proxy.$modal.msgWarning("浠呮湭鍙戣揣鎴栧鎵瑰け璐ョ殑鍙拌处鍙互鍙戣揣");
+ if (!isTrue) {
return;
}
@@ -4235,4 +4509,20 @@
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>
--
Gitblit v1.9.3