From 88f5470dc4829e2bdde9dc4aeb79be85837c1c84 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期六, 16 五月 2026 10:08:22 +0800
Subject: [PATCH] 发货功能重构

---
 src/pages/sales/salesAccount/goOut.vue        |  976 +++++++++++++++++++++--------------------------------
 src/api/inventoryManagement/stockInventory.js |  104 +++--
 2 files changed, 441 insertions(+), 639 deletions(-)

diff --git a/src/api/inventoryManagement/stockInventory.js b/src/api/inventoryManagement/stockInventory.js
index c4115ea..328657c 100644
--- a/src/api/inventoryManagement/stockInventory.js
+++ b/src/api/inventoryManagement/stockInventory.js
@@ -1,70 +1,78 @@
 import request from "@/utils/request";
 // 鍒嗛〉鏌ヨ搴撳瓨璁板綍鍒楄〃
-export const getStockInventoryListPage = (params) => {
-    return request({
-        url: "/stockInventory/pagestockInventory",
-        method: "get",
-        params,
-    });
+export const getStockInventoryListPage = params => {
+  return request({
+    url: "/stockInventory/pagestockInventory",
+    method: "get",
+    params,
+  });
 };
 
 // 鍒嗛〉鏌ヨ鑱斿悎搴撳瓨璁板綍鍒楄〃锛堝寘鍚晢鍝佷俊鎭級
-export const getStockInventoryListPageCombined = (params) => {
-    return request({
-        url: "/stockInventory/pageListCombinedStockInventory",
-        method: "get",
-        params,
-    });
+export const getStockInventoryListPageCombined = params => {
+  return request({
+    url: "/stockInventory/pageListCombinedStockInventory",
+    method: "get",
+    params,
+  });
 };
 
 // 鍒涘缓搴撳瓨璁板綍
-export const createStockInventory = (params) => {
-    return request({
-        url: "/stockInventory/addstockInventory",
-        method: "post",
-        data: params,
-    });
+export const createStockInventory = params => {
+  return request({
+    url: "/stockInventory/addstockInventory",
+    method: "post",
+    data: params,
+  });
 };
 
 // 鍑忓皯搴撳瓨璁板綍
-export const subtractStockInventory = (params) => {
-    return request({
-        url: "/stockInventory/subtractStockInventory",
-        method: "post",
-        data: params,
-    });
+export const subtractStockInventory = params => {
+  return request({
+    url: "/stockInventory/subtractStockInventory",
+    method: "post",
+    data: params,
+  });
 };
 
-export const getStockInventoryReportList = (params) => {
-    return request({
-        url: "/stockInventory/stockInventoryPage",
-        method: "get",
-        params,
-    });
+export const getStockInventoryReportList = params => {
+  return request({
+    url: "/stockInventory/stockInventoryPage",
+    method: "get",
+    params,
+  });
 };
 
-export const getStockInventoryInAndOutReportList = (params) => {
-    return request({
-        url: "/stockInventory/stockInAndOutRecord",
-        method: "get",
-        params,
-    });
+export const getStockInventoryInAndOutReportList = params => {
+  return request({
+    url: "/stockInventory/stockInAndOutRecord",
+    method: "get",
+    params,
+  });
 };
 
 // 鍐荤粨搴撳瓨璁板綍
-export const frozenStockInventory = (params) => {
-    return request({
-        url: "/stockInventory/frozenStock",
-        method: "post",
-        data: params,
-    });
+export const frozenStockInventory = params => {
+  return request({
+    url: "/stockInventory/frozenStock",
+    method: "post",
+    data: params,
+  });
 };
 
 // 瑙e喕搴撳瓨璁板綍
-export const thawStockInventory = (params) => {
-    return request({
-        url: "/stockInventory/thawStock",
-        method: "post",
-        data: params,
-    });
+export const thawStockInventory = params => {
+  return request({
+    url: "/stockInventory/thawStock",
+    method: "post",
+    data: params,
+  });
+};
+
+export const getStockInventoryByModelId = productModelId => {
+  return request({
+    url: "/stockInventory/getByModelId",
+    method: "get",
+    params: { productModelId },
+  });
 };
diff --git a/src/pages/sales/salesAccount/goOut.vue b/src/pages/sales/salesAccount/goOut.vue
index 9980e5f..42321c9 100644
--- a/src/pages/sales/salesAccount/goOut.vue
+++ b/src/pages/sales/salesAccount/goOut.vue
@@ -1,657 +1,451 @@
 <template>
-  <view class="account-detail">
+  <view class="shipment-page">
     <PageHeader title="鍙戣揣"
                 @back="goBack" />
-    <!-- 琛ㄥ崟鍖哄煙 -->
-    <u-form ref="formRef"
-            @submit="submitForm"
-            :rules="rules"
-            :model="form"
-            label-width="140rpx">
-      <u-form-item prop="typeValue"
-                   label="鍙戣揣绫诲瀷"
-                   required>
-        <u-input v-model="typeValue"
-                 readonly
-                 placeholder="璇烽�夋嫨鍙戣揣鏂瑰紡"
-                 @click="showPicker = true" />
-        <template #right>
-          <up-icon name="arrow-right"
-                   @click="showPicker = true"></up-icon>
-        </template>
-      </u-form-item>
-    </u-form>
-    <!-- 閫夋嫨鍣ㄥ脊绐� -->
-    <up-action-sheet :show="showPicker"
-                     :actions="productOptions"
-                     title="鍙戣揣鏂瑰紡"
-                     @select="onConfirm"
-                     @close="showPicker = false" />
-    <!-- 瀹℃牳娴佺▼鍖哄煙 -->
-    <view class="approval-process">
-      <view class="approval-header">
-        <text class="approval-title">瀹℃牳娴佺▼</text>
-        <text class="approval-desc">姣忎釜姝ラ鍙兘閫夋嫨涓�涓鎵逛汉</text>
-      </view>
-      <view class="approval-steps">
-        <view v-for="(step, stepIndex) in approverNodes"
-              :key="stepIndex"
-              class="approval-step">
-          <view class="step-dot"></view>
-          <view class="step-title">
-            <text>瀹℃壒浜�</text>
+    <view class="form-container">
+      <up-form ref="formRef"
+               :model="form"
+               :rules="rules"
+               label-width="100"
+               input-align="right"
+               error-message-align="right">
+        <!-- 鍩烘湰淇℃伅 -->
+        <u-cell-group title="鍩烘湰淇℃伅"
+                      class="form-section">
+          <up-form-item label="鍙戣揣鏂瑰紡"
+                        prop="type"
+                        required>
+            <up-input v-model="form.type"
+                      readonly
+                      placeholder="璇烽�夋嫨鍙戣揣鏂瑰紡"
+                      @click="showTypePicker = true" />
+            <template #right>
+              <up-icon name="arrow-right"
+                       @click="showTypePicker = true"></up-icon>
+            </template>
+          </up-form-item>
+          <block v-if="form.type === '璐ц溅'">
+            <up-form-item label="鍙戣揣杞︾墝"
+                          prop="shippingCarNumber"
+                          required>
+              <up-input v-model="form.shippingCarNumber"
+                        placeholder="璇疯緭鍏ュ彂璐ц溅鐗屽彿"
+                        clearable />
+            </up-form-item>
+          </block>
+          <block v-if="form.type === '蹇��'">
+            <up-form-item label="蹇�掑叕鍙�"
+                          prop="expressCompany"
+                          required>
+              <up-input v-model="form.expressCompany"
+                        placeholder="璇疯緭鍏ュ揩閫掑叕鍙�"
+                        clearable />
+            </up-form-item>
+            <up-form-item label="蹇�掑崟鍙�"
+                          prop="expressNumber"
+                          required>
+              <up-input v-model="form.expressNumber"
+                        placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+                        clearable />
+            </up-form-item>
+          </block>
+        </u-cell-group>
+        <!-- 鎵规閫夋嫨 -->
+        <u-cell-group title="鎵规閫夋嫨"
+                      class="form-section">
+          <view class="section-header-info">
+            <text class="subtitle">寰呭彂璐ф暟閲�: {{ goOutData.noQuantity || 0 }}</text>
           </view>
-          <view class="approver-container">
-            <view v-if="step.nickName"
-                  class="approver-item">
-              <view class="approver-avatar">
-                <text class="avatar-text">{{ step.nickName.charAt(0) }}</text>
-                <view class="status-dot"></view>
+          <view v-if="batchList.length === 0"
+                class="empty-text">
+            <text>鏆傛棤鍙敤搴撳瓨鎵规</text>
+          </view>
+          <view v-else
+                class="batch-list">
+            <view v-for="(item, index) in batchList"
+                  :key="index"
+                  class="batch-card">
+              <view class="batch-header">
+                <text class="batch-no">鎵瑰彿: {{ item.batchNo }}</text>
+                <text class="batch-qty">搴撳瓨: {{ getAvailableQty(item) }}</text>
               </view>
-              <view class="approver-info">
-                <text class="approver-name">{{ step.nickName }}</text>
+              <up-divider></up-divider>
+              <view class="batch-body">
+                <up-form-item label="鍙戣揣鏁伴噺">
+                  <up-input v-model="item.deliveryQuantity"
+                            type="digit"
+                            placeholder="0.00"
+                            input-align="right"
+                            @blur="onBatchQtyChange(item)" />
+                </up-form-item>
               </view>
-              <view class="delete-approver-btn"
-                    @click="removeApprover(stepIndex)">脳</view>
-            </view>
-            <view v-else
-                  class="add-approver-btn"
-                  @click="addApprover(stepIndex)">
-              <view class="add-circle">+</view>
-              <text class="add-label">閫夋嫨瀹℃壒浜�</text>
             </view>
           </view>
-          <view class="step-line"
-                v-if="stepIndex < approverNodes.length - 1"></view>
-          <view class="delete-step-btn"
-                v-if="approverNodes.length > 1"
-                @click="removeApprovalStep(stepIndex)">鍒犻櫎鑺傜偣</view>
-        </view>
-      </view>
-      <view class="add-step-btn">
-        <u-button icon="plus"
-                  plain
-                  type="primary"
-                  style="width: 100%"
-                  @click="addApprovalStep">鏂板鑺傜偣</u-button>
-      </view>
+        </u-cell-group>
+        <!-- 鍙戣揣鍥剧墖 -->
+        <u-cell-group title="鍙戣揣鍥剧墖"
+                      class="form-section">
+          <view class="upload-container">
+            <up-upload :fileList="fileList"
+                       @afterRead="afterRead"
+                       @delete="deleteFile"
+                       multiple
+                       :maxCount="9"
+                       width="160rpx"
+                       height="160rpx" />
+          </view>
+        </u-cell-group>
+      </up-form>
     </view>
     <!-- 搴曢儴鎸夐挳 -->
-    <view class="footer-btns">
-      <u-button class="cancel-btn"
-                @click="goBack">鍙栨秷</u-button>
-      <u-button class="save-btn"
-                @click="submitForm">鍙戣揣</u-button>
-    </view>
+    <FooterButtons confirmText="纭鍙戣揣"
+                   @cancel="goBack"
+                   @confirm="submitForm" />
+    <!-- 鍙戣揣鏂瑰紡閫夋嫨鍣� -->
+    <up-action-sheet :show="showTypePicker"
+                     :actions="typeActions"
+                     title="鍙戣揣鏂瑰紡"
+                     @select="onTypeSelect"
+                     @close="showTypePicker = false" />
   </view>
 </template>
 
 <script setup>
-  import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
+  import config from "@/config";
+  import { ref, onMounted, reactive } from "vue";
   import PageHeader from "@/components/PageHeader.vue";
+  import FooterButtons from "@/components/FooterButtons.vue";
   import { addShippingInfo } from "@/api/salesManagement/salesLedger";
-  const showToast = message => {
-    uni.showToast({
-      title: message,
-      icon: "none",
-    });
-  };
-  import { userListNoPageByTenantId } from "@/api/system/user";
+  import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory";
+  import { getToken } from "@/utils/auth";
 
-  const data = reactive({
-    form: {
-      approveTime: "",
-      approveId: "",
-      approveUser: "",
-      approveUserName: "",
-      approveDeptName: "",
-      approveDeptId: "",
-      approveReason: "",
-      checkResult: "",
-      tempFileIds: [],
-      approverList: [], // 鏂板瀛楁锛屽瓨鍌ㄦ墍鏈夎妭鐐圭殑瀹℃壒浜篿d
-      startDate: "",
-      endDate: "",
-      location: "",
-      price: "",
-    },
-    rules: {
-      typeValue: [{ required: false, message: "璇烽�夋嫨", trigger: "change" }],
-    },
-  });
-  const { form, rules } = toRefs(data);
-  const showPicker = ref(false);
-  const productOptions = ref([
-    {
-      value: "璐ц溅",
-      name: "璐ц溅",
-    },
-    {
-      value: "蹇��",
-      name: "蹇��",
-    },
-  ]);
-  const operationType = ref("");
-  const currentApproveStatus = ref("");
-  const approverNodes = ref([]);
-  const userList = ref([]);
-  const formRef = ref(null);
-  const approveType = ref(0);
   const goOutData = ref({});
+  const batchList = ref([]);
+  const fileList = ref([]);
+  const showTypePicker = ref(false);
+  const typeActions = [
+    { name: "璐ц溅", value: "璐ц溅" },
+    { name: "蹇��", value: "蹇��" },
+  ];
+
+  const form = reactive({
+    type: "璐ц溅",
+    shippingCarNumber: "",
+    expressCompany: "",
+    expressNumber: "",
+  });
+
+  const rules = {
+    type: [{ required: true, message: "璇烽�夋嫨鍙戣揣鏂瑰紡", trigger: "change" }],
+    shippingCarNumber: [
+      {
+        required: true,
+        validator: (rule, value, callback) => {
+          if (form.type === "璐ц溅" && !value) {
+            return false;
+          }
+          return true;
+        },
+        message: "璇疯緭鍏ヨ溅鐗屽彿",
+        trigger: "blur",
+      },
+    ],
+    expressCompany: [
+      {
+        required: true,
+        validator: (rule, value, callback) => {
+          if (form.type === "蹇��" && !value) {
+            return false;
+          }
+          return true;
+        },
+        message: "璇疯緭鍏ュ揩閫掑叕鍙�",
+        trigger: "blur",
+      },
+    ],
+    expressNumber: [
+      {
+        required: true,
+        validator: (rule, value, callback) => {
+          if (form.type === "蹇��" && !value) {
+            return false;
+          }
+          return true;
+        },
+        message: "璇疯緭鍏ュ揩閫掑崟鍙�",
+        trigger: "blur",
+      },
+    ],
+  };
+
+  const formRef = ref(null);
+
   onMounted(async () => {
-    try {
-      userListNoPageByTenantId().then(res => {
-        userList.value = res.data;
-      });
-      // 浠庢湰鍦板瓨鍌ㄨ幏鍙栧彂璐ц鎯�
-      goOutData.value = JSON.parse(uni.getStorageSync("goOutData"));
-      console.log(goOutData.value, "goOutData.value");
-
-      // 鍒濆鍖栧鎵规祦绋嬭妭鐐癸紝榛樿涓�涓妭鐐�
-      approverNodes.value = [{ id: 1, userId: null }];
-
-      // 鐩戝惉鑱旂郴浜洪�夋嫨浜嬩欢
-      uni.$on("selectContact", handleSelectContact);
-    } catch (error) {
-      console.error("鑾峰彇澶辫触:", error);
+    const storedData = uni.getStorageSync("goOutData");
+    goOutData.value = JSON.parse(storedData || "{}");
+    if (goOutData.value.productModelId) {
+      loadBatches(goOutData.value.productModelId);
     }
   });
 
-  onUnmounted(() => {
-    // 绉婚櫎浜嬩欢鐩戝惉
-    uni.$off("selectContact", handleSelectContact);
-  });
-  const typeValue = ref("璐ц溅");
-  const onConfirm = item => {
-    // 璁剧疆閫変腑鐨勯儴闂�
-    typeValue.value = item.name;
-    showPicker.value = false;
+  const loadBatches = async modelId => {
+    if (!modelId) return;
+    try {
+      const res = await getStockInventoryByModelId(modelId);
+      const rawList = Array.isArray(res?.data)
+        ? res.data
+        : res?.data?.records || res?.data?.rows || res || [];
+      const seenIds = new Set();
+      batchList.value = rawList
+        .filter(item => {
+          if (!item?.id || !item?.batchNo || seenIds.has(item.id)) {
+            return false;
+          }
+          seenIds.add(item.id);
+          return true;
+        })
+        .map(item => ({
+          ...item,
+          deliveryQuantity: "",
+        }));
+    } catch (e) {
+      console.error("鍔犺浇鎵规澶辫触", e);
+    }
   };
 
-  const goBack = () => {
-    // 娓呴櫎鏈湴瀛樺偍鐨勬暟鎹�
-    uni.removeStorageSync("operationType");
-    uni.removeStorageSync("invoiceLedgerEditRow");
-    uni.removeStorageSync("approveType");
-    uni.navigateBack();
+  const getAvailableQty = item => {
+    const quantity =
+      item?.qualitity ??
+      item?.quantity ??
+      item?.unLockedQuantity ??
+      item?.qualifiedUnLockedQuantity ??
+      item?.qualifiedQuantity ??
+      item?.stockQuantity;
+    return quantity ?? 0;
   };
 
-  const submitForm = () => {
-    // 妫�鏌ユ瘡涓鎵规楠ゆ槸鍚﹂兘鏈夊鎵逛汉
-    const hasEmptyStep = approverNodes.value.some(step => !step.nickName);
-    if (hasEmptyStep) {
-      showToast("璇蜂负姣忎釜瀹℃壒姝ラ閫夋嫨瀹℃壒浜�");
+  const onBatchQtyChange = item => {
+    const val = parseFloat(item.deliveryQuantity);
+    if (isNaN(val) || val <= 0) {
+      item.deliveryQuantity = "";
       return;
     }
-    formRef.value
-      .validate()
-      .then(valid => {
-        if (valid) {
-          // 琛ㄥ崟鏍¢獙閫氳繃锛屽彲浠ユ彁浜ゆ暟鎹�
-          // 鏀堕泦鎵�鏈夎妭鐐圭殑瀹℃壒浜篿d
-          console.log("approverNodes---", approverNodes.value);
-          const approveUserIds = approverNodes.value
-            .map(node => node.userId)
-            .join(",");
-          const params = {
-            salesLedgerId: goOutData.value.salesLedgerId,
-            salesLedgerProductId: goOutData.value.id,
-            type: typeValue.value,
-            approveUserIds,
-          };
-          console.log(params, "params");
 
-          addShippingInfo(params).then(res => {
-            showToast("鍙戣揣鎴愬姛");
-            setTimeout(() => {
-              goBack();
-            }, 500);
-          });
-        }
-      })
-      .catch(error => {
-        console.error("琛ㄥ崟鏍¢獙澶辫触:", error);
-        // 灏濊瘯鑾峰彇鍏蜂綋鐨勯敊璇瓧娈�
-        if (error && error.errors) {
-          const firstError = error.errors[0];
-          if (firstError) {
-            uni.showToast({
-              title: firstError.message || "琛ㄥ崟鏍¢獙澶辫触锛岃妫�鏌ュ繀濉」",
-              icon: "none",
-            });
-            return;
+    const available = getAvailableQty(item);
+    if (val > available) {
+      uni.showToast({ title: "涓嶈兘瓒呰繃搴撳瓨鏁伴噺", icon: "none" });
+      item.deliveryQuantity = available.toString();
+    }
+
+    const totalToShip = Number(goOutData.value.noQuantity || 0);
+    const otherBatchesTotal = batchList.value.reduce((sum, b) => {
+      if (b.id === item.id) return sum;
+      return sum + Number(b.deliveryQuantity || 0);
+    }, 0);
+
+    if (val + otherBatchesTotal > totalToShip) {
+      uni.showToast({ title: "鎬绘暟涓嶈兘瓒呰繃寰呭彂璐ф暟閲�", icon: "none" });
+      item.deliveryQuantity = (totalToShip - otherBatchesTotal).toString();
+    }
+  };
+
+  const onTypeSelect = item => {
+    form.type = item.name;
+    showTypePicker.value = false;
+  };
+
+  const afterRead = async event => {
+    const { file } = event;
+    const lists = [].concat(file);
+    const token = getToken();
+
+    for (let i = 0; i < lists.length; i++) {
+      const item = lists[i];
+      const uploadIndex = fileList.value.length;
+      fileList.value.push({
+        ...item,
+        status: "uploading",
+        message: "涓婁紶涓�",
+      });
+
+      uni.uploadFile({
+        url: config.baseUrl + "/common/upload",
+        filePath: item.url,
+        name: "files",
+        header: {
+          Authorization: "Bearer " + token,
+        },
+        success: res => {
+          try {
+            const data = JSON.parse(res.data);
+            if (data.code === 200) {
+              const fileData = Array.isArray(data.data)
+                ? data.data[0]
+                : data.data || data;
+              fileList.value[uploadIndex].status = "success";
+              fileList.value[uploadIndex].message = "";
+              fileList.value[uploadIndex].url = fileData.url;
+              fileList.value[uploadIndex].storageBlobDTO = fileData;
+            } else {
+              fileList.value[uploadIndex].status = "failed";
+              fileList.value[uploadIndex].message = data.msg || "涓婁紶澶辫触";
+            }
+          } catch (e) {
+            fileList.value[uploadIndex].status = "failed";
+            fileList.value[uploadIndex].message = "瑙f瀽澶辫触";
           }
-        }
-        // 鏄剧ず閫氱敤閿欒淇℃伅
-        uni.showToast({
-          title: "琛ㄥ崟鏍¢獙澶辫触锛岃妫�鏌ュ繀濉」",
-          icon: "none",
-        });
+        },
+        fail: () => {
+          fileList.value[uploadIndex].status = "failed";
+          fileList.value[uploadIndex].message = "缃戠粶寮傚父";
+        },
       });
+    }
   };
 
-  // 澶勭悊鑱旂郴浜洪�夋嫨缁撴灉
-  const handleSelectContact = data => {
-    const { stepIndex, contact } = data;
-    // 灏嗛�変腑鐨勮仈绯讳汉璁剧疆涓哄搴斿鎵规楠ょ殑瀹℃壒浜�
-    approverNodes.value[stepIndex].userId = contact.userId;
-    approverNodes.value[stepIndex].nickName = contact.nickName;
+  const deleteFile = event => {
+    fileList.value.splice(event.index, 1);
   };
 
-  const addApprover = stepIndex => {
-    // 璺宠浆鍒拌仈绯讳汉閫夋嫨椤甸潰
-    uni.setStorageSync("stepIndex", stepIndex);
-    uni.navigateTo({
-      url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect",
-    });
-  };
+  const goBack = () => uni.navigateBack();
 
-  const addApprovalStep = () => {
-    // 娣诲姞鏂扮殑瀹℃壒姝ラ
-    approverNodes.value.push({ userId: null, nickName: null });
-  };
+  const submitForm = async () => {
+    const valid = await formRef.value.validate().catch(() => false);
+    if (!valid) return;
 
-  const removeApprover = stepIndex => {
-    // 绉婚櫎瀹℃壒浜�
-    approverNodes.value[stepIndex].userId = null;
-    approverNodes.value[stepIndex].nickName = null;
-  };
+    const selectedBatches = batchList.value.filter(
+      b => parseFloat(b.deliveryQuantity) > 0
+    );
+    if (selectedBatches.length === 0) {
+      uni.showToast({ title: "璇疯嚦灏戝~鍐欎竴涓壒娆$殑鍙戣揣鏁伴噺", icon: "none" });
+      return;
+    }
 
-  const removeApprovalStep = stepIndex => {
-    // 纭繚鑷冲皯淇濈暀涓�涓鎵规楠�
-    if (approverNodes.value.length > 1) {
-      approverNodes.value.splice(stepIndex, 1);
-    } else {
-      uni.showToast({
-        title: "鑷冲皯闇�瑕佷竴涓鎵规楠�",
-        icon: "none",
-      });
+    // Check if any file is still uploading
+    if (fileList.value.some(f => f.status === "uploading")) {
+      uni.showToast({ title: "璇风瓑寰呭浘鐗囦笂浼犲畬鎴�", icon: "none" });
+      return;
+    }
+
+    const payload = {
+      salesLedgerId: goOutData.value.salesLedgerId,
+      salesLedgerProductId: goOutData.value.id,
+      type: form.type,
+      shippingCarNumber: form.type === "璐ц溅" ? form.shippingCarNumber : "",
+      expressCompany: form.type === "蹇��" ? form.expressCompany : "",
+      expressNumber: form.type === "蹇��" ? form.expressNumber : "",
+      storageBlobDTOs: fileList.value
+        .filter(f => f.status === "success")
+        .map(f => f.storageBlobDTO),
+      batchNo: selectedBatches.map(b => b.id),
+      batchNoDetailList: selectedBatches.map(b => ({
+        stockInventoryId: b.id,
+        batchNo: b.batchNo,
+        quantity: parseFloat(b.deliveryQuantity),
+        productModelId: goOutData.value.productModelId,
+      })),
+    };
+
+    try {
+      uni.showLoading({ title: "鎻愪氦涓�..." });
+      const res = await addShippingInfo(payload);
+      uni.hideLoading();
+      uni.showToast({ title: "鍙戣揣鎴愬姛" });
+      setTimeout(() => goBack(), 500);
+    } catch (e) {
+      uni.hideLoading();
+      uni.showToast({ title: "鍙戣揣澶辫触", icon: "none" });
     }
   };
 </script>
 
 <style scoped lang="scss">
   @import "@/static/scss/form-common.scss";
-
-  .approval-process {
-    background: #fff;
-    margin: 16px;
-    border-radius: 16px;
-    padding: 16px;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+  .shipment-page {
+    min-height: 100vh;
+    background: #f8f9fa;
+    padding-bottom: 100px;
   }
 
-  .approval-header {
-    margin-bottom: 16px;
+  .form-container {
+    padding: 12px 12px 0;
   }
 
-  .approval-title {
-    font-size: 16px;
-    font-weight: 600;
-    color: #333;
-    display: block;
-    margin-bottom: 4px;
-  }
-
-  .approval-desc {
-    font-size: 12px;
-    color: #999;
-  }
-
-  /* 鏍峰紡澧炲己涓衡�滅畝娲佸皬鍦嗗湀椋庢牸鈥� */
-  .approval-steps {
-    padding-left: 22px;
-    position: relative;
-
-    &::before {
-      content: "";
-      position: absolute;
-      left: 11px;
-      top: 40px;
-      bottom: 40px;
-      width: 2px;
-      background: linear-gradient(
-        to bottom,
-        #e6f7ff 0%,
-        #bae7ff 50%,
-        #91d5ff 100%
-      );
-      border-radius: 1px;
-    }
-  }
-
-  .approval-step {
-    position: relative;
-    margin-bottom: 24px;
-
-    &::before {
-      content: "";
-      position: absolute;
-      left: -18px;
-      top: 14px; // 浠� 8px 璋冩暣涓� 14px锛屼笌鏂囧瓧涓績瀵归綈
-      width: 12px;
-      height: 12px;
-      background: #fff;
-      border: 3px solid #006cfb;
-      border-radius: 50%;
-      z-index: 2;
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-    }
-  }
-
-  .step-title {
-    top: 12px;
+  .form-section {
     margin-bottom: 12px;
-    position: relative;
-    margin-left: 6px;
-  }
-
-  .step-title text {
-    font-size: 14px;
-    color: #666;
-    background: #f0f0f0;
-    padding: 4px 12px;
     border-radius: 12px;
-    position: relative;
-    line-height: 1.4; // 纭繚鏂囧瓧琛岄珮涓�鑷�
+    overflow: hidden;
+    box-shadow: 0 2px 10px rgba(15, 35, 95, 0.05);
   }
 
-  .approver-item {
+  .section-header-info {
+    padding: 10px 18px;
+    background: #f8fbff;
     display: flex;
-    align-items: center;
-    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
-    border-radius: 16px;
-    padding: 16px;
+    justify-content: flex-end;
+
+    .subtitle {
+      font-size: 13px;
+      color: #7a8599;
+    }
+  }
+
+  .batch-list {
+    padding: 12px;
+    display: flex;
+    flex-direction: column;
     gap: 12px;
-    position: relative;
-    border: 1px solid #e6f7ff;
-    box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08);
-    transition: all 0.3s ease;
   }
 
-  .approver-avatar {
-    width: 48px;
-    height: 48px;
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-    border-radius: 50%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    position: relative;
-    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
-  }
-
-  .avatar-text {
-    color: #fff;
-    font-size: 18px;
-    font-weight: 600;
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
-  }
-
-  .approver-info {
-    flex: 1;
-    position: relative;
-  }
-
-  .approver-name {
-    display: block;
-    font-size: 16px;
-    color: #333;
-    font-weight: 500;
-    position: relative;
-  }
-
-  .approver-dept {
-    font-size: 12px;
-    color: #999;
-    background: rgba(0, 108, 251, 0.05);
-    padding: 2px 8px;
-    border-radius: 8px;
-    display: inline-block;
-    position: relative;
-
-    &::before {
-      content: "";
-      position: absolute;
-      left: 4px;
-      top: 50%;
-      transform: translateY(-50%);
-      width: 2px;
-      height: 2px;
-      background: #006cfb;
-      border-radius: 50%;
-    }
-  }
-
-  .delete-approver-btn {
-    font-size: 16px;
-    color: #ff4d4f;
-    background: linear-gradient(
-      135deg,
-      rgba(255, 77, 79, 0.1) 0%,
-      rgba(255, 77, 79, 0.05) 100%
-    );
-    width: 28px;
-    height: 28px;
-    border-radius: 50%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    transition: all 0.3s ease;
-    position: relative;
-  }
-
-  .add-approver-btn {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
-    border: 2px dashed #006cfb;
-    border-radius: 16px;
-    padding: 20px;
-    color: #006cfb;
-    font-size: 14px;
-    position: relative;
-    transition: all 0.3s ease;
-
-    &::before {
-      content: "";
-      position: absolute;
-      left: 50%;
-      top: 50%;
-      transform: translate(-50%, -50%);
-      width: 32px;
-      height: 32px;
-      border: 2px solid #006cfb;
-      border-radius: 50%;
-      opacity: 0;
-      transition: all 0.3s ease;
-    }
-  }
-
-  .delete-step-btn {
-    color: #ff4d4f;
-    font-size: 12px;
-    background: linear-gradient(
-      135deg,
-      rgba(255, 77, 79, 0.1) 0%,
-      rgba(255, 77, 79, 0.05) 100%
-    );
-    padding: 6px 12px;
-    border-radius: 12px;
-    display: inline-block;
-    position: relative;
-    transition: all 0.3s ease;
-
-    &::before {
-      content: "";
-      position: absolute;
-      left: 6px;
-      top: 50%;
-      transform: translateY(-50%);
-      width: 4px;
-      height: 4px;
-      background: #ff4d4f;
-      border-radius: 50%;
-    }
-  }
-
-  .step-line {
-    display: none; // 闅愯棌鍘熸潵鐨勭嚎鏉★紝浣跨敤浼厓绱犱唬鏇�
-  }
-
-  .add-step-btn {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-  .footer-btns {
-    position: fixed;
-    left: 0;
-    right: 0;
-    bottom: 0;
+  .batch-card {
     background: #fff;
+    border-radius: 12px;
+    padding: 0 12px 12px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+    border: 1px solid #f0f3f7;
+  }
+
+  .batch-header {
+    padding: 12px 0;
     display: flex;
-    justify-content: space-around;
+    justify-content: space-between;
     align-items: center;
-    padding: 0.75rem 0;
-    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
-    z-index: 1000;
-  }
 
-  .cancel-btn {
-    font-weight: 400;
-    font-size: 1rem;
-    color: #ffffff;
-    width: 6.375rem;
-    background: #c7c9cc;
-    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
-    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
-  }
-
-  .save-btn {
-    font-weight: 400;
-    font-size: 1rem;
-    color: #ffffff;
-    width: 14rem;
-    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
-    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
-    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
-  }
-
-  // 鍔ㄧ敾瀹氫箟
-  @keyframes pulse {
-    0% {
-      transform: scale(1);
-      opacity: 1;
+    .batch-no {
+      font-size: 14px;
+      font-weight: 600;
+      color: #22324d;
     }
-    50% {
-      transform: scale(1.2);
-      opacity: 0.7;
-    }
-    100% {
-      transform: scale(1);
-      opacity: 1;
+
+    .batch-qty {
+      font-size: 13px;
+      color: #2979ff;
+      background: #eef6ff;
+      padding: 2px 8px;
+      border-radius: 4px;
     }
   }
 
-  @keyframes rotate {
-    0% {
-      transform: rotate(0deg);
-    }
-    100% {
-      transform: rotate(360deg);
-    }
-  }
-
-  @keyframes ripple {
-    0% {
-      transform: translate(-50%, -50%) scale(0.8);
-      opacity: 1;
-    }
-    100% {
-      transform: translate(-50%, -50%) scale(1.6);
-      opacity: 0;
-    }
-  }
-
-  /* 濡傛灉宸叉湁 .step-line锛岃繖閲屾洿绮惧噯瀹氫綅鍒板乏渚т笌灏忓渾鐐瑰榻� */
-  .step-line {
-    position: absolute;
-    left: 4px;
-    top: 48px;
-    width: 2px;
-    height: calc(100% - 48px);
-    background: #e5e7eb;
-  }
-
-  .approver-container {
-    display: flex;
-    align-items: center;
-    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
-    border-radius: 16px;
-    gap: 12px;
-    padding: 10px 0;
-    background: transparent;
-    border: none;
-    box-shadow: none;
-  }
-
-  .approver-item {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    padding: 8px 10px;
-    background: transparent;
-    border: none;
-    box-shadow: none;
-    border-radius: 0;
-  }
-
-  .approver-avatar {
-    position: relative;
-    width: 40px;
-    height: 40px;
-    border-radius: 50%;
-    background: #f3f4f6;
-    border: 2px solid #e5e7eb;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    animation: none; /* 绂佺敤鏃嬭浆绛夊姩鐢伙紝鍥炲綊绠�娲� */
-  }
-
-  .avatar-text {
-    font-size: 14px;
-    color: #374151;
-    font-weight: 600;
-  }
-
-  .add-approver-btn {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    background: transparent;
-    border: none;
-    box-shadow: none;
-    padding: 0;
-  }
-
-  .add-approver-btn .add-circle {
-    width: 40px;
-    height: 40px;
-    border: 2px dashed #a0aec0;
-    border-radius: 50%;
-    color: #6b7280;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    font-size: 22px;
-    line-height: 1;
-  }
-
-  .add-approver-btn .add-label {
-    color: #3b82f6;
+  .empty-text {
+    padding: 30px 12px;
+    text-align: center;
+    color: #999;
     font-size: 14px;
   }
-</style>
\ No newline at end of file
+
+  .upload-container {
+    padding: 12px 18px;
+  }
+
+  :deep(.u-cell-group__title) {
+    padding: 14px 18px 10px !important;
+    font-size: 15px !important;
+    font-weight: 600 !important;
+    color: #22324d !important;
+    background: #f8fbff !important;
+  }
+
+  :deep(.u-form-item) {
+    padding: 0 18px !important;
+  }
+</style>

--
Gitblit v1.9.3