From 3682ad63b5bdb47228325dea1efe2bb9069254a5 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期一, 11 五月 2026 15:53:18 +0800
Subject: [PATCH] 合格出库扫销售二维码进行发货

---
 src/pages/inventoryManagement/scanOut/scanOut.fields.ts |    2 
 src/pages/sales/salesAccount/goOut.vue                  |  570 +++++++++++++++++++++++++++++
 src/pages/inventoryManagement/scanOut/index.vue         |  128 ++++++
 src/pages/sales/salesAccount/index.vue                  |  181 ---------
 src/pages/sales/salesAccount/out.vue                    |    5 
 src/utils/salesLedgerShip.js                            |  209 +++++++++++
 6 files changed, 917 insertions(+), 178 deletions(-)

diff --git a/src/pages/inventoryManagement/scanOut/index.vue b/src/pages/inventoryManagement/scanOut/index.vue
index 8359451..69b9d07 100644
--- a/src/pages/inventoryManagement/scanOut/index.vue
+++ b/src/pages/inventoryManagement/scanOut/index.vue
@@ -28,7 +28,7 @@
 
           <text class="module-label">鍚堟牸鍑哄簱</text>
 
-          <text class="module-desc">鎵弿鍚堟牸鍝佽繘琛岄鐢ㄥ嚭搴�</text>
+          <text class="module-desc">鎵弿鍚堟牸鍝佽繘琛岄鐢ㄥ嚭搴擄紝閿�鍞壂鐮佸彂璐�</text>
 
         </view>
 
@@ -225,7 +225,19 @@
 
                   @click="cancelForm">杩斿洖</u-button>
 
-        <u-button class="footer-confirm-btn"
+        <u-button v-if="showSalesShipButton"
+
+                  class="footer-confirm-btn"
+
+                  :loading="submitLoading"
+
+                  :disabled="salesShipButtonDisabled"
+
+                  @click="handleSalesShipFromScan">鍙戣揣</u-button>
+
+        <u-button v-else
+
+                  class="footer-confirm-btn"
 
                   :loading="submitLoading"
 
@@ -247,7 +259,12 @@
 
   import PageHeader from "@/components/PageHeader.vue";
 
-  import { productList as salesProductList } from "@/api/salesManagement/salesLedger";
+  import {
+    productList as salesProductList,
+    getSalesLedgerWithProducts,
+  } from "@/api/salesManagement/salesLedger";
+
+  import { canLedgerShip, executeSalesLedgerShip, getLedgerShippingLabel } from "@/utils/salesLedgerShip";
 
   import modal from "@/plugins/modal";
 
@@ -281,7 +298,24 @@
   /** 浜岀淮鐮佷腑鐨勫彴璐︿富閿� id */
   const scanLedgerId = ref(null);
 
+  /** 鍚堟牸鍑哄簱+閿�鍞爜锛氱敤浜庛�屽彂璐с�嶆寜閽笌鍙拌处绾ф牎楠岋紙涓庨攢鍞彴璐︿竴鑷达級 */
+  const salesLedgerSnapshotForShip = ref(null);
+
   const submitLoading = ref(false);
+
+  const showSalesShipButton = computed(
+    () =>
+      showForm.value &&
+      type.value === QUALITY_TYPE.qualified &&
+      contractKind.value === CONTRACT_KIND.sales
+  );
+
+  const salesShipButtonDisabled = computed(() => {
+    if (!showSalesShipButton.value) return false;
+    const snap = salesLedgerSnapshotForShip.value;
+    if (!snap) return false;
+    return !canLedgerShip(snap);
+  });
 
   const submitConfigByScene = createSubmitConfig(scanLedgerId);
 
@@ -568,6 +602,8 @@
 
       return formatProductStockStatus(item.productStockStatus);
 
+    if (row.key === "ledgerShippingStatus") return getLedgerShippingLabel(item);
+
     if (row.key === "heavyBox") return formatHeavyBox(item.heavyBox);
 
     if (row.key === "remainingQuantity") {
@@ -633,6 +669,8 @@
     contractKind.value = CONTRACT_KIND.sales;
 
     scanLedgerId.value = null;
+
+    salesLedgerSnapshotForShip.value = null;
 
     expandedByIndex.value = {};
 
@@ -724,6 +762,62 @@
       modal.closeLoading();
 
       console.error("鎵爜鍑哄簱鎻愪氦澶辫触", e);
+
+    } finally {
+
+      submitLoading.value = false;
+
+    }
+
+  };
+
+  const handleSalesShipFromScan = async () => {
+
+    if (scanLedgerId.value == null || scanLedgerId.value === "") {
+
+      modal.msgError("缂哄皯璁㈠崟淇℃伅锛岃閲嶆柊鎵爜");
+
+      return;
+
+    }
+
+    if (salesLedgerSnapshotForShip.value && !canLedgerShip(salesLedgerSnapshotForShip.value)) return;
+
+    submitLoading.value = true;
+
+    try {
+
+      modal.loading("鍔犺浇涓�...");
+
+      let ledgerRow = salesLedgerSnapshotForShip.value;
+
+      if (!ledgerRow?.id) {
+
+        ledgerRow = await getSalesLedgerWithProducts({ id: scanLedgerId.value, type: 1 });
+
+      }
+
+      modal.closeLoading();
+
+      if (!ledgerRow?.id) {
+
+        modal.msgError("鑾峰彇鍙拌处澶辫触");
+
+        return;
+
+      }
+
+      const { productData: _pd, ...ledgerForShip } = ledgerRow;
+
+      await executeSalesLedgerShip(ledgerForShip);
+
+    } catch (e) {
+
+      modal.closeLoading();
+
+      console.error("鎵爜鍙戣揣澶辫触", e);
+
+      modal.msgError("鑾峰彇鍙拌处澶辫触");
 
     } finally {
 
@@ -900,9 +994,35 @@
 
         showForm.value = true;
 
+        if (type.value === QUALITY_TYPE.qualified && kind === CONTRACT_KIND.sales) {
+
+          salesLedgerSnapshotForShip.value = null;
+
+          getSalesLedgerWithProducts({ id: scanData.id, type: 1 })
+
+            .then(res => {
+
+              salesLedgerSnapshotForShip.value = res;
+
+            })
+
+            .catch(() => {
+
+              salesLedgerSnapshotForShip.value = null;
+
+            });
+
+        } else {
+
+          salesLedgerSnapshotForShip.value = null;
+
+        }
+
       } else {
 
         scanLedgerId.value = null;
+
+        salesLedgerSnapshotForShip.value = null;
 
         modal.msgError("鏈煡璇㈠埌鏄庣粏鏁版嵁");
 
@@ -914,6 +1034,8 @@
 
       scanLedgerId.value = null;
 
+      salesLedgerSnapshotForShip.value = null;
+
       console.error("澶勭悊鎵爜缁撴灉澶辫触", error);
 
       modal.msgError("鎵爜澶勭悊澶辫触锛岃閲嶈瘯");
diff --git a/src/pages/inventoryManagement/scanOut/scanOut.fields.ts b/src/pages/inventoryManagement/scanOut/scanOut.fields.ts
index 6aabc56..ea46b12 100644
--- a/src/pages/inventoryManagement/scanOut/scanOut.fields.ts
+++ b/src/pages/inventoryManagement/scanOut/scanOut.fields.ts
@@ -28,6 +28,7 @@
   { label: "閲嶇", key: "heavyBox" },
   { label: "浜у搧鐘舵��", key: "approveStatus" },
   { label: "鍏ュ簱鐘舵��", key: "productStockStatus" },
+  { label: "鍙戣揣鐘舵��", key: "ledgerShippingStatus" },
   { label: "蹇�掑叕鍙�", key: "expressCompany" },
   { label: "蹇�掑崟鍙�", key: "expressNumber" },
   { label: "鍙戣揣杞︾墝", key: "shippingCarNumber" },
@@ -59,6 +60,7 @@
   { label: "鏁伴噺", key: "quantity" },
   { label: "浜у搧鐘舵��", key: "approveStatus" },
   { label: "鍏ュ簱鐘舵��", key: "productStockStatus" },
+  { label: "鍙戣揣鐘舵��", key: "ledgerShippingStatus" },
 ] as const;
 
 export const summaryFieldRowsPurchase = [
diff --git a/src/pages/sales/salesAccount/goOut.vue b/src/pages/sales/salesAccount/goOut.vue
index 35e2a3f..05e3f90 100644
--- a/src/pages/sales/salesAccount/goOut.vue
+++ b/src/pages/sales/salesAccount/goOut.vue
@@ -20,7 +20,78 @@
                    @click="showPicker = true"></up-icon>
         </template>
       </u-form-item>
+      <u-form-item v-if="typeValue === '璐ц溅'"
+                   prop="shippingCarNumber"
+                   label="杞︾墝鍙�"
+                   required>
+        <u-input v-model="form.shippingCarNumber"
+                 placeholder="璇疯緭鍏ヨ溅鐗屽彿"
+                 clearable />
+      </u-form-item>
+      <u-form-item v-if="typeValue === '蹇��'"
+                   prop="expressNumber"
+                   label="蹇�掑崟鍙�"
+                   required>
+        <u-input v-model="form.expressNumber"
+                 placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+                 clearable />
+      </u-form-item>
     </u-form>
+    <!-- 鍙戣揣鍥剧墖锛氱浉鍐屾垨鐩告満銆�/file/upload銆侀瑙堜笌鍒楄〃鏍峰紡 -->
+    <view class="ship-images-card">
+      <view class="ship-images-header">
+        <text class="ship-images-title">鍙戣揣鍥剧墖</text>
+        <text class="ship-images-hint">鏈�澶� {{ uploadConfig.limit }} 寮�</text>
+      </view>
+      <view class="simple-upload-area">
+        <view class="upload-buttons">
+          <u-button type="primary"
+                    @click="chooseShipImage"
+                    :loading="shipUploading"
+                    :disabled="shipFiles.length >= uploadConfig.limit"
+                    :customStyle="{ width: '100%' }">
+            <u-icon name="camera"
+                    size="18"
+                    color="#fff"
+                    style="margin-right: 5px;"></u-icon>
+            {{ shipUploading ? '涓婁紶涓�...' : '娣诲姞鍥剧墖' }}
+          </u-button>
+        </view>
+        <view v-if="shipUploading"
+              class="upload-progress">
+          <u-line-progress :percentage="shipUploadProgress"
+                           :showText="true"
+                           activeColor="#409eff"></u-line-progress>
+        </view>
+        <view v-if="shipFiles.length > 0"
+              class="file-list">
+          <view v-for="(file, index) in shipFiles"
+                :key="file.uid || index"
+                class="file-item">
+            <view class="file-preview-container"
+                  @click="previewShipImage(file)">
+              <image :src="getFileAccessUrl(file)"
+                     class="file-preview"
+                     mode="aspectFill" />
+              <view class="delete-btn"
+                    @click.stop="removeShipFile(index)">
+                <u-icon name="close"
+                        size="12"
+                        color="#fff"></u-icon>
+              </view>
+            </view>
+            <view class="file-info">
+              <text class="file-name">{{ file.bucketFilename || file.name || '鍥剧墖' }}</text>
+              <text class="file-size">{{ formatFileSize(file.size) }}</text>
+            </view>
+          </view>
+        </view>
+        <view v-else
+              class="empty-state">
+          <text>璇蜂粠鐩稿唽閫夋嫨鎴栨媿鐓т笂浼犲彂璐у浘鐗�</text>
+        </view>
+      </view>
+    </view>
     <!-- 閫夋嫨鍣ㄥ脊绐� -->
     <up-action-sheet :show="showPicker"
                      :actions="productOptions"
@@ -87,9 +158,11 @@
 </template>
 
 <script setup>
-  import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
+  import { ref, computed, onMounted, onUnmounted, reactive, toRefs } from "vue";
   import PageHeader from "@/components/PageHeader.vue";
   import { addShippingInfo } from "@/api/salesManagement/salesLedger";
+  import config from "@/config";
+  import { getToken } from "@/utils/auth";
   const showToast = message => {
     uni.showToast({
       title: message,
@@ -97,6 +170,8 @@
     });
   };
   import { userListNoPageByTenantId } from "@/api/system/user";
+
+  const typeValue = ref("璐ц溅");
 
   const data = reactive({
     form: {
@@ -114,9 +189,43 @@
       endDate: "",
       location: "",
       price: "",
+      shippingCarNumber: "",
+      expressNumber: "",
     },
     rules: {
       typeValue: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
+      shippingCarNumber: [
+        {
+          validator: (rule, value, callback) => {
+            if (typeValue.value !== "璐ц溅") {
+              callback();
+              return;
+            }
+            if (!value || !String(value).trim()) {
+              callback(new Error("璇疯緭鍏ヨ溅鐗屽彿"));
+              return;
+            }
+            callback();
+          },
+          trigger: ["blur", "change"],
+        },
+      ],
+      expressNumber: [
+        {
+          validator: (rule, value, callback) => {
+            if (typeValue.value !== "蹇��") {
+              callback();
+              return;
+            }
+            if (!value || !String(value).trim()) {
+              callback(new Error("璇疯緭鍏ュ揩閫掑崟鍙�"));
+              return;
+            }
+            callback();
+          },
+          trigger: ["blur", "change"],
+        },
+      ],
     },
   });
   const { form, rules } = toRefs(data);
@@ -138,6 +247,329 @@
   const formRef = ref(null);
   const approveType = ref(0);
   const goOutData = ref({});
+
+  // 涓庤澶囧贰妫� inspectionUpload/index.vue 涓� uploadConfig 涓�鑷�
+  const uploadConfig = {
+    action: "/file/upload",
+    limit: 10,
+    fileSize: 50,
+    fileType: ["jpg", "jpeg", "png", "gif", "webp"],
+  };
+
+  /** 涓庤澶囧贰妫�涓�鑷达細鐢熶骇鍓� type=10 浼犵粰 /file/upload 鐨� formData.type */
+  const shipUploadFormType = 10;
+
+  const uploadFileUrl = computed(() => (config.baseUrl || "").replace(/\/$/, "") + uploadConfig.action);
+
+  const shipFiles = ref([]);
+  const shipUploading = ref(false);
+  const shipUploadProgress = ref(0);
+
+  const isImageFile = file => {
+    if (file?.contentType && String(file.contentType).startsWith("image/")) return true;
+    if (file?.type === "image" || file?.mediaType === "image") return true;
+    const name = file?.bucketFilename || file?.originalFilename || file?.name || "";
+    const ext = name.split(".").pop()?.toLowerCase();
+    return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext);
+  };
+
+  const filePreviewBase = config.fileUrl;
+
+  const normalizeFileUrl = (rawUrl = "") => {
+    let fileUrl = rawUrl || "";
+    if (typeof fileUrl === "string") {
+      fileUrl = fileUrl.trim().replace(/^['"]|['"]$/g, "");
+    }
+    const javaApi = filePreviewBase;
+    const localPrefixes = ["wxfile://", "file://", "content://", "blob:", "data:"];
+
+    if (localPrefixes.some(prefix => fileUrl.startsWith(prefix))) {
+      return fileUrl;
+    }
+
+    if (fileUrl && fileUrl.indexOf("\\") > -1) {
+      const lowerPath = fileUrl.toLowerCase();
+      const uploadPathIndex = lowerPath.indexOf("uploadpath");
+      const prodIndex = lowerPath.indexOf("\\prod\\");
+
+      if (uploadPathIndex > -1) {
+        fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/");
+      } else if (prodIndex > -1) {
+        fileUrl = fileUrl
+          .substring(prodIndex + "\\prod\\".length)
+          .replace(/\\/g, "/");
+      } else {
+        fileUrl = fileUrl.replace(/\\/g, "/");
+      }
+    }
+    const normalizedLower = String(fileUrl).toLowerCase();
+    const fileProdIdx = normalizedLower.indexOf("/file/prod/");
+    if (fileProdIdx > -1) {
+      fileUrl = `/profile/prod/${fileUrl.substring(fileProdIdx + "/file/prod/".length)}`;
+    }
+    const fileTempIdx = normalizedLower.indexOf("/file/temp/");
+    if (fileTempIdx > -1) {
+      fileUrl = `/profile/temp/${fileUrl.substring(fileTempIdx + "/file/temp/".length)}`;
+    }
+
+    if (/^\/?uploadPath/i.test(fileUrl)) {
+      fileUrl = fileUrl.replace(/^\/?uploadPath/i, "/profile");
+    }
+
+    if (fileUrl && !fileUrl.startsWith("http")) {
+      if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
+      fileUrl = javaApi + fileUrl;
+    }
+
+    return fileUrl;
+  };
+
+  const getFileAccessUrl = (file = {}) => {
+    if (file?.link) {
+      if (String(file.link).startsWith("http")) return file.link;
+      return normalizeFileUrl(file.link);
+    }
+    const remoteUrl = normalizeFileUrl(
+      file?.url || file?.downloadUrl || file?.tempPath || ""
+    );
+    if (remoteUrl) return remoteUrl;
+    if (file?._localPreviewUrl) return file._localPreviewUrl;
+    return normalizeFileUrl(file?.tempFilePath || file?.path || "");
+  };
+
+  const formatFileSize = size => {
+    if (!size) return "";
+    if (size < 1024) return size + "B";
+    if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB";
+    return (size / (1024 * 1024)).toFixed(1) + "MB";
+  };
+
+  const handleShipUploadError = (message = "涓婁紶鏂囦欢澶辫触") => {
+    shipUploading.value = false;
+    shipUploadProgress.value = 0;
+    uni.showToast({ title: message, icon: "error" });
+  };
+
+  const handleShipUploadSuccess = (res, file) => {
+    const uploadedFile = res.data;
+    if (!uploadedFile) {
+      handleShipUploadError("涓婁紶鍝嶅簲鏁版嵁鏍煎紡閿欒");
+      return;
+    }
+    const fileData = {
+      ...file,
+      id: uploadedFile.id,
+      tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id,
+      url:
+        uploadedFile.url ||
+        uploadedFile.downloadUrl ||
+        uploadedFile.tempPath ||
+        file.tempFilePath ||
+        file.path,
+      tempPath: uploadedFile.tempPath || file.tempPath || "",
+      _localPreviewUrl: file.tempFilePath || file.path || "",
+      bucketFilename:
+        uploadedFile.bucketFilename ||
+        uploadedFile.originalFilename ||
+        uploadedFile.originalName ||
+        file.name,
+      downloadUrl: uploadedFile.downloadUrl || uploadedFile.url,
+      size: uploadedFile.size || uploadedFile.byteSize || file.size,
+      createTime: uploadedFile.createTime || new Date().getTime(),
+      mediaType: file.type || uploadedFile.mediaType,
+    };
+    shipFiles.value.push(fileData);
+    uni.showToast({ title: "涓婁紶鎴愬姛", icon: "success" });
+  };
+
+  const uploadShipWithUniUploadFile = (file, filePath, token) => {
+    if (!filePath) {
+      handleShipUploadError("鏂囦欢璺緞涓嶅瓨鍦�");
+      return;
+    }
+    shipUploading.value = true;
+    shipUploadProgress.value = 0;
+
+    const uploadTask = uni.uploadFile({
+      url: uploadFileUrl.value,
+      filePath,
+      name: "file",
+      formData: {
+        type: shipUploadFormType,
+      },
+      header: {
+        Authorization: `Bearer ${token}`,
+      },
+      success: res => {
+        try {
+          if (res.statusCode === 200) {
+            const response = JSON.parse(res.data || "{}");
+            if (response.code === 200) {
+              handleShipUploadSuccess(response, file);
+            } else {
+              handleShipUploadError(response.msg || "鏈嶅姟鍣ㄨ繑鍥為敊璇�");
+            }
+          } else {
+            handleShipUploadError(`鏈嶅姟鍣ㄩ敊璇紝鐘舵�佺爜: ${res.statusCode}`);
+          }
+        } catch (e) {
+          console.error("瑙f瀽鍝嶅簲澶辫触:", e);
+          handleShipUploadError("鍝嶅簲鏁版嵁瑙f瀽澶辫触");
+        }
+      },
+      fail: err => {
+        let errorMessage = "涓婁紶澶辫触";
+        if (err.errMsg) {
+          if (err.errMsg.includes("statusCode: null")) {
+            errorMessage = "缃戠粶杩炴帴澶辫触锛岃妫�鏌ョ綉缁滆缃�";
+          } else if (err.errMsg.includes("timeout")) {
+            errorMessage = "涓婁紶瓒呮椂锛岃閲嶈瘯";
+          } else if (err.errMsg.includes("fail")) {
+            errorMessage = "涓婁紶澶辫触锛岃妫�鏌ョ綉缁滆繛鎺�";
+          } else {
+            errorMessage = err.errMsg;
+          }
+        }
+        handleShipUploadError(errorMessage);
+      },
+      complete: () => {
+        shipUploading.value = false;
+        shipUploadProgress.value = 0;
+      },
+    });
+
+    if (uploadTask && uploadTask.onProgressUpdate) {
+      uploadTask.onProgressUpdate(r => {
+        shipUploadProgress.value = r.progress;
+      });
+    }
+  };
+
+  const uploadShipFile = file => {
+    const token = getToken();
+    if (!token) {
+      handleShipUploadError("鐢ㄦ埛鏈櫥褰�");
+      return;
+    }
+    uploadShipWithUniUploadFile(file, file.tempFilePath || file.path || "", token);
+  };
+
+  const handleBeforeShipUpload = async file => {
+    if (uploadConfig.fileType?.length) {
+      const fileName = file.name || "";
+      const fileExtension = fileName ? fileName.split(".").pop().toLowerCase() : "";
+      const expectedTypes = ["jpg", "jpeg", "png", "gif", "webp"];
+      if (fileExtension && expectedTypes.length > 0) {
+        const isAllowed = expectedTypes.some(
+          t => uploadConfig.fileType.includes(t) && t === fileExtension
+        );
+        if (!isAllowed) {
+          uni.showToast({
+            title: `鏂囦欢鏍煎紡涓嶆敮鎸侊紝璇烽�夋嫨 ${expectedTypes.join("/")} 鏍煎紡鐨勫浘鐗嘸,
+            icon: "none",
+          });
+          return false;
+        }
+      }
+    }
+    uploadShipFile(file);
+    return true;
+  };
+
+  const chooseShipImage = () => {
+    if (shipFiles.value.length >= uploadConfig.limit) {
+      uni.showToast({
+        title: `鏈�澶氬彧鑳戒笂浼�${uploadConfig.limit}寮犲浘鐗嘸,
+        icon: "none",
+      });
+      return;
+    }
+
+    const remaining = uploadConfig.limit - shipFiles.value.length;
+
+    if (typeof uni.chooseMedia === "function") {
+      uni.chooseMedia({
+        count: Math.min(remaining, 1),
+        mediaType: ["image"],
+        sizeType: ["compressed", "original"],
+        sourceType: ["album", "camera"],
+        success: res => {
+          try {
+            const files = res?.tempFiles || [];
+            if (!files.length) throw new Error("鏈幏鍙栧埌鏂囦欢");
+            files.forEach((tf, idx) => {
+              const filePath = tf.tempFilePath || tf.path || "";
+              const file = {
+                tempFilePath: filePath,
+                path: filePath,
+                type: "image",
+                name: `image_${Date.now()}_${idx}.jpg`,
+                size: tf.size || 0,
+                createTime: Date.now(),
+                uid: Date.now() + Math.random() + idx,
+              };
+              handleBeforeShipUpload(file);
+            });
+          } catch (e) {
+            console.error("澶勭悊鍥剧墖閫夋嫨缁撴灉澶辫触:", e);
+            uni.showToast({ title: "澶勭悊鏂囦欢澶辫触", icon: "error" });
+          }
+        },
+        fail: () => uni.showToast({ title: "閫夋嫨鍥剧墖澶辫触", icon: "error" }),
+      });
+      return;
+    }
+
+    uni.chooseImage({
+      count: 1,
+      sizeType: ["compressed", "original"],
+      sourceType: ["album", "camera"],
+      success: res => {
+        const tempFilePath = res?.tempFilePaths?.[0];
+        const tempFile = res?.tempFiles?.[0] || {};
+        if (!tempFilePath) return;
+        handleBeforeShipUpload({
+          tempFilePath,
+          path: tempFilePath,
+          type: "image",
+          name: `photo_${Date.now()}.jpg`,
+          size: tempFile.size || 0,
+          createTime: Date.now(),
+          uid: Date.now() + Math.random(),
+        });
+      },
+      fail: () => uni.showToast({ title: "閫夋嫨鍥剧墖澶辫触", icon: "none" }),
+    });
+  };
+
+  const previewShipImage = file => {
+    if (!file || !isImageFile(file)) return;
+    const imageUrls = shipFiles.value
+      .filter(f => isImageFile(f))
+      .map(f => getFileAccessUrl(f))
+      .filter(Boolean);
+    const current = getFileAccessUrl(file);
+    if (!imageUrls.length || !current) return;
+    uni.previewImage({ urls: imageUrls, current });
+  };
+
+  const removeShipFile = index => {
+    uni.showModal({
+      title: "纭鍒犻櫎",
+      content: "纭畾瑕佸垹闄よ繖涓枃浠跺悧锛�",
+      success: res => {
+        if (res.confirm) {
+          shipFiles.value.splice(index, 1);
+          uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+        }
+      },
+    });
+  };
+
+  const getShipTempFileIds = () =>
+    shipFiles.value
+      .map(it => it.tempId ?? it.tempFileId ?? it.id)
+      .filter(v => v !== undefined && v !== null && v !== "");
   onMounted(async () => {
     try {
       userListNoPageByTenantId().then(res => {
@@ -161,11 +593,14 @@
     // 绉婚櫎浜嬩欢鐩戝惉
     uni.$off("selectContact", handleSelectContact);
   });
-  const typeValue = ref("璐ц溅");
   const onConfirm = item => {
-    // 璁剧疆閫変腑鐨勯儴闂�
     typeValue.value = item.name;
     showPicker.value = false;
+    if (item.name === "璐ц溅") {
+      form.value.expressNumber = "";
+    } else {
+      form.value.shippingCarNumber = "";
+    }
   };
 
   const goBack = () => {
@@ -183,6 +618,10 @@
       showToast("璇蜂负姣忎釜瀹℃壒姝ラ閫夋嫨瀹℃壒浜�");
       return;
     }
+    if (shipUploading.value) {
+      showToast("鍥剧墖姝e湪涓婁紶锛岃绋嶅��");
+      return;
+    }
     formRef.value
       .validate()
       .then(valid => {
@@ -198,6 +637,11 @@
             salesLedgerProductId: goOutData.value.id,
             type: typeValue.value,
             approveUserIds,
+            shippingCarNumber:
+              typeValue.value === "璐ц溅" ? String(form.value.shippingCarNumber || "").trim() : "",
+            expressNumber:
+              typeValue.value === "蹇��" ? String(form.value.expressNumber || "").trim() : "",
+            tempFileIds: getShipTempFileIds(),
           };
           console.log(params, "params");
 
@@ -273,6 +717,126 @@
 <style scoped lang="scss">
   @import "@/static/scss/form-common.scss";
 
+  .ship-images-card {
+    background: #fff;
+    margin: 16px;
+    border-radius: 16px;
+    padding: 16px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+  }
+
+  .ship-images-header {
+    margin-bottom: 12px;
+  }
+
+  .ship-images-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+    display: block;
+    margin-bottom: 4px;
+  }
+
+  .ship-images-hint {
+    font-size: 12px;
+    color: #999;
+  }
+
+  /* 浠ヤ笅涓� inspectionUpload/index.vue 绠�鍖栦笂浼犲尯銆佹枃浠跺垪琛ㄣ�佽棰戝脊绐椾竴鑷� */
+  .simple-upload-area {
+    padding: 0;
+  }
+
+  .upload-buttons {
+    display: flex;
+    gap: 10px;
+    margin-bottom: 15px;
+  }
+
+  .upload-progress {
+    margin-bottom: 12px;
+  }
+
+  .file-list {
+    margin-top: 15px;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+
+  .file-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background: #fff;
+    border-radius: 12px;
+    padding: 8px;
+    border: 1px solid #e9ecef;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+    width: calc(50% - 6px);
+    min-width: 120px;
+  }
+
+  .file-preview-container {
+    position: relative;
+    margin-bottom: 8px;
+  }
+
+  .file-preview {
+    width: 80px;
+    height: 80px;
+    border-radius: 8px;
+    object-fit: cover;
+    border: 2px solid #f0f0f0;
+  }
+
+  .delete-btn {
+    position: absolute;
+    top: -6px;
+    right: -6px;
+    width: 20px;
+    height: 20px;
+    background: #ff4757;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 2px 4px rgba(255, 71, 87, 0.3);
+  }
+
+  .file-info {
+    text-align: center;
+    width: 100%;
+  }
+
+  .file-name {
+    font-size: 12px;
+    color: #333;
+    font-weight: 500;
+    display: block;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 100px;
+  }
+
+  .file-size {
+    font-size: 10px;
+    color: #999;
+    margin-top: 2px;
+    display: block;
+  }
+
+  .empty-state {
+    text-align: center;
+    padding: 24px 16px;
+    color: #999;
+    font-size: 14px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    border: 2px dashed #ddd;
+  }
+
   .approval-process {
     background: #fff;
     margin: 16px;
diff --git a/src/pages/sales/salesAccount/index.vue b/src/pages/sales/salesAccount/index.vue
index 3834435..2e1b083 100644
--- a/src/pages/sales/salesAccount/index.vue
+++ b/src/pages/sales/salesAccount/index.vue
@@ -137,11 +137,14 @@
 <script setup>
   import { ref } from "vue";
   import { onShow } from "@dcloudio/uni-app";
+  import { ledgerListPage, delLedger, productList } from "@/api/salesManagement/salesLedger";
   import {
-    ledgerListPage,
-    delLedger,
-    productList,
-  } from "@/api/salesManagement/salesLedger";
+    hasShippedProducts,
+    getLedgerShippingLabel,
+    getLedgerShippingTagType,
+    canLedgerShip,
+    executeSalesLedgerShip,
+  } from "@/utils/salesLedgerShip";
   import useUserStore from "@/store/modules/user";
   import PageHeader from "@/components/PageHeader.vue";
   const userStore = useUserStore();
@@ -161,173 +164,7 @@
   // 閿�鍞彴璐︽暟鎹�
   const ledgerList = ref([]);
 
-  // 鍒ゆ柇鏄惁瀛樺湪宸插彂璐�/鍙戣揣瀹屾垚鐨勪骇鍝�
-  const hasShippedProducts = products => {
-    if (!products || products.length === 0) return false;
-    return products.some(p => {
-      const statusCode = normalizeShippingStatusToCode(p.deliveryStatus ?? p.shippingStatus);
-      const statusStr = (p.shippingStatus ?? "").toString().trim();
-      // 鏈夊彂璐ф棩鏈�/杞︾墝鍙凤紝鎴栫姸鎬佹槑纭负鈥滃凡鍙戣揣/鍙戣揣瀹屾垚鈥濊涓哄凡鍙戣揣
-      return (
-        statusCode === 5 ||
-        statusStr === "宸插彂璐�" ||
-        statusStr === "鍙戣揣瀹屾垚" ||
-        statusStr === "宸插畬鎴愬彂璐�" ||
-        !!p.shippingDate ||
-        !!p.shippingCarNumber
-      );
-    });
-  };
-
-  // 鍙拌处鍙戣揣鐘舵�侊細1-鏈彂璐э紝2-瀹℃壒涓紝3-瀹℃壒涓嶉�氳繃锛�4-瀹℃壒閫氳繃锛�5-宸插彂璐э紙涓庡悗绔灇涓惧榻愶紝鍏煎澶氱瀛楁鍚嶏級
-  const LEDGER_SHIPPING_LABELS = {
-    1: "鏈彂璐�",
-    2: "瀹℃壒涓�",
-    3: "瀹℃壒涓嶉�氳繃",
-    4: "瀹℃壒閫氳繃",
-    5: "宸插彂璐�",
-  };
-
-  const normalizeShippingStatusToCode = v => {
-    if (v === null || v === undefined || v === "") return 1;
-    const n = Number(v);
-    if (!Number.isNaN(n) && n >= 1 && n <= 5) return n;
-    const s = String(v).trim();
-    const textMap = {
-      鏈彂璐�: 1,
-      寰呭彂璐�: 1,
-      瀹℃壒涓�: 2,
-      瀹℃牳涓�: 2,
-      寰呭鏍�: 2,
-      瀹℃壒涓嶉�氳繃: 3,
-      瀹℃牳鎷掔粷: 3,
-      瀹℃壒閫氳繃: 4,
-      瀹℃牳閫氳繃: 4,
-      宸插彂璐�: 5,
-      鍙戣揣瀹屾垚: 5,
-      宸插畬鎴愬彂璐�: 5,
-    };
-    return textMap[s] ?? 1;
-  };
-
-  const getLedgerShippingStatusCode = item => {
-    if (!item) return 1;
-    const raw =
-      item.deliveryStatus ??
-      item.shippingApprovalStatus ??
-      item.shipmentApproveStatus ??
-      item.ledgerShippingStatus;
-    if (raw !== null && raw !== undefined && raw !== "") {
-      return normalizeShippingStatusToCode(raw);
-    }
-    if (item.shippingStatus !== null && item.shippingStatus !== undefined && item.shippingStatus !== "") {
-      return normalizeShippingStatusToCode(item.shippingStatus);
-    }
-    return 1;
-  };
-
-  const getLedgerShippingLabel = item =>
-    LEDGER_SHIPPING_LABELS[getLedgerShippingStatusCode(item)] ?? "鏈彂璐�";
-
-  const getLedgerShippingTagType = item => {
-    const t = { 1: "info", 2: "warning", 3: "error", 4: "primary", 5: "success" };
-    return t[getLedgerShippingStatusCode(item)] ?? "info";
-  };
-
-  const canLedgerShip = item => {
-    const c = getLedgerShippingStatusCode(item);
-    return c === 1 || c === 3;
-  };
-
-  /**
-   * 鍒ゆ柇鏄惁鍙互鍙戣揣
-   * 鍙湁鍦ㄤ骇鍝佺姸鎬佹槸鍏呰冻锛屽彂璐х姸鎬佹槸寰呭彂璐у拰瀹℃牳鎷掔粷鐨勬椂鍊欐墠鍙互鍙戣揣
-   * @param row 琛屾暟鎹�
-   */
-  const canShip = row => {
-    if (!row) return false;
-
-    // 浜у搧鐘舵�佸繀椤绘槸鍏呰冻锛坅pproveStatus === 1锛�
-    if (row.approveStatus !== 1) return false;
-
-    // 濡傛灉宸插彂璐э紙鏈夊彂璐ф棩鏈熸垨杞︾墝鍙凤級锛屼笉鑳藉啀娆″彂璐�
-    if (row.shippingDate || row.shippingCarNumber) return false;
-
-    // 濡傛灉鍚庣杩斿洖浜嗗彂璐х姸鎬侊紙deliveryStatus锛夛紝宸插彂璐у垯绂佹鍐嶆鍙戣揣
-    const deliveryStatus = row.deliveryStatus;
-    if (deliveryStatus !== null && deliveryStatus !== undefined && String(deliveryStatus).trim() !== "") {
-      const code = normalizeShippingStatusToCode(deliveryStatus);
-      if (code === 5) return false;
-    }
-
-    // 鍙戣揣鐘舵�佸繀椤绘槸"寰呭彂璐�"鎴�"瀹℃牳鎷掔粷"
-    const statusStr = row.shippingStatus ? String(row.shippingStatus).trim() : "";
-    return statusStr === "寰呭彂璐�" || statusStr === "瀹℃牳鎷掔粷";
-  };
-
-  const productLabel = row => {
-    if (!row) return "浜у搧";
-    const parts = [row.productCategory, row.floorCode, row.specificationModel].filter(Boolean);
-    return parts.length ? parts.join(" / ") : (row.productName || row.goodsName || "浜у搧");
-  };
-
-  const handleShip = async item => {
-    if (!canLedgerShip(item)) {
-      uni.showToast({
-        title: "浠呮湭鍙戣揣鎴栧鎵逛笉閫氳繃鏃跺彲鍙戣揣",
-        icon: "none",
-      });
-      return;
-    }
-    if (!item?.id) return;
-    showLoadingToast("鍔犺浇涓�...");
-    try {
-      const res = await productList({ salesLedgerId: item.id, type: 1 });
-      const products = res.data || res.records || [];
-      closeToast();
-      if (!products.length) {
-        uni.showToast({
-          title: "娌℃湁浜у搧鏁版嵁",
-          icon: "none",
-        });
-        return;
-      }
-
-      // 鍏堟鏌ユ槸鍚﹀瓨鍦ㄢ�滀笉瓒斥�濈殑浜у搧锛氭湁涓�涓笉瓒冲氨绂佹鍙戣揣骞舵彁绀�
-      const insufficient = products.filter(p => p?.approveStatus !== 1);
-      if (insufficient.length) {
-        const names = insufficient.slice(0, 3).map(productLabel).join("銆�");
-        uni.showToast({
-          title: `瀛樺湪搴撳瓨涓嶈冻浜у搧锛�${names}${insufficient.length > 3 ? "鈥�" : ""}`,
-          icon: "none",
-          duration: 2500,
-        });
-        return;
-      }
-
-      // 鍏ㄩ儴鍏呰冻鍚庯紝鍐嶇瓫閫夊彲鍙戣揣浜у搧锛堜粎寰呭彂璐�/瀹℃牳鎷掔粷锛�
-      const row = products.find(p => canShip(p));
-      if (!row) {
-        uni.showToast({
-          title: "娌℃湁鍙彂璐х殑浜у搧锛堜粎寰呭彂璐�/瀹℃牳鎷掔粷鍙彂璐э級",
-          icon: "none",
-          duration: 2500,
-        });
-        return;
-      }
-
-      uni.setStorageSync("goOutData", JSON.stringify(row));
-      uni.navigateTo({
-        url: "/pages/sales/salesAccount/goOut",
-      });
-    } catch (e) {
-      closeToast();
-      uni.showToast({
-        title: "鍔犺浇浜у搧澶辫触",
-        icon: "none",
-      });
-    }
-  };
+  const handleShip = item => executeSalesLedgerShip(item);
 
   // 杩斿洖涓婁竴椤�
   const goBack = () => {
@@ -374,7 +211,7 @@
 
     if (hasShippedProducts(products)) {
       uni.showToast({
-        title: "宸插彂璐�/鍙戣揣瀹屾垚鐨勯攢鍞鍗曚笉鑳藉垹闄�",
+        title: "宸插彂璐с�侀儴鍒嗗彂璐ф垨宸叉湁鍙戣揣璁板綍鐨勯攢鍞鍗曚笉鑳藉垹闄�",
         icon: "none",
       });
       return;
diff --git a/src/pages/sales/salesAccount/out.vue b/src/pages/sales/salesAccount/out.vue
index 74affb7..117bd6b 100644
--- a/src/pages/sales/salesAccount/out.vue
+++ b/src/pages/sales/salesAccount/out.vue
@@ -113,6 +113,10 @@
                   class="detail-value danger">涓嶈冻</text>
           </view>
           <view class="detail-row">
+            <text class="detail-label">鍙戣揣鐘舵��</text>
+            <text class="detail-value">{{ getLedgerShippingLabel(item) }}</text>
+          </view>
+          <view class="detail-row">
             <text class="detail-label">蹇�掑叕鍙�</text>
             <text class="detail-value">{{ dv(item.expressCompany) }}</text>
           </view>
@@ -166,6 +170,7 @@
 <script setup>
   import { ref, onMounted } from "vue";
   import { productList } from "@/api/salesManagement/salesLedger";
+  import { getLedgerShippingLabel } from "@/utils/salesLedgerShip";
 
   // 瀹㈡埛淇℃伅
   const supplierId = ref("");
diff --git a/src/utils/salesLedgerShip.js b/src/utils/salesLedgerShip.js
new file mode 100644
index 0000000..00df9af
--- /dev/null
+++ b/src/utils/salesLedgerShip.js
@@ -0,0 +1,209 @@
+import { productList } from "@/api/salesManagement/salesLedger";
+
+/** 鍙拌处/浜у搧鍙戣揣鐘舵�侊細1-鏈彂璐� 鈥� 5-宸插彂璐� 6-閮ㄥ垎鍙戣揣锛堜笌鍚庣鏋氫妇瀵归綈锛屽吋瀹瑰绉嶅瓧娈靛悕锛� */
+export const LEDGER_SHIPPING_LABELS = {
+  1: "鏈彂璐�",
+  2: "瀹℃壒涓�",
+  3: "瀹℃壒涓嶉�氳繃",
+  4: "瀹℃壒閫氳繃",
+  5: "宸插彂璐�",
+  6: "閮ㄥ垎鍙戣揣",
+};
+
+export const normalizeShippingStatusToCode = v => {
+  if (v === null || v === undefined || v === "") return 1;
+  const n = Number(v);
+  if (!Number.isNaN(n) && n >= 1 && n <= 6) return n;
+  const s = String(v).trim();
+  const textMap = {
+    鏈彂璐�: 1,
+    寰呭彂璐�: 1,
+    瀹℃壒涓�: 2,
+    瀹℃牳涓�: 2,
+    寰呭鏍�: 2,
+    瀹℃壒涓嶉�氳繃: 3,
+    瀹℃牳鎷掔粷: 3,
+    瀹℃壒閫氳繃: 4,
+    瀹℃牳閫氳繃: 4,
+    宸插彂璐�: 5,
+    鍙戣揣瀹屾垚: 5,
+    宸插畬鎴愬彂璐�: 5,
+    閮ㄥ垎鍙戣揣: 6,
+    閮ㄥ垎宸插彂璐�: 6,
+  };
+  return textMap[s] ?? 1;
+};
+
+export const getLedgerShippingStatusCode = item => {
+  if (!item) return 1;
+  const raw =
+    item.deliveryStatus ??
+    item.shippingApprovalStatus ??
+    item.shipmentApproveStatus ??
+    item.ledgerShippingStatus;
+  if (raw !== null && raw !== undefined && raw !== "") {
+    return normalizeShippingStatusToCode(raw);
+  }
+  if (item.shippingStatus !== null && item.shippingStatus !== undefined && item.shippingStatus !== "") {
+    return normalizeShippingStatusToCode(item.shippingStatus);
+  }
+  return 1;
+};
+
+export const getLedgerShippingLabel = item =>
+  LEDGER_SHIPPING_LABELS[getLedgerShippingStatusCode(item)] ?? "鏈彂璐�";
+
+export const getLedgerShippingTagType = item => {
+  const t = {
+    1: "info",
+    2: "warning",
+    3: "error",
+    4: "primary",
+    5: "success",
+    6: "warning",
+  };
+  return t[getLedgerShippingStatusCode(item)] ?? "info";
+};
+
+/** 鍙拌处绾ф槸鍚﹀厑璁稿彂璧�/缁х画鍙戣揣锛堝惈閮ㄥ垎鍙戣揣鍚庣户缁彂鍓╀綑锛� */
+export const canLedgerShip = item => {
+  const c = getLedgerShippingStatusCode(item);
+  return c === 1 || c === 3 || c === 6;
+};
+
+/** productStockStatus锛�0 鏈嚭搴� 鈥� 涓嶅厑璁稿彂璐э紙涓庝笟鍔$灞曠ず涓�鑷达級 */
+export const isProductStockStatusUnOutbound = row => {
+  if (!row) return false;
+  const v = row.productStockStatus;
+  return v === 0 || v === "0";
+};
+
+/**
+ * 鍗曡浜у搧鏄惁鍏佽杩涘叆鍙戣揣椤碉紙鍏呰冻 + 闈炴湭鍑哄簱锛涘緟鍙戣揣/瀹℃牳鎷掔粷/閮ㄥ垎鍙戣揣鍙户缁級
+ * 閮ㄥ垎鍙戣揣琛屽彲鑳藉凡鏈� shippingDate/杞︾墝锛屼粛鍏佽鍐嶆鍙戣揣
+ */
+export const canShipProductRow = row => {
+  if (!row) return false;
+  if (isProductStockStatusUnOutbound(row)) return false;
+  if (row.approveStatus !== 1) return false;
+
+  const statusStr = row.shippingStatus ? String(row.shippingStatus).trim() : "";
+  let rowCode = 1;
+  if (row.deliveryStatus !== null && row.deliveryStatus !== undefined && String(row.deliveryStatus).trim() !== "") {
+    rowCode = normalizeShippingStatusToCode(row.deliveryStatus);
+  } else if (statusStr) {
+    rowCode = normalizeShippingStatusToCode(statusStr);
+  }
+
+  if (rowCode === 5) return false;
+
+  const isPartialRow = rowCode === 6 || statusStr === "閮ㄥ垎鍙戣揣" || statusStr === "閮ㄥ垎宸插彂璐�";
+  if ((row.shippingDate || row.shippingCarNumber) && !isPartialRow) return false;
+
+  if (row.deliveryStatus !== null && row.deliveryStatus !== undefined && String(row.deliveryStatus).trim() !== "") {
+    const code = normalizeShippingStatusToCode(row.deliveryStatus);
+    if (code === 5) return false;
+  }
+
+  return (
+    statusStr === "寰呭彂璐�" ||
+    statusStr === "瀹℃牳鎷掔粷" ||
+    statusStr === "閮ㄥ垎鍙戣揣" ||
+    statusStr === "閮ㄥ垎宸插彂璐�" ||
+    rowCode === 6
+  );
+};
+
+export const productLabelForShip = row => {
+  if (!row) return "浜у搧";
+  const parts = [row.productCategory, row.floorCode, row.specificationModel].filter(Boolean);
+  return parts.length ? parts.join(" / ") : row.productName || row.goodsName || "浜у搧";
+};
+
+/** 鍒ゆ柇鏄惁瀛樺湪宸插彂璐с�侀儴鍒嗗彂璐ф垨宸叉湁鍙戣揣鐥曡抗鐨勪骇鍝侊紙鍒犻櫎鍙拌处绛夊満鏅級 */
+export const hasShippedProducts = products => {
+  if (!products || products.length === 0) return false;
+  return products.some(p => {
+    const statusCode = normalizeShippingStatusToCode(p.deliveryStatus ?? p.shippingStatus);
+    const statusStr = (p.shippingStatus ?? "").toString().trim();
+    return (
+      statusCode === 5 ||
+      statusCode === 6 ||
+      statusStr === "宸插彂璐�" ||
+      statusStr === "鍙戣揣瀹屾垚" ||
+      statusStr === "宸插畬鎴愬彂璐�" ||
+      statusStr === "閮ㄥ垎鍙戣揣" ||
+      statusStr === "閮ㄥ垎宸插彂璐�" ||
+      !!p.shippingDate ||
+      !!p.shippingCarNumber
+    );
+  });
+};
+
+const showLoadingToast = message => {
+  uni.showLoading({ title: message, mask: true });
+};
+const closeToast = () => {
+  uni.hideLoading();
+};
+
+/**
+ * 涓庨攢鍞彴璐︺�屽彂璐с�嶆寜閽竴鑷达細鏍¢獙鍙拌处鐘舵�� 鈫� 鎷変骇鍝� 鈫� 鏍¢獙搴撳瓨涓庡彲鍙戣揣琛� 鈫� 璺宠浆 goOut
+ * @param {Record<string, any>} ledgerItem 鑷冲皯鍚� id锛涘惈鍙戣揣瀹℃壒瀛楁鏃跺厛鍋� canLedgerShip 鏍¢獙
+ */
+export async function executeSalesLedgerShip(ledgerItem) {
+  if (!canLedgerShip(ledgerItem)) {
+    uni.showToast({
+      title: "浠呮湭鍙戣揣銆佸鎵逛笉閫氳繃鎴栭儴鍒嗗彂璐ф椂鍙户缁彂璐�",
+      icon: "none",
+    });
+    return;
+  }
+  if (!ledgerItem?.id) return;
+  showLoadingToast("鍔犺浇涓�...");
+  try {
+    const res = await productList({ salesLedgerId: ledgerItem.id, type: 1 });
+    const products = res.data || res.records || [];
+    closeToast();
+    if (!products.length) {
+      uni.showToast({ title: "娌℃湁浜у搧鏁版嵁", icon: "none" });
+      return;
+    }
+    const insufficient = products.filter(p => p?.approveStatus !== 1);
+    if (insufficient.length) {
+      const names = insufficient.slice(0, 3).map(productLabelForShip).join("銆�");
+      uni.showToast({
+        title: `瀛樺湪搴撳瓨涓嶈冻浜у搧锛�${names}${insufficient.length > 3 ? "鈥�" : ""}`,
+        icon: "none",
+        duration: 2500,
+      });
+      return;
+    }
+    const unOutbound = products.filter(p => isProductStockStatusUnOutbound(p));
+    if (unOutbound.length) {
+      const names = unOutbound.slice(0, 3).map(productLabelForShip).join("銆�");
+      uni.showToast({
+        title: `瀛樺湪鏈嚭搴撲骇鍝侊紝涓嶈兘鍙戣揣锛�${names}${unOutbound.length > 3 ? "鈥�" : ""}`,
+        icon: "none",
+        duration: 2500,
+      });
+      return;
+    }
+    const row = products.find(p => canShipProductRow(p));
+    if (!row) {
+      uni.showToast({
+        title: "娌℃湁鍙彂璐х殑浜у搧锛堝緟鍙戣揣銆佸鏍告嫆缁濇垨閮ㄥ垎鍙戣揣鍙户缁級",
+        icon: "none",
+        duration: 2500,
+      });
+      return;
+    }
+    uni.setStorageSync("goOutData", JSON.stringify(row));
+    uni.navigateTo({
+      url: "/pages/sales/salesAccount/goOut",
+    });
+  } catch (e) {
+    closeToast();
+    uni.showToast({ title: "鍔犺浇浜у搧澶辫触", icon: "none" });
+  }
+}

--
Gitblit v1.9.3