From 01c045e6ebc486e2a050b1739fad075b7f424254 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期一, 18 五月 2026 14:47:50 +0800
Subject: [PATCH] 优化设备保养:在新增和编辑保养计划时,清除本地存储中的repairId,确保数据一致性。

---
 src/pages/inventoryManagement/scanOut/index.vue | 2469 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 2,289 insertions(+), 180 deletions(-)

diff --git a/src/pages/inventoryManagement/scanOut/index.vue b/src/pages/inventoryManagement/scanOut/index.vue
index fadc34a..8e42473 100644
--- a/src/pages/inventoryManagement/scanOut/index.vue
+++ b/src/pages/inventoryManagement/scanOut/index.vue
@@ -1,339 +1,2448 @@
 <template>
+
   <view class="scan-container">
-    <PageHeader title="鎵爜鍑哄簱"
+
+    <PageHeader title="鎵爜鍙戣揣"
+
                 @back="goBack" />
+
     <view class="module-selector"
+
           v-if="!showForm">
+
       <view class="module-card"
+
             @click="startScan('qualified')">
+
         <view class="module-icon qualified">
+
           <u-icon name="checkbox-mark"
+
                   color="#fff"
+
                   size="40"></u-icon>
+
         </view>
+
         <view class="module-info">
+
           <text class="module-label">鍚堟牸鍑哄簱</text>
-          <text class="module-desc">鎵弿鍚堟牸鍝佽繘琛岄鐢ㄥ嚭搴�</text>
+
+          <text class="module-desc">璁板綍鍚堟牸鍝佺殑鍑哄簱娴佸悜锛岄攢鍞簩缁寸爜鎵弿鍙戣揣</text>
+
         </view>
+
       </view>
+
       <view class="module-card"
+
             @click="startScan('unqualified')">
+
         <view class="module-icon unqualified">
+
           <u-icon name="close"
+
                   color="#fff"
+
                   size="40"></u-icon>
+
         </view>
+
         <view class="module-info">
+
           <text class="module-label">涓嶅悎鏍煎嚭搴�</text>
+
           <text class="module-desc">璁板綍涓嶅悎鏍煎搧鐨勫嚭搴撴祦鍚�</text>
+
         </view>
+
       </view>
+
     </view>
-    <view class="form-content"
-          v-if="showForm">
-      <u-form ref="formRef"
-              :model="form"
-              :rules="formRules"
-              label-width="100px">
-        <u-form-item label="鍑哄簱绫诲瀷"
-                     border-bottom>
-          <u-tag :text="type === 'qualified' ? '鍚堟牸鍑哄簱' : '涓嶅悎鏍煎嚭搴�'"
-                 :type="type === 'qualified' ? 'success' : 'error'"></u-tag>
-        </u-form-item>
-        <u-form-item label="浜у搧鍚嶇О"
-                     border-bottom>
-          <u-input v-model="form.productName"
-                   readonly
-                   border="none"></u-input>
-        </u-form-item>
-        <u-form-item label="瑙勬牸鍨嬪彿"
-                     border-bottom>
-          <u-input v-model="form.model"
-                   readonly
-                   border="none"></u-input>
-        </u-form-item>
-        <u-form-item label="鍙敤搴撳瓨"
-                     border-bottom>
-          <u-input v-model="form.unLockedQuantity"
-                   readonly
-                   border="none"></u-input>{{form.unit}}
-        </u-form-item>
-        <u-form-item label="鍑哄簱鏁伴噺"
-                     prop="qualitity"
-                     required
-                     border-bottom>
-          <u-number-box v-model="form.qualitity"
-                        :min="1"
-                        :max="form.unLockedQuantity"
-                        :step="1"></u-number-box>
-          <text class="limit-tip">鏈�澶у彲棰嗙敤: {{form.unLockedQuantity}}</text>
-        </u-form-item>
-        <u-form-item label="澶囨敞"
-                     prop="remark"
-                     border-bottom>
-          <u-textarea v-model="form.remark"
-                      placeholder="璇疯緭鍏ュ娉�"
-                      count></u-textarea>
-        </u-form-item>
-      </u-form>
+
+    <scroll-view v-if="showForm"
+
+                 scroll-y
+
+                 class="detail-scroll">
+
+      <view class="detail-card"
+
+            v-for="(item, idx) in recordList"
+
+            :key="idx">
+
+        <view class="detail-card-title"
+
+              :class="{
+
+                'detail-card-title--collapsible': recordList.length > 1,
+
+                'is-collapsed': recordList.length > 1 && !isCardExpanded(idx),
+
+              }"
+
+              @click="recordList.length > 1 && toggleCardDetail(idx)">
+
+          <view class="detail-card-title-text">
+
+            <text class="detail-card-title-no">{{ cardTitleMain }}</text>
+
+            <text v-if="recordList.length > 1"
+
+                  class="detail-card-title-seq">锛坽{ idx + 1 }}/{{ recordList.length }}锛�</text>
+
+          </view>
+
+          <u-icon v-if="recordList.length > 1"
+
+                  :name="isCardExpanded(idx) ? 'arrow-up' : 'arrow-down'"
+
+                  color="#999"
+
+                  size="18"></u-icon>
+
+        </view>
+
+        <view v-show="recordList.length > 1 && !isCardExpanded(idx)"
+
+              class="detail-card-summary"
+
+              @click="toggleCardDetail(idx)">
+
+          <view class="kv-row kv-row--summary"
+
+                v-for="row in summaryFieldRows"
+
+                :key="'sum-' + row.key">
+
+            <text class="kv-label">{{ row.label }}</text>
+
+            <view class="kv-value kv-value--tag"
+
+                  v-if="row.key === 'approveStatus'">
+
+              <u-tag :type="approveStatusTagType(item)"
+
+                     size="small">{{ formatApproveStatus(item) }}</u-tag>
+
+            </view>
+
+            <view class="kv-value kv-value--tag"
+
+                  v-else-if="row.key === 'productStockStatus'">
+
+              <u-tag :type="productStockStatusTagType(item.productStockStatus)"
+
+                     size="small">{{ formatProductStockStatus(item.productStockStatus) }}</u-tag>
+
+            </view>
+
+            <text class="kv-value"
+
+                  v-else>{{ formatCell(item, row, idx) }}</text>
+
+          </view>
+
+          <text class="summary-tip">鐐瑰嚮鏌ョ湅鍏ㄩ儴</text>
+
+        </view>
+
+        <view v-show="isCardExpanded(idx)"
+
+              class="detail-card-body">
+
+          <view class="kv-row"
+
+                v-for="row in detailFieldRows"
+
+                :key="row.key">
+
+            <text class="kv-label">{{ row.label }}</text>
+
+            <view class="kv-value kv-value--tag"
+
+                  v-if="row.key === 'approveStatus'">
+
+              <u-tag :type="approveStatusTagType(item)"
+
+                     size="small">{{ formatApproveStatus(item) }}</u-tag>
+
+            </view>
+
+            <view class="kv-value kv-value--tag"
+
+                  v-else-if="row.key === 'productStockStatus'">
+
+              <u-tag :type="productStockStatusTagType(item.productStockStatus)"
+
+                     size="small">{{ formatProductStockStatus(item.productStockStatus) }}</u-tag>
+
+            </view>
+
+            <text class="kv-value"
+
+                  v-else>{{ formatCell(item, row, idx) }}</text>
+
+          </view>
+
+        </view>
+
+        <view v-if="!isFullyOutbound(item)"
+              class="stocked-qty-block">
+
+          <view class="kv-row stocked-qty-row">
+
+            <text class="kv-label">{{ needScanShipFlow ? "鏈鍙戣揣鏁伴噺" : "鍑哄簱鏁伴噺" }}</text>
+
+            <view class="kv-value stocked-qty-input-wrap">
+
+              <text v-if="needScanShipFlow"
+
+                    class="scan-ship-qty-readonly">{{ resolveScanShipLineQuantity(item) }}</text>
+
+              <up-input v-else
+
+                        :key="'stocked-' + idx"
+
+                        v-model="item.operateQuantity"
+
+                        type="number"
+
+                        placeholder="璇疯緭鍏ュ嚭搴撴暟閲�"
+                        :clearable="!isSalesQualifiedOutboundQtyLocked"
+
+                        :disabled="isSalesQualifiedOutboundQtyLocked"
+
+
+                        border="surround"
+
+                        @blur="onStockedQtyBlur(item)" />
+
+            </view>
+
+          </view>
+
+          <text v-if="needScanShipFlow"
+
+                class="scan-ship-qty-hint">鏁村崟涓�娆℃�у彂璐э紝鏁伴噺浠ヨ鍗曚负鍑嗭紝涓嶅彲淇敼</text>
+
+        </view>
+
+      </view>
+
+      <view v-if="needScanShipFlow"
+
+            class="scan-ship-card">
+
+        <view class="scan-ship-title">鍙戣揣淇℃伅</view>
+
+        <u-form label-width="220rpx">
+
+          <u-form-item label="鍙戣揣鏂瑰紡"
+
+                       class="scan-ship-type-form-item"
+
+                       required>
+
+            <view class="scan-ship-type-trigger"
+
+                  @tap.stop="openScanShipTypeSheet">
+
+              <u-input v-model="scanShipTypeLabel"
+
+                       readonly
+
+                       placeholder="璇烽�夋嫨" />
+
+              <u-icon name="arrow-right"
+
+                      color="#c0c4cc"
+
+                      size="18" />
+
+            </view>
+
+          </u-form-item>
+
+          <u-form-item v-if="scanShipTypeValue === '璐ц溅'"
+
+                       label="杞︾墝鍙�"
+
+                       required>
+
+            <u-input v-model="scanShipCarNumber"
+
+                     placeholder="璇疯緭鍏ヨ溅鐗屽彿"
+
+                     clearable />
+
+          </u-form-item>
+
+          <u-form-item v-if="scanShipTypeValue === '蹇��'"
+
+                       label="蹇�掑崟鍙�"
+
+                       required>
+
+            <u-input v-model="scanShipExpress"
+
+                     placeholder="璇疯緭鍏ュ揩閫掑崟鍙�"
+
+                     clearable />
+
+          </u-form-item>
+
+        </u-form>
+
+        <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 scanShipApproverNodes"
+
+                  :key="step.id"
+
+                  class="approval-step">
+
+              <view class="step-title">
+
+                <text>瀹℃壒浜�</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>
+
+                  <view class="approver-info">
+
+                    <text class="approver-name">{{ step.nickName }}</text>
+
+                  </view>
+
+                  <view class="delete-approver-btn"
+
+                        @tap.stop="removeScanShipApproverUser(stepIndex)">脳</view>
+
+                </view>
+
+                <view v-else
+
+                      class="add-approver-btn"
+
+                      @tap.stop="openScanShipContactSelect(stepIndex)">
+
+                  <view class="add-circle">+</view>
+
+                  <text class="add-label">閫夋嫨瀹℃壒浜�</text>
+
+                </view>
+
+              </view>
+
+              <view class="delete-step-btn"
+
+                    v-if="scanShipApproverNodes.length > 1"
+
+                    @tap.stop="removeScanShipApproverNode(stepIndex)">鍒犻櫎鑺傜偣</view>
+
+            </view>
+
+          </view>
+
+          <view class="add-step-btn">
+
+            <u-button icon="plus"
+
+                      plain
+
+                      type="primary"
+
+                      style="width: 100%"
+
+                      @click="addScanShipApproverNode">鏂板鑺傜偣</u-button>
+
+          </view>
+
+        </view>
+
+        <view class="scan-ship-files">
+
+          <text class="scan-ship-subtitle">鍙戣揣闄勪欢锛堥�夊~锛屾渶澶�10寮狅級</text>
+
+          <u-button class="scan-ship-add-img-btn"
+
+                    size="small"
+
+                    type="primary"
+
+                    :loading="scanShipUploading"
+
+                    :disabled="scanShipFiles.length >= 10"
+
+                    @click="chooseScanShipImage">娣诲姞鍥剧墖</u-button>
+
+          <view v-if="scanShipFiles.length"
+
+                class="scan-ship-file-grid">
+
+            <view v-for="(f, fi) in scanShipFiles"
+
+                  :key="fi"
+
+                  class="scan-ship-thumb-wrap">
+
+              <image :src="scanShipFileUrl(f)"
+
+                     class="scan-ship-thumb"
+
+                     mode="aspectFill"
+
+                     @click="previewScanShipImage(fi)" />
+
+              <text class="scan-ship-thumb-del"
+
+                    @click.stop="removeScanShipFile(fi)">脳</text>
+
+            </view>
+
+          </view>
+
+        </view>
+
+      </view>
+
+      <up-action-sheet :show="scanShipTypeSheetShow"
+
+                       :actions="scanShipTypeActions"
+
+                       title="鍙戣揣鏂瑰紡"
+
+                       @select="onScanShipTypeSelect"
+
+                       @close="scanShipTypeSheetShow = false" />
+
       <view class="footer-btns">
-        <u-button class="cancel-btn"
-                  @click="cancelForm">鍙栨秷</u-button>
-        <u-button class="save-btn"
-                  @click="handleSubmit"
-                  :loading="loading">纭鍑哄簱</u-button>
+
+        <u-button class="footer-cancel-btn"
+
+                  @click="cancelForm">杩斿洖</u-button>
+
+        <u-button v-if="needScanShipFlow"
+
+                  class="footer-confirm-btn"
+
+                  :loading="submitLoading"
+
+                  @click="confirmScanShipApply">鎻愪氦鍙戣揣瀹℃壒</u-button>
+
+        <u-button v-else
+
+                  class="footer-confirm-btn"
+
+                  :loading="submitLoading"
+
+                  @click="confirmOutbound">纭</u-button>
+
       </view>
-    </view>
+
+    </scroll-view>
+
   </view>
+
 </template>
 
+
+
 <script setup>
-  import { ref, reactive } from "vue";
+
+  import { ref, computed, onMounted, onUnmounted } from "vue";
+
   import PageHeader from "@/components/PageHeader.vue";
-  import {
-    subtractStockInventory,
-    getStockInventoryListPage,
-  } from "@/api/inventoryManagement/stockInventory.js";
-  import {
-    subtractStockUnInventory,
-    getStockUninventoryListPage,
-  } from "@/api/inventoryManagement/stockUninventory.js";
+
+  import { productList as salesProductList, scanShipApply } from "@/api/salesManagement/salesLedger";
+
+  import config from "@/config";
+  import { getToken } from "@/utils/auth";
+  import { getLedgerShippingLabel } from "@/utils/salesLedgerShip";
+
   import modal from "@/plugins/modal";
 
+  import { QUALITY_TYPE, CONTRACT_KIND } from "./scanOut.constants";
+  import { useScanOutFieldRows } from "./scanOut.fields";
+  import {
+    parseOptionalNumber,
+    defaultStockedQuantityFromRow,
+    resolveQrContractKind,
+    resolveListTypeForDetail,
+    resolveContractNo,
+    buildSalesLedgerProductList,
+    buildScanShipProductList,
+    resolveScanShipLineQuantity,
+    hasAnyPositiveStockedQty,
+    resolveSubmitSceneKey,
+  } from "./scanOut.logic";
+  import { createSubmitConfig } from "./scanOut.submit";
+
   const showForm = ref(false);
-  const type = ref("qualified"); // qualified | unqualified
-  const loading = ref(false);
-  const formRef = ref(null);
 
-  const form = ref({
-    id: undefined,
-    productId: undefined,
-    productModelId: undefined,
-    productName: "",
-    model: "",
-    unit: "",
-    qualitity: 1,
-    unLockedQuantity: 0,
-    remark: "",
-  });
+  const type = ref(QUALITY_TYPE.qualified);
 
-  const formRules = {
-    qualitity: [
-      {
-        required: true,
-        type: "number",
-        message: "璇疯緭鍏ュ嚭搴撴暟閲�",
-        trigger: ["blur", "change"],
-      },
-      {
-        validator: (rule, value, callback) => {
-          if (value > form.value.unLockedQuantity) {
-            callback(new Error("涓嶈兘瓒呰繃鍙敤搴撳瓨"));
-          } else {
-            callback();
-          }
-        },
-        trigger: ["blur", "change"],
-      },
-    ],
+  const recordList = ref([]);
+
+  const expandedByIndex = ref({});
+
+  const scanContractNo = ref("");
+
+  /** 鎵爜鍚堝悓绫诲瀷锛氶攢鍞彴璐� / 閲囪喘鍙拌处 */
+  const contractKind = ref(CONTRACT_KIND.sales);
+
+  /** 浜岀淮鐮佷腑鐨勫彴璐︿富閿� id */
+  const scanLedgerId = ref(null);
+
+  const submitLoading = ref(false);
+
+  /** 閿�鍞� + 鍚堟牸锛氳蛋銆屾彁浜ゅ彂璐у鎵广�嶏紝涓嶅啀鐩存帴鍑哄簱鎴栬烦杞師鍙戣揣椤� */
+  const needScanShipFlow = computed(
+    () =>
+      showForm.value &&
+      type.value === QUALITY_TYPE.qualified &&
+      contractKind.value === CONTRACT_KIND.sales
+  );
+
+  const scanShipTypeValue = ref("璐ц溅");
+  const scanShipTypeLabel = computed(() => scanShipTypeValue.value);
+  const scanShipTypeSheetShow = ref(false);
+  const scanShipTypeActions = ref([
+    { name: "璐ц溅", value: "璐ц溅" },
+    { name: "蹇��", value: "蹇��" },
+  ]);
+  const scanShipCarNumber = ref("");
+  const scanShipExpress = ref("");
+  let nextScanShipApproverNodeId = 2;
+  const scanShipApproverNodes = ref([{ id: 1, userId: null, nickName: "" }]);
+  const scanShipFiles = ref([]);
+  const scanShipUploading = ref(false);
+
+  const scanShipUploadConfig = {
+    action: "/file/upload",
+    limit: 10,
+    fileType: ["jpg", "jpeg", "png", "gif", "webp"],
+    formType: 10,
   };
 
-  const goBack = () => {
-    if (showForm.value) {
-      showForm.value = false;
-    } else {
-      uni.navigateBack();
+  const uploadScanShipFileUrl = computed(
+    () => (config.baseUrl || "").replace(/\/$/, "") + scanShipUploadConfig.action
+  );
+
+  const onScanShipTypeSelect = item => {
+    scanShipTypeValue.value = item.name || item.value || "璐ц溅";
+    scanShipTypeSheetShow.value = false;
+    if (scanShipTypeValue.value === "璐ц溅") scanShipExpress.value = "";
+    else scanShipCarNumber.value = "";
+  };
+
+  const openScanShipTypeSheet = () => {
+    scanShipTypeSheetShow.value = true;
+  };
+
+  const addScanShipApproverNode = () => {
+    scanShipApproverNodes.value.push({
+      id: nextScanShipApproverNodeId++,
+      userId: null,
+      nickName: "",
+    });
+  };
+
+  const removeScanShipApproverNode = stepIndex => {
+    if (scanShipApproverNodes.value.length <= 1) return;
+    scanShipApproverNodes.value.splice(stepIndex, 1);
+  };
+
+  const removeScanShipApproverUser = stepIndex => {
+    const step = scanShipApproverNodes.value[stepIndex];
+    if (!step) return;
+    step.userId = null;
+    step.nickName = "";
+  };
+
+  const openScanShipContactSelect = stepIndex => {
+    uni.setStorageSync("stepIndex", stepIndex);
+    uni.navigateTo({
+      url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect?approveType=7&source=scanShip",
+    });
+  };
+
+  const handleScanShipSelectContact = data => {
+    if (data?.source !== "scanShip") return;
+    if (!needScanShipFlow.value) return;
+    const c = data?.contact;
+    if (!c) return;
+    const idx = Number(data.stepIndex);
+    const i = Number.isNaN(idx) ? 0 : idx;
+    if (i < 0 || i >= scanShipApproverNodes.value.length) return;
+    const row = scanShipApproverNodes.value[i];
+    row.userId = c.userId;
+    row.nickName = c.nickName || c.userName || "";
+  };
+
+  const scanShipFileUrl = file => {
+    const base = config.fileUrl || "";
+    const link = file?.link || file?.url || "";
+    if (link && String(link).startsWith("http")) return link;
+    if (link && link.startsWith("/")) return base + link;
+    return file?.tempFilePath || file?.path || "";
+  };
+
+  const chooseScanShipImage = () => {
+    if (scanShipFiles.value.length >= scanShipUploadConfig.limit) {
+      modal.msgError(`鏈�澶�${scanShipUploadConfig.limit}寮燻);
+      return;
     }
-  };
-
-  const cancelForm = () => {
-    showForm.value = false;
-  };
-
-  const startScan = scanType => {
-    type.value = scanType;
-    uni.scanCode({
+    uni.chooseImage({
+      count: 1,
+      sizeType: ["compressed"],
+      sourceType: ["album", "camera"],
       success: res => {
-        handleScanResult(res.result);
-      },
-      fail: err => {
-        modal.msgError("鎵爜澶辫触");
+        const path = res?.tempFilePaths?.[0];
+        const tf = res?.tempFiles?.[0] || {};
+        if (!path) return;
+        const token = getToken();
+        if (!token) {
+          modal.msgError("鏈櫥褰�");
+          return;
+        }
+        scanShipUploading.value = true;
+        uni.uploadFile({
+          url: uploadScanShipFileUrl.value,
+          filePath: path,
+          name: "file",
+          formData: { type: scanShipUploadConfig.formType },
+          header: { Authorization: `Bearer ${token}` },
+          success: up => {
+            try {
+              const body = JSON.parse(up.data || "{}");
+              if (body.code === 200 && body.data) {
+                const d = body.data;
+                scanShipFiles.value.push({
+                  tempId: d.tempId ?? d.tempFileId ?? d.id,
+                  link: d.link || d.url,
+                  url: d.url,
+                  name: d.originalFilename || d.originalName || "鍥剧墖",
+                  tempFilePath: path,
+                });
+                modal.msgSuccess("涓婁紶鎴愬姛");
+              } else {
+                modal.msgError(body.msg || "涓婁紶澶辫触");
+              }
+            } catch (e) {
+              modal.msgError("涓婁紶瑙f瀽澶辫触");
+            }
+          },
+          fail: () => modal.msgError("涓婁紶澶辫触"),
+          complete: () => {
+            scanShipUploading.value = false;
+          },
+        });
       },
     });
   };
 
-  const handleScanResult = async result => {
+  const removeScanShipFile = idx => {
+    scanShipFiles.value.splice(idx, 1);
+  };
+
+  const previewScanShipImage = idx => {
+    const urls = scanShipFiles.value.map(f => scanShipFileUrl(f)).filter(Boolean);
+    const cur = urls[idx];
+    if (urls.length && cur) uni.previewImage({ urls, current: cur });
+  };
+
+  const getScanShipTempFileIds = () =>
+    scanShipFiles.value.map(f => f.tempId).filter(id => id != null && id !== "");
+
+  const confirmScanShipApply = async () => {
+    if (scanLedgerId.value == null || scanLedgerId.value === "") {
+      modal.msgError("缂哄皯璁㈠崟淇℃伅锛岃閲嶆柊鎵爜");
+      return;
+    }
+    if (!hasEditableOutboundItems.value) {
+      modal.msgError("璇ヤ骇鍝佸凡缁忓叏閮ㄥ嚭搴�");
+      return;
+    }
+    const salesLedgerProductList = buildScanShipProductList(recordList.value);
+    if (!hasAnyPositiveStockedQty(salesLedgerProductList)) {
+      modal.msgError("褰撳墠璁㈠崟鏃犲彲鍙戣揣鏁伴噺");
+      return;
+    }
+    if (scanShipTypeValue.value === "璐ц溅" && !String(scanShipCarNumber.value || "").trim()) {
+      modal.msgError("璇疯緭鍏ヨ溅鐗屽彿");
+      return;
+    }
+    if (scanShipTypeValue.value === "蹇��" && !String(scanShipExpress.value || "").trim()) {
+      modal.msgError("璇疯緭鍏ュ揩閫掑崟鍙�");
+      return;
+    }
+    const hasEmptyStep = scanShipApproverNodes.value.some(s => !s?.userId);
+    if (hasEmptyStep) {
+      modal.msgError("璇蜂负姣忎釜瀹℃壒姝ラ閫夋嫨瀹℃壒浜�");
+      return;
+    }
+    if (scanShipUploading.value) {
+      modal.msgError("闄勪欢涓婁紶涓紝璇风◢鍊�");
+      return;
+    }
+    const approveUserIds = scanShipApproverNodes.value
+      .map(s => s.userId)
+      .filter(id => id != null && id !== "")
+      .join(",");
+    const payload = {
+      salesLedgerId: scanLedgerId.value,
+      salesLedgerProductList,
+      approveUserIds,
+      shipType: scanShipTypeValue.value,
+      shippingCarNumber:
+        scanShipTypeValue.value === "璐ц溅" ? String(scanShipCarNumber.value || "").trim() : "",
+      expressNumber:
+        scanShipTypeValue.value === "蹇��" ? String(scanShipExpress.value || "").trim() : "",
+      tempFileIds: getScanShipTempFileIds(),
+    };
     try {
-      // 瑙f瀽浜岀淮鐮佹暟鎹�
-      const scanData = JSON.parse(result);
-      if (!scanData.id) {
-        modal.msgError("鏃犳晥鐨勪簩缁寸爜鏁版嵁");
-        return;
+      submitLoading.value = true;
+      modal.loading("鎻愪氦涓�...");
+      const res = await scanShipApply(payload);
+      modal.closeLoading();
+      if (res.code === 200) {
+        modal.msgSuccess(res.msg || "鍙戣揣瀹℃壒宸插彂璧�");
+        resetDetailView();
+      } else {
+        modal.msgError(res.msg || "鎻愪氦澶辫触");
+      }
+    } catch (e) {
+      modal.closeLoading();
+      console.error(e);
+    } finally {
+      submitLoading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    uni.$on("selectContact", handleScanShipSelectContact);
+  });
+  onUnmounted(() => {
+    uni.$off("selectContact", handleScanShipSelectContact);
+  });
+  const isSalesQualifiedOutboundQtyLocked = computed(
+    () =>
+      type.value === QUALITY_TYPE.qualified &&
+      contractKind.value === CONTRACT_KIND.sales
+  );
+  const submitConfigByScene = createSubmitConfig(scanLedgerId);
+
+  const cardTitleMain = computed(() => {
+
+    const no = scanContractNo.value?.trim();
+
+    return no || "鈥�";
+
+  });
+
+
+
+  const isCardExpanded = idx => {
+
+    if (recordList.value.length <= 1) return true;
+
+    return !!expandedByIndex.value[idx];
+
+  };
+
+
+
+  const toggleCardDetail = idx => {
+
+    if (recordList.value.length <= 1) return;
+
+    expandedByIndex.value = {
+
+      ...expandedByIndex.value,
+
+      [idx]: !expandedByIndex.value[idx],
+
+    };
+
+  };
+
+
+
+  const { detailFieldRows: rawDetailFieldRows, summaryFieldRows: rawSummaryFieldRows } = useScanOutFieldRows(
+    contractKind,
+    "outbound"
+  );
+  const shouldShowOutboundQuantityField = key => {
+    if (type.value === QUALITY_TYPE.qualified)
+      return key !== "unqualifiedShippedQuantity" && key !== "unqualifiedStockedQuantity";
+    if (type.value === QUALITY_TYPE.unqualified) return key !== "shippedQuantity" && key !== "remainingShippedQuantity";
+    return true;
+  };
+  const detailFieldRows = computed(() =>
+    rawDetailFieldRows.value.filter(row => shouldShowOutboundQuantityField(row.key))
+  );
+  const summaryFieldRows = computed(() =>
+    rawSummaryFieldRows.value.filter(row => shouldShowOutboundQuantityField(row.key))
+  );
+
+
+
+  const emptyDash = v => {
+
+    if (v === null || v === undefined || v === "") return "-";
+
+    return v;
+
+  };
+
+
+
+  const formatApproveStatus = row => {
+
+    const a = row.approveStatus;
+
+    const noShipInfo = !row.shippingDate || !row.shippingCarNumber;
+
+    const hasShipInfo = !!(row.shippingDate || row.shippingCarNumber);
+
+    if ((a === 1 || a === "1") && noShipInfo) return "鍏呰冻";
+
+    if ((a === 0 || a === "0") && hasShipInfo) return "宸插嚭搴�";
+
+    return "涓嶈冻";
+
+  };
+
+
+
+  const formatProductStockStatus = v => {
+
+    if (v == 1) return "閮ㄥ垎鍏ュ簱";
+
+    if (v == 2) return "宸插叆搴�";
+
+    if (v == 0) return "鏈嚭搴�";
+
+    return "涓嶈冻";
+
+  };
+
+
+
+  const approveStatusTagType = row => {
+
+    const a = row.approveStatus;
+
+    const noShipInfo = !row.shippingDate || !row.shippingCarNumber;
+
+    const hasShipInfo = !!(row.shippingDate || row.shippingCarNumber);
+
+    if ((a === 1 || a === "1") && noShipInfo) return "success";
+
+    if ((a === 0 || a === "0") && hasShipInfo) return "success";
+
+    return "error";
+
+  };
+
+
+
+  const productStockStatusTagType = v => {
+
+    if (v == 1) return "warning";
+
+    if (v == 2) return "success";
+
+    if (v == 0) return "info";
+
+    return "error";
+
+  };
+
+
+
+  const formatHeavyBox = v => {
+
+    if (v === 1 || v === true || v === "1") return "鏄�";
+
+    if (v === 0 || v === false || v === "0") return "鍚�";
+
+    return emptyDash(v);
+
+  };
+
+
+
+  const parseOptionalNumberLocal = raw => {
+
+    if (raw === null || raw === undefined || raw === "") return null;
+
+    const n = Number(String(raw).trim());
+
+    return Number.isNaN(n) ? null : n;
+
+  };
+
+
+
+  const parseRemainingQuantityLocal = row => {
+
+    const remRaw =
+
+      row?.remainingQuantity ??
+
+      row?.remaining_quantity ??
+
+      row?.remainQuantity ??
+
+      row?.remain_quantity;
+
+    return parseOptionalNumberLocal(remRaw);
+
+  };
+
+
+
+  const defaultStockedQuantityFromRowLocal = row => {
+
+    const rem = parseRemainingQuantityLocal(row);
+
+    if (rem !== null) return String(Math.max(0, rem));
+
+    const avail = parseOptionalNumberLocal(
+
+      row?.availableQuality ?? row?.availableQuantity
+
+    );
+
+    if (avail !== null) return String(Math.max(0, avail));
+
+    const qty = parseOptionalNumberLocal(row?.quantity);
+
+    if (qty !== null) return String(Math.max(0, qty));
+
+    return "0";
+
+  };
+
+
+
+  const onStockedQtyBlur = item => {
+
+    const raw = item.operateQuantity;
+
+    if (raw === null || raw === undefined || String(raw).trim() === "") {
+
+      item.operateQuantity = "0";
+
+      return;
+
+    }
+
+    const n = Number(String(raw).trim());
+
+    if (Number.isNaN(n)) {
+      if (type.value === QUALITY_TYPE.unqualified) {
+        const unqualifiedInbound = parseOptionalNumber(item.unqualifiedStockedQuantity) ?? 0;
+        const unqualifiedOutbound = parseOptionalNumber(item.unqualifiedShippedQuantity) ?? 0;
+        item.operateQuantity = String(Math.max(0, unqualifiedInbound - unqualifiedOutbound));
+      } else {
+        item.operateQuantity = defaultStockedQuantityFromRow(item, "outbound");
       }
 
-      // 鑾峰彇瀹炴椂搴撳瓨璇︽儏
-      modal.loading("鑾峰彇浜у搧搴撳瓨璇︽儏...");
-      const apiCall =
-        type.value === "qualified"
-          ? getStockInventoryListPage
-          : getStockUninventoryListPage;
+      return;
 
-      const res = await apiCall({ productModelId: scanData.id });
+    }
+
+    item.operateQuantity = String(Math.max(0, n));
+
+  };
+
+  const parseUnqualifiedInboundQty = item => {
+    return (
+      parseOptionalNumber(
+        item?.unqualifiedStockedQuantity ??
+          item?.unQualifiedStockedQuantity ??
+          item?.unqualifiedStockedQty ??
+          item?.unqualifiedInboundQuantity
+      ) ?? 0
+    );
+  };
+
+  const parseUnqualifiedOutboundQty = item => {
+    return (
+      parseOptionalNumber(
+        item?.unqualifiedShippedQuantity ??
+          item?.unQualifiedShippedQuantity ??
+          item?.unqualifiedShippedQty ??
+          item?.unqualifiedOutboundQuantity
+      ) ?? 0
+    );
+  };
+
+  const isFullyOutbound = item => {
+    if (type.value === QUALITY_TYPE.unqualified) {
+      return parseUnqualifiedInboundQty(item) - parseUnqualifiedOutboundQty(item) <= 0;
+    }
+    const remaining = parseOptionalNumber(item?.remainingShippedQuantity);
+    if (remaining !== null) return remaining <= 0;
+    const fallback = parseOptionalNumber(defaultStockedQuantityFromRow(item, "outbound"));
+    return fallback !== null ? fallback <= 0 : false;
+  };
+
+  const hasEditableOutboundItems = computed(() => {
+    if (!recordList.value?.length) return false;
+    return recordList.value.some(item => !isFullyOutbound(item));
+  });
+
+
+
+  const formatCell = (item, row, idx) => {
+
+    if (row.key === "index") {
+
+      const v = item.index;
+
+      if (v !== null && v !== undefined && v !== "") return String(v);
+
+      return String(idx + 1);
+
+    }
+
+    if (row.key === "approveStatus") return formatApproveStatus(item);
+
+    if (row.key === "productStockStatus")
+
+      return formatProductStockStatus(item.productStockStatus);
+
+    if (row.key === "ledgerShippingStatus") return getLedgerShippingLabel(item);
+
+    if (row.key === "heavyBox") return formatHeavyBox(item.heavyBox);
+
+    if (row.key === "remainingQuantity") {
+
+      const v = item.remainingQuantity;
+
+      return emptyDash(v);
+
+    }
+    if (row.key === "remainingShippedQuantity") {
+      const v = item.remainingShippedQuantity;
+      return emptyDash(v);
+    }
+    if (row.key === "shippedQuantity") {
+      const v = item.shippedQuantity;
+      return emptyDash(v);
+    }
+    if (row.key === "unqualifiedShippedQuantity") {
+      const v =
+        item.unqualifiedShippedQuantity ??
+        item.unQualifiedShippedQuantity ??
+        item.unqualifiedShippedQty ??
+        item.unqualifiedOutboundQuantity;
+      return emptyDash(v);
+    }
+    if (row.key === "stockedQuantity") {
+      const v = item.stockedQuantity;
+      return emptyDash(v);
+    }
+    if (row.key === "unqualifiedStockedQuantity") {
+      const v = item.unqualifiedStockedQuantity;
+      return emptyDash(v);
+    }
+
+    if (row.key === "availableQuality") {
+
+      const v = item.availableQuality ?? item.availableQuantity;
+
+      return emptyDash(v);
+
+    }
+
+    if (row.key === "returnQuality") {
+
+      const v = item.returnQuality ?? item.returnQuantity;
+
+      return emptyDash(v);
+
+    }
+
+    return emptyDash(item[row.key]);
+
+  };
+
+
+
+  const resetDetailView = () => {
+
+    showForm.value = false;
+
+    scanContractNo.value = "";
+
+    contractKind.value = CONTRACT_KIND.sales;
+
+    scanLedgerId.value = null;
+
+    expandedByIndex.value = {};
+
+    recordList.value = [];
+
+    scanShipTypeValue.value = "璐ц溅";
+    scanShipCarNumber.value = "";
+    scanShipExpress.value = "";
+    nextScanShipApproverNodeId = 2;
+    scanShipApproverNodes.value = [{ id: 1, userId: null, nickName: "" }];
+    scanShipFiles.value = [];
+    scanShipUploading.value = false;
+    scanShipTypeSheetShow.value = false;
+
+  };
+
+
+
+  /** 缁勮鎻愪氦鐢ㄧ殑浜у搧琛岋紙鍚暟鍊煎寲鍑哄簱鏁伴噺 stockedQuantity锛� */
+  const buildSalesLedgerProductListLocal = () => {
+
+    return recordList.value.map(item => {
+
+      const n = parseOptionalNumber(item.stockedQuantity);
+
+      const qty = n !== null && !Number.isNaN(n) ? Math.max(0, n) : 0;
+
+      const { stockedQuantity: _sq, ...rest } = item;
+
+      return { ...rest, stockedQuantity: qty };
+
+    });
+
+  };
+
+
+
+  const confirmOutbound = async () => {
+
+    if (scanLedgerId.value == null || scanLedgerId.value === "") {
+
+      modal.msgError("缂哄皯璁㈠崟淇℃伅锛岃閲嶆柊鎵爜");
+
+      return;
+
+    }
+    if (!hasEditableOutboundItems.value) {
+      modal.msgError("璇ヤ骇鍝佸凡缁忓叏閮ㄥ嚭搴�");
+      return;
+    }
+
+    const salesLedgerProductList = buildSalesLedgerProductList(recordList.value);
+
+    if (!hasAnyPositiveStockedQty(salesLedgerProductList)) {
+
+      modal.msgError("璇疯嚦灏戝~鍐欎竴琛屽ぇ浜� 0 鐨勫嚭搴撴暟閲�");
+
+      return;
+
+    }
+
+    const sceneKey = resolveSubmitSceneKey(contractKind.value, type.value);
+
+    const currentSubmitConfig = submitConfigByScene[sceneKey];
+
+    if (!currentSubmitConfig) {
+      modal.msgError("鏆備笉鏀寔褰撳墠鍑哄簱鍦烘櫙");
+      return;
+    }
+
+    const runApi = currentSubmitConfig.runApi;
+    const payload = currentSubmitConfig.payloadBuilder(salesLedgerProductList);
+
+    try {
+
+      submitLoading.value = true;
+
+      modal.loading("鎻愪氦涓�...");
+
+      const res = await runApi(payload);
+
       modal.closeLoading();
 
-      if (res.code === 200 && res.data.records && res.data.records.length > 0) {
-        const detail = res.data.records[0];
-        form.value.id = detail.id;
-        form.value.productId = detail.productId;
-        form.value.productName = detail.productName;
-        form.value.productModelId = detail.productModelId;
-        form.value.model = detail.model;
-        form.value.unit = detail.unit;
-        form.value.unLockedQuantity = detail.unLockedQuantity;
-        form.value.qualitity = 1;
-        form.value.remark = "";
+      if (res.code === 200) {
 
-        if (form.value.unLockedQuantity <= 0) {
-          modal.msgError("褰撳墠搴撳瓨涓嶈冻锛屾棤娉曞嚭搴�");
-          return;
-        }
+        modal.msgSuccess("鎻愪氦鎴愬姛");
+
+        resetDetailView();
+
+      } else {
+
+        modal.msgError(res.msg || "鎻愪氦澶辫触");
+
+      }
+
+    } catch (e) {
+
+      modal.closeLoading();
+
+      console.error("鎵爜鍑哄簱鎻愪氦澶辫触", e);
+
+    } finally {
+
+      submitLoading.value = false;
+
+    }
+
+  };
+
+  const goBack = () => {
+
+    if (showForm.value) {
+
+      resetDetailView();
+
+    } else {
+
+      uni.navigateBack();
+
+    }
+
+  };
+
+
+
+  const cancelForm = () => {
+
+    resetDetailView();
+
+  };
+
+
+
+  const startScan = scanType => {
+
+    type.value = scanType;
+
+    uni.scanCode({
+
+      success: res => {
+
+        handleScanResult(res.result);
+
+      },
+
+      fail: () => {
+
+        modal.msgError("鎵爜澶辫触");
+
+      },
+
+    });
+
+  };
+
+
+
+  /** 鏍规嵁浜岀淮鐮� JSON 鍒ゆ柇閿�鍞�(XS)/閲囪喘(CG)锛屼笌鎺ュ彛 type锛�1 閿�鍞��2 閲囪喘 瀵瑰簲 */
+  const resolveQrContractKindLocal = scanData => {
+
+    const t = scanData?.type;
+
+    const ts =
+
+      t !== null && t !== undefined && t !== ""
+
+        ? String(t).trim().toUpperCase()
+
+        : "";
+
+    if (ts === "CG" || t === 2 || t === "2") return CONTRACT_KIND.purchase;
+
+    if (ts === "XS" || t === 1 || t === "1") return CONTRACT_KIND.sales;
+
+    const pc = scanData?.purchaseContractNumber;
+
+    const sc = scanData?.salesContractNo;
+
+    if (
+
+      pc != null &&
+
+      String(pc).trim() !== "" &&
+
+      (sc == null || String(sc).trim() === "")
+
+    )
+
+      return CONTRACT_KIND.purchase;
+
+    return CONTRACT_KIND.sales;
+
+  };
+
+  const handleScanResult = async result => {
+
+    try {
+
+      const scanData = JSON.parse(result);
+
+      if (!scanData.id) {
+
+        modal.msgError("鏃犳晥鐨勪簩缁寸爜鏁版嵁");
+
+        return;
+
+      }
+
+      const kind = resolveQrContractKind(scanData);
+
+      contractKind.value = kind;
+
+      scanLedgerId.value = scanData.id;
+
+      scanContractNo.value = resolveContractNo(scanData, kind);
+
+      const listType = resolveListTypeForDetail(kind);
+
+      modal.loading("鑾峰彇浜у搧搴撳瓨璇︽儏...");
+
+      const res = await salesProductList({
+
+        salesLedgerId: scanData.id,
+
+        type: listType,
+
+      });
+
+      modal.closeLoading();
+
+
+
+      if (res.code === 200 && res.data && res.data.length > 0) {
+
+        recordList.value = res.data.map(row => ({
+
+          ...row,
+          unqualifiedShippedQuantity:
+            row.unqualifiedShippedQuantity ??
+            row.unQualifiedShippedQuantity ??
+            row.unqualifiedShippedQty ??
+            row.unqualifiedOutboundQuantity,
+          unqualifiedStockedQuantity:
+            row.unqualifiedStockedQuantity ??
+            row.unQualifiedStockedQuantity ??
+            row.unqualifiedStockedQty ??
+            row.unqualifiedInboundQuantity,
+          operateQuantity:
+            type.value === QUALITY_TYPE.unqualified
+              ? String(
+                  Math.max(
+                    0,
+                    (parseOptionalNumber(
+                      row.unqualifiedStockedQuantity ??
+                        row.unQualifiedStockedQuantity ??
+                        row.unqualifiedStockedQty ??
+                        row.unqualifiedInboundQuantity
+                    ) ?? 0) -
+                      (parseOptionalNumber(
+                        row.unqualifiedShippedQuantity ??
+                          row.unQualifiedShippedQuantity ??
+                          row.unqualifiedShippedQty ??
+                          row.unqualifiedOutboundQuantity
+                      ) ?? 0)
+                  )
+                )
+              : defaultStockedQuantityFromRow(row, "outbound"),
+
+        }));
+
+        expandedByIndex.value = {};
 
         showForm.value = true;
+
       } else {
-        modal.msgError("鏈壘鍒拌浜у搧鍨嬪彿鐨勫簱瀛樿褰�");
+
+        scanLedgerId.value = null;
+
+        modal.msgError("鏈煡璇㈠埌鏄庣粏鏁版嵁");
+
       }
+
     } catch (error) {
+
       modal.closeLoading();
+
+      scanLedgerId.value = null;
+
       console.error("澶勭悊鎵爜缁撴灉澶辫触", error);
+
       modal.msgError("鎵爜澶勭悊澶辫触锛岃閲嶈瘯");
+
     }
+
   };
 
-  const handleSubmit = async () => {
-    try {
-      const valid = await formRef.value.validate();
-      if (!valid) return;
-
-      loading.value = true;
-      const apiCall =
-        type.value === "qualified"
-          ? subtractStockInventory
-          : subtractStockUnInventory;
-
-      const res = await apiCall(form.value);
-      if (res.code === 200) {
-        modal.msgSuccess("鍑哄簱鎴愬姛");
-        setTimeout(() => {
-          showForm.value = false;
-        }, 1500);
-      }
-    } catch (error) {
-      console.error("鎻愪氦澶辫触", error);
-    } finally {
-      loading.value = false;
-    }
-  };
 </script>
 
+
+
 <style scoped lang="scss">
+
   .scan-container {
+
     min-height: 100vh;
+
     background-color: #f5f7fa;
+
   }
+
+
 
   .module-selector {
+
     display: flex;
+
     flex-direction: column;
+
     padding: 40rpx;
+
     height: 80vh;
+
     justify-content: center;
+
   }
+
+
 
   .module-card {
+
     display: flex;
+
     align-items: center;
+
     background-color: #fff;
+
     padding: 80rpx 50rpx;
+
     border-radius: 32rpx;
+
     box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
+
     margin-bottom: 50rpx;
+
     transition: all 0.3s ease;
+
     border: 2rpx solid transparent;
 
+
+
     &:active {
+
       transform: scale(0.98);
+
       background-color: #f9f9f9;
+
     }
+
   }
+
+
 
   .module-icon {
+
     width: 140rpx;
+
     height: 140rpx;
+
+    min-width: 140rpx;
+
+    min-height: 140rpx;
+
+    flex-shrink: 0;
+
     border-radius: 32rpx;
+
     display: flex;
+
     justify-content: center;
+
     align-items: center;
+
     margin-right: 40rpx;
 
+
+
     &.qualified {
+
       background: linear-gradient(135deg, #52c41a, #73d13d);
+
       box-shadow: 0 10rpx 20rpx rgba(82, 196, 26, 0.2);
+
     }
+
+
 
     &.unqualified {
+
       background: linear-gradient(135deg, #ff4d4f, #ff7875);
+
       box-shadow: 0 10rpx 20rpx rgba(255, 77, 79, 0.2);
+
     }
+
   }
+
+
 
   .module-info {
+
     display: flex;
+
     flex-direction: column;
+
+    flex: 1;
+
+    min-width: 0;
+
   }
+
+
 
   .module-label {
+
     font-size: 40rpx;
+
     font-weight: 700;
+
     color: #1a1a1a;
+
     margin-bottom: 12rpx;
+
   }
+
+
 
   .module-desc {
+
     font-size: 28rpx;
+
     color: #999;
+
   }
 
-  .form-content {
+
+
+  .detail-scroll {
+
+    max-height: calc(100vh - 120rpx);
+
+    box-sizing: border-box;
+
+  }
+
+
+
+  .detail-card {
+
     background-color: #fff;
+
     margin: 20rpx;
-    padding: 30rpx;
+
+    padding: 28rpx;
+
     border-radius: 16rpx;
+
+    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+
   }
 
-  .limit-tip {
-    font-size: 24rpx;
-    color: #999;
-    margin-left: 20rpx;
+
+
+  .detail-card-title {
+
+    font-size: 30rpx;
+
+    font-weight: 600;
+
+    color: #333;
+
+    margin-bottom: 20rpx;
+
+    padding-bottom: 16rpx;
+
+    border-bottom: 1rpx solid #eee;
+
   }
+
+
+
+  .detail-card-title--collapsible {
+
+    display: flex;
+
+    flex-direction: row;
+
+    justify-content: space-between;
+
+    align-items: center;
+
+  }
+
+
+
+  .detail-card-title--collapsible.is-collapsed {
+
+    margin-bottom: 0;
+
+    padding-bottom: 0;
+
+    border-bottom: none;
+
+  }
+
+
+
+  .detail-card-title-text {
+
+    flex: 1;
+
+    min-width: 0;
+
+    margin-right: 16rpx;
+
+    display: flex;
+
+    flex-wrap: wrap;
+
+    align-items: baseline;
+
+  }
+
+
+
+  .detail-card-title-no {
+
+    word-break: break-all;
+
+    line-height: 1.4;
+
+  }
+
+
+
+  .detail-card-title-seq {
+
+    flex-shrink: 0;
+
+    font-size: 26rpx;
+
+    font-weight: 500;
+
+    color: #888;
+
+    margin-left: 8rpx;
+
+  }
+
+
+
+  .detail-card-body {
+
+    padding-top: 4rpx;
+
+  }
+
+
+
+  .detail-card-summary {
+
+    padding-top: 8rpx;
+
+  }
+
+
+
+  .kv-row--summary {
+
+    padding: 12rpx 0;
+
+    font-size: 26rpx;
+
+  }
+
+
+
+  .summary-tip {
+
+    display: block;
+
+    font-size: 24rpx;
+
+    color: #999;
+
+    text-align: center;
+
+    padding: 20rpx 0 8rpx;
+
+  }
+
+
+
+  .kv-row {
+
+    display: flex;
+
+    align-items: flex-start;
+
+    padding: 16rpx 0;
+
+    border-bottom: 1rpx solid #f0f0f0;
+
+    font-size: 28rpx;
+
+  }
+
+
+
+  .kv-label {
+
+    flex-shrink: 0;
+
+    width: 220rpx;
+
+    color: #888;
+
+    line-height: 1.5;
+
+  }
+
+
+
+  .kv-value {
+
+    flex: 1;
+
+    color: #1a1a1a;
+
+    line-height: 1.5;
+
+    word-break: break-all;
+
+    text-align: right;
+
+  }
+
+
+
+  .kv-value--tag {
+
+    display: flex;
+
+    justify-content: flex-end;
+
+    align-items: center;
+
+  }
+
+
+
+  .stocked-qty-block {
+
+    margin-top: 8rpx;
+
+    padding-top: 8rpx;
+
+    border-top: 1rpx solid #f0f0f0;
+
+  }
+
+
+
+  .stocked-qty-row {
+
+    border-bottom: none;
+
+    align-items: center;
+
+  }
+
+
+
+  .stocked-qty-input-wrap {
+
+    min-width: 0;
+
+  }
+
+
+
+  /* 鍙戣揣淇℃伅锛氫笌鎵爜鍏ュ簱瀹℃壒鍖恒�佸彴璐﹀彂璐ч〉琛ㄥ崟椋庢牸涓�鑷� */
+
+  .scan-ship-card {
+
+    background-color: #fff;
+
+    margin: 20rpx;
+
+    padding: 0 0 24rpx;
+
+    border-radius: 16rpx;
+
+    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+
+    overflow: hidden;
+
+  }
+
+
+
+  .scan-ship-title {
+
+    font-size: 30rpx;
+
+    font-weight: 600;
+
+    color: #333;
+
+    padding: 28rpx 28rpx 20rpx;
+
+    border-bottom: 1rpx solid #eee;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-form) {
+
+    background: transparent;
+
+    margin: 0;
+
+    padding: 0;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-form-item) {
+
+    background: #fff;
+
+    padding: 0 28rpx;
+
+    border-bottom: 1rpx solid #f0f0f0;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-form-item:last-child) {
+
+    border-bottom: none;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-form-item__body) {
+
+    padding: 24rpx 0;
+
+    min-height: 88rpx;
+
+    display: flex;
+
+    align-items: center;
+
+    border: none;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-form-item__label) {
+
+    font-size: 28rpx;
+
+    color: #888;
+
+    font-weight: 400;
+
+    flex-shrink: 0;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-form-item__content) {
+
+    flex: 1;
+
+    min-width: 0;
+
+    display: flex;
+
+    justify-content: flex-end;
+
+    align-items: center;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-input),
+
+  .scan-ship-card :deep(.u-input input),
+
+  .scan-ship-card :deep(.u-input__content__field-wrapper__field),
+
+  .scan-ship-card :deep(.u-input__input) {
+
+    border: none !important;
+
+    box-shadow: none !important;
+
+    background: transparent !important;
+
+    font-size: 28rpx;
+
+    color: #1a1a1a;
+
+    text-align: right;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-input input[readonly]),
+
+  .scan-ship-card :deep(.u-input__input[readonly]) {
+
+    color: #666;
+
+  }
+
+
+
+  .scan-ship-card :deep(.scan-ship-type-form-item .u-form-item__content) {
+
+    justify-content: flex-start;
+
+    align-items: center;
+
+  }
+
+
+
+  .scan-ship-type-trigger {
+
+    display: flex;
+
+    flex-direction: row;
+
+    align-items: center;
+
+    width: 100%;
+
+    min-height: 72rpx;
+
+    padding: 8rpx 0;
+
+    box-sizing: border-box;
+
+  }
+
+
+
+  .scan-ship-type-trigger :deep(.u-input) {
+
+    flex: 1;
+
+    min-width: 0;
+
+  }
+
+
+
+  .scan-ship-type-trigger :deep(input),
+
+  .scan-ship-type-trigger :deep(.u-input__input) {
+
+    pointer-events: none;
+
+  }
+
+
+
+  .scan-ship-card :deep(.u-input input::placeholder),
+
+  .scan-ship-card :deep(.u-input__input::placeholder) {
+
+    color: #c0c4cc;
+
+    font-size: 28rpx;
+
+  }
+
+
+
+  .scan-ship-card :deep(.scan-ship-type-form-item .u-input input),
+
+  .scan-ship-card :deep(.scan-ship-type-form-item .u-input__input) {
+
+    text-align: left;
+
+  }
+
+
+
+  .scan-ship-card .approval-process {
+
+    background: transparent;
+
+    margin: 0;
+
+    padding: 24rpx 28rpx 8rpx;
+
+    border-radius: 0;
+
+    box-shadow: none;
+
+    border-top: 1rpx solid #f0f0f0;
+
+    margin-top: 8rpx;
+
+  }
+
+
+
+  .scan-ship-card .approval-header {
+
+    margin-bottom: 16rpx;
+
+  }
+
+
+
+  .scan-ship-card .approval-title {
+
+    font-size: 30rpx;
+
+    font-weight: 600;
+
+    color: #333;
+
+    display: block;
+
+  }
+
+
+
+  .scan-ship-card .approval-desc {
+
+    font-size: 24rpx;
+
+    color: #999;
+
+    margin-top: 6rpx;
+
+  }
+
+
+
+  .scan-ship-card .approval-step {
+
+    margin-bottom: 18rpx;
+
+  }
+
+
+
+  .scan-ship-card .step-title text {
+
+    font-size: 24rpx;
+
+    color: #666;
+
+  }
+
+
+
+  .scan-ship-card .approver-container {
+
+    display: flex;
+
+    align-items: center;
+
+    margin-top: 10rpx;
+
+  }
+
+
+
+  .scan-ship-card .approver-item {
+
+    width: 100%;
+
+    display: flex;
+
+    align-items: center;
+
+    gap: 12rpx;
+
+    padding: 12rpx 0;
+
+  }
+
+
+
+  .scan-ship-card .approver-avatar {
+
+    width: 64rpx;
+
+    height: 64rpx;
+
+    border-radius: 50%;
+
+    background: #f3f4f6;
+
+    border: 2rpx solid #e5e7eb;
+
+    display: flex;
+
+    align-items: center;
+
+    justify-content: center;
+
+  }
+
+
+
+  .scan-ship-card .avatar-text {
+
+    font-size: 24rpx;
+
+    color: #374151;
+
+    font-weight: 600;
+
+  }
+
+
+
+  .scan-ship-card .approver-info {
+
+    flex: 1;
+
+  }
+
+
+
+  .scan-ship-card .approver-name {
+
+    font-size: 28rpx;
+
+    color: #333;
+
+  }
+
+
+
+  .scan-ship-card .delete-approver-btn {
+
+    font-size: 32rpx;
+
+    color: #ff4d4f;
+
+    padding: 0 8rpx;
+
+  }
+
+
+
+  .scan-ship-card .add-approver-btn {
+
+    display: flex;
+
+    align-items: center;
+
+    gap: 10rpx;
+
+    color: #3b82f6;
+
+    padding: 10rpx 0;
+
+  }
+
+
+
+  .scan-ship-card .add-circle {
+
+    width: 52rpx;
+
+    height: 52rpx;
+
+    border: 2rpx dashed #a0aec0;
+
+    border-radius: 50%;
+
+    color: #6b7280;
+
+    display: flex;
+
+    align-items: center;
+
+    justify-content: center;
+
+    font-size: 34rpx;
+
+    line-height: 1;
+
+  }
+
+
+
+  .scan-ship-card .add-label {
+
+    font-size: 26rpx;
+
+  }
+
+
+
+  .scan-ship-card .delete-step-btn {
+
+    color: #ff4d4f;
+
+    font-size: 24rpx;
+
+    margin-top: 8rpx;
+
+  }
+
+
+
+  .scan-ship-card .add-step-btn {
+
+    margin-top: 8rpx;
+
+  }
+
+
+
+  .scan-ship-subtitle {
+
+    display: block;
+
+    font-size: 24rpx;
+
+    color: #666;
+
+    margin-bottom: 16rpx;
+
+  }
+
+
+
+  .scan-ship-files {
+
+    padding: 28rpx 28rpx 0;
+
+    border-top: 1rpx solid #f0f0f0;
+
+    margin-top: 16rpx;
+
+  }
+
+
+
+  .scan-ship-files :deep(.scan-ship-add-img-btn) {
+
+    width: 100% !important;
+
+    margin-top: 8rpx;
+
+    border-radius: 16rpx !important;
+
+    min-height: 88rpx;
+
+  }
+
+
+
+  .scan-ship-file-grid {
+
+    display: flex;
+
+    flex-wrap: wrap;
+
+    gap: 24rpx;
+
+    margin-top: 24rpx;
+
+  }
+
+
+
+  .scan-ship-thumb-wrap {
+
+    position: relative;
+
+    width: calc(50% - 12rpx);
+
+    min-width: 200rpx;
+
+    padding: 16rpx;
+
+    box-sizing: border-box;
+
+    background: #fff;
+
+    border-radius: 16rpx;
+
+    border: 1rpx solid #e9ecef;
+
+    box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.05);
+
+  }
+
+
+
+  .scan-ship-thumb {
+
+    width: 100%;
+
+    height: 200rpx;
+
+    border-radius: 12rpx;
+
+    display: block;
+
+    border: 2rpx solid #f0f0f0;
+
+    background: #f5f5f5;
+
+  }
+
+
+
+  .scan-ship-thumb-del {
+
+    position: absolute;
+
+    top: 8rpx;
+
+    right: 8rpx;
+
+    width: 44rpx;
+
+    height: 44rpx;
+
+    background: #ff4757;
+
+    color: #fff;
+
+    font-size: 28rpx;
+
+    line-height: 44rpx;
+
+    text-align: center;
+
+    border-radius: 50%;
+
+    box-shadow: 0 4rpx 8rpx rgba(255, 71, 87, 0.35);
+
+  }
+
+
+
+  .scan-ship-qty-readonly {
+
+    font-size: 30rpx;
+
+    font-weight: 600;
+
+    color: #1a1a1a;
+
+  }
+
+  .scan-ship-qty-hint {
+
+    display: block;
+
+    font-size: 24rpx;
+
+    color: #999;
+
+    padding: 8rpx 0 0;
+
+    line-height: 1.4;
+
+  }
+
+
 
   .footer-btns {
-    margin-top: 60rpx;
+
     display: flex;
+
     justify-content: space-between;
-    padding-bottom: 40rpx;
+
+    align-items: center;
+
+    gap: 24rpx;
+
+    padding: 20rpx 40rpx 60rpx;
+
   }
 
-  .cancel-btn {
-    width: 30%;
+
+
+  .footer-cancel-btn {
+
+    flex: 1;
+
     background-color: #f5f5f5;
+
     color: #666;
+
     border: none;
+
   }
 
-  .save-btn {
-    width: 65%;
+
+
+  .footer-confirm-btn {
+
+    flex: 1;
+
     background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
+
     color: #fff;
+
     border: none;
+
   }
+
 </style>
+

--
Gitblit v1.9.3