Pārlūkot izejas kodu

feat:优化常用领料和指纹录入
- 常用领料统一卡片样式
- 指纹录入去除账号验证

liuyanpeng 5 mēneši atpakaļ
vecāks
revīzija
861aecb5bd
2 mainītis faili ar 645 papildinājumiem un 119 dzēšanām
  1. 85 106
      src/Fingerprint/FingerprintEnroll.vue
  2. 560 13
      src/stock/RegularRequisition.vue

+ 85 - 106
src/Fingerprint/FingerprintEnroll.vue

@@ -17,42 +17,8 @@
     <main class="main-content">
       <!-- 步骤内容 -->
       <div class="step-container">
-        <!-- 第一步输入账号 -->
-        <div v-if="currentStep === 1" class="step-card">
-          <div class="card-header">
-            <div class="step-icon">
-              <i class="fas fa-user" />
-            </div>
-            <div>
-              <h2 class="step-title">身份验证</h2>
-              <p class="step-desc">请输入登录账号进行身份验证</p>
-            </div>
-          </div>
-
-          <div class="input-section">
-            <div class="input-wrapper">
-              <label class="input-label">登录账号</label>
-              <input
-                v-model="jobNo"
-                type="text"
-                placeholder="请输入登录账号"
-                class="dark-input"
-                @keyup.enter="handleNextStep"
-              />
-            </div>
-
-            <button
-              class="submit-btn"
-              :disabled="checking"
-              @click="handleNextStep"
-            >
-              {{ checking ? '验证中...' : '验证并继续' }}
-            </button>
-          </div>
-        </div>
-
-        <!-- 第二步 指纹录入 -->
-        <div v-else class="step-card step-two">
+        <!-- 指纹录入 -->
+        <div class="step-card step-two">
           <!-- 设备状态 - 右上角 -->
           <div class="device-status-top" @click="handleDeviceClick">
             <div class="status-indicator" :class="{ connected: isConnected }">
@@ -70,7 +36,7 @@
             <div class="header-content">
               <h2 class="step-title">指纹录入</h2>
               <p class="step-desc">
-                登录账号:<span class="job-highlight">{{ jobNo }}</span>
+                登录人:<span class="job-highlight">{{ loginName }}</span>
               </p>
             </div>
           </div>
@@ -141,22 +107,20 @@
         </div>
       </div>
     </main>
-
-    <!-- Loading -->
     <div v-if="checking" class="loading-overlay">
       <div class="loading-dots">
         <div class="dot" />
         <div class="dot" />
         <div class="dot" />
       </div>
-      <span class="loading-text">加载中...</span>
+      <span class="loading-text">指纹设备连接中...</span>
     </div>
   </div>
 </template>
 
 <script setup>
 import { useRouter } from 'vue-router';
-import { showToast, showFailToast } from 'vant';
+import { showToast, showFailToast, showNotify } from 'vant';
 import { ref, onMounted, onUnmounted } from 'vue';
 import { checkEmployeeByJobNo } from '../api/fingerprint.js';
 
@@ -165,71 +129,16 @@ import bgImg from '../assets/images/bj.png';
 
 const router = useRouter();
 
-const currentStep = ref(1);
-const jobNo = ref('');
-const checking = ref(false);
 const isConnected = ref(false);
 const isScanning = ref(false);
 const statusText = ref('');
 
-const handleBack = () => {
-    if (currentStep.value === 2) {
-        currentStep.value = 1;
-        if (plugin.fingerprintConfig) {
-            plugin.fingerprintConfig.disableConnect();
-        }
-        isConnected.value = false;
-        isScanning.value = false;
-        statusText.value = '请点击指纹录入按钮进行录入';
-    } else if (currentStep.value === 1) {
-        router.push('/home');
-    }
-};
-
-// 验证账号是否为系统成员
-const handleNextStep = async () => {
-    if (!jobNo.value) {
-        showToast('请输入账号');
-        return;
-    }
-    checking.value = true;
-
-    try {
-        const res = await checkEmployeeByJobNo(jobNo.value);
-        if (res.errorCode === 0) {
-            if (!res.data) {
-                showToast('该账号不是系统成员');
-            } else {
-                currentStep.value = 2;
-                if (plugin.fingerprintConfig) {
-                    plugin.fingerprintConfig.connect();
-                } else {
-                    isConnected.value = false;
-                    statusText.value = '指纹设备插件未就绪,请检查设备';
-                }
-            }
-        } else {
-            showToast(res.errorMessage);
-        }
+const loginName = ref('');
+const checking = ref(false);
 
-    } catch (error) {
-        console.log(error, '账号验证失败');
-    } finally {
-        checking.value = false;
-    }
-};
 
-// 返回输入账号
-const backToStepOne = () => {
-    currentStep.value = 1;
-    if (plugin.fingerprintConfig) {
-        try {
-            plugin.fingerprintConfig.disableConnect();
-        } catch (e) {
-            console.error(e);
-        }
-    }
-    isConnected.value = false;
+const handleBack = () => {
+    router.push('/home');
 };
 
 // 录入指纹
@@ -238,14 +147,9 @@ const handleEnroll = () => {
         showToast('设备未连接或插件异常,请检查指纹设备');
         return;
     }
-    if (!jobNo.value) {
-        showToast('账号为空,将返回上一步重新输入');
-        currentStep.value = 1;
-        return;
-    }
     isScanning.value = true;
     statusText.value = '请将手指放在指纹采集器上多次按压';
-    plugin.fingerprintConfig.enroll(jobNo.value);
+    plugin.fingerprintConfig.enroll(loginName.value);
 };
 
 // 重新录入指纹
@@ -301,9 +205,21 @@ const handleFingerprintResponse = data => {
 
 // 设置指纹设备的响应处理函数
 onMounted(() => {
+    checking.value = true;
     if (plugin.fingerprintConfig) {
         plugin.fingerprintConfig.receiveFingerprintResponse = handleFingerprintResponse;
     }
+
+    // 获取登录账号
+    const loginInfo = localStorage.getItem('#LoginInfo');
+    if (loginInfo) {
+        const loginInfoObj = JSON.parse(loginInfo);
+        loginName.value = loginInfoObj.loginName;
+    }
+    
+    setTimeout(() => {
+        checking.value = false;
+    }, 1500);
 });
 
 // 断开连接
@@ -458,6 +374,58 @@ onUnmounted(() => {
   border-radius: 6px;
 }
 
+
+.loading-text {
+  color: #7ec8ff;
+  font-size: 16px;
+  margin: 0;
+}
+
+/* ========== 验证失败状态区域 ========== */
+.verify-failed-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 20px;
+  padding: 40px 20px;
+}
+
+.failed-icon {
+  color: #ef4444;
+  font-size: 60px;
+  animation: shake 0.5s ease-in-out;
+}
+
+.failed-text {
+  color: #fca5a5;
+  font-size: 16px;
+  margin: 0;
+  text-align: center;
+}
+
+.retry-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 12px 30px;
+  background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
+  color: #fff;
+  border: none;
+  border-radius: 8px;
+  font-size: 16px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.retry-btn:hover {
+  background: linear-gradient(90deg, #0d7cc1 0%, #0099cc 100%);
+  box-shadow: 0 4px 15px rgba(30, 144, 255, 0.4);
+  transform: translateY(-2px);
+}
+
 /* ========== 输入区域 ========== */
 .input-section {
   display: flex;
@@ -569,6 +537,17 @@ onUnmounted(() => {
   50% { opacity: 0.6; transform: scale(0.9); }
 }
 
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+@keyframes shake {
+  0%, 100% { transform: translateX(0); }
+  10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
+  20%, 40%, 60%, 80% { transform: translateX(3px); }
+}
+
 /* ========== 指纹区域 ========== */
 .fingerprint-section {
   display: flex;

+ 560 - 13
src/stock/RegularRequisition.vue

@@ -7,7 +7,7 @@
     <!-- 顶部标题区域 -->
     <div class="header-section">
       <button class="logout-btn" @click="goBack">
-        <i class="fas fa-home" />
+        <i class="fas fa-arrow-left" />
         <span>返回领料</span>
       </button>
       <h1 class="page-title">常用领料</h1>
@@ -122,6 +122,53 @@
                 <i class="fas fa-map-marker-alt location-icon" />
                 <span class="info-value">{{ item.inventoryActulPosition || item.inventoryPosition || '-' }} / {{ item.inventoryWarehouse || '-' }}</span>
               </div>
+              <!-- 配送类型徽章 -->
+              <div class="info-row delivery-type-row">
+                <span class="delivery-type-badge" :class="getDeliveryTypeBadgeClass(item.deliveryType)">
+                  {{ item.deliveryType || '未指定' }}
+                </span>
+              </div>
+            </div>
+
+            <!-- 配送选择区域(固定高度,始终存在) -->
+            <div class="card-delivery-section" @click.stop>
+              <!-- 选中时显示配送选择 -->
+              <template v-if="selectedIds.includes(item.id)">
+                <div class="delivery-row">
+                  <span class="delivery-label">配送:</span>
+                  <div class="delivery-method-buttons">
+                    <button
+                      :class="['delivery-btn', { 'active': item.deliveryMethod === 'AGV_Delivery', 'disabled': item.deliveryType === '人工配送' }]"
+                      :disabled="item.deliveryType === '人工配送'" @click="changeDeliveryMethod(item, 'AGV_Delivery')"
+                    >
+                      <i class="fas fa-robot" /> AGV
+                    </button>
+                    <button
+                      :class="['delivery-btn', { 'active': item.deliveryMethod === 'Manual_Delivery', 'disabled': item.deliveryType === '强制AGV配送' }]"
+                      :disabled="item.deliveryType === '强制AGV配送'"
+                      @click="changeDeliveryMethod(item, 'Manual_Delivery')"
+                    >
+                      <i class="fas fa-user" /> 人工
+                    </button>
+                  </div>
+                </div>
+                <!-- AGV配送位置选择 -->
+                <div class="delivery-location-row">
+                  <span class="delivery-label">位置:</span>
+                  <v-select
+                    v-if="item.deliveryMethod === 'AGV_Delivery'" v-model="item.selectedLocation"
+                    :options="locator" :clearable="false" class="location-select dark-select" :append-to-body="true"
+                    :calculate-position="withPopper"
+                  />
+                  <span v-else style="font-size: 16px;">人工配送</span>
+                </div>
+              </template>
+              <!-- 未选中时显示占位 -->
+              <template v-else>
+                <div class="delivery-placeholder">
+                  <span>点击选择后设置配送方式</span>
+                </div>
+              </template>
             </div>
             
             <!-- 选中状态指示 -->
@@ -144,13 +191,36 @@
 
     <!-- 底部操作按钮 -->
     <div class="bottom-actions">
-      <button
-        class="submit-btn"
-        :disabled="selectedIds.length === 0"
-        @click="submitStock"
-      >
+      <button class="submit-btn" :disabled="selectedIds.length === 0" @click="submitStock">
         加入领料车
       </button>
+      <button class="pick-btn" :disabled="selectedIds.length === 0" @click="handleComplete">
+        领料 
+        <!-- ({{ selectedIds.length }}) -->
+        <span v-if="selectedIds && selectedIds.length > 0">({{ selectedIds.length }})</span>
+      </button>
+    </div>
+
+    <!-- 拣货完成确认弹窗 - 科技感风格 -->
+    <div v-if="completeModalVisible" class="tech-modal-overlay" @click.self="handleCompleteCancel">
+      <div class="tech-modal success-modal">
+        <div class="modal-content-row">
+          <div class="modal-text">
+            <h3>领料完成</h3>
+            <p>领料申请已完成,配送任务已创建!<br />请确认是否返回主页?</p>
+          </div>
+          <div class="modal-icon">
+            <div class="icon-box success-box">
+              <i class="fas fa-truck-loading" />
+              <i class="fas fa-check check-icon" />
+            </div>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button class="modal-btn cancel-btn" @click="handleCompleteCancel">取消</button>
+          <button class="modal-btn confirm-btn" @click="handleCompleteConfirm">确认</button>
+        </div>
+      </div>
     </div>
 
     <!-- Loading -->
@@ -175,8 +245,9 @@ import 'vue-select/dist/vue-select.css';
 // 图片资源
 import bgImg from '../assets/images/bj.png';
 
-import { getWarehouseList } from '../api/stock.js';
-import { queryCommonUse, createStockOutPrepareLine, queryPickingCarNumber } from '../api/stockOut.js';
+import { getWarehouseList, queryFeedAreaStatus } from '../api/stock.js';
+import { queryCommonUse, createStockOutPrepareLine, queryPickingCarNumber, generatePickPaper } from '../api/stockOut.js';
+import { gateController } from '../hardware/GateOperate.js';
 
 const router = useRouter();
 
@@ -191,11 +262,18 @@ const count = ref(0);
 const warehouseList = ref([]);
 const stockRequisitionList = ref([]);
 const selectedIds = ref([]);
+const selectedRows = ref([]);
 
 const loading = ref(false);
 const loadingMore = ref(false);
 const noMoreData = ref(false);
 
+// 弹窗控制
+const completeModalVisible = ref(false);
+
+// 可配送位置
+const locator = ref([]);
+
 const inventoryTypeList = ref([
     { value: 'Clamp', label: '工装' },
     { value: 'Instrument', label: '设备' },
@@ -207,6 +285,45 @@ const pageSize = 20;
 const currentStart = ref(0);
 const total = ref(0);
 
+// 根据 deliveryType 初始化配送方式
+const initDeliveryMethod = item => {
+    if (item.deliveryType === '人工配送') {
+    // 人工配送:只能人工,不可切换
+        item.deliveryMethod = 'Manual_Delivery';
+        item.selectedLocation = '';
+    } else if (item.deliveryType === '强制AGV配送') {
+    // 强制AGV配送:只能AGV,不可切换为人工
+        item.deliveryMethod = 'AGV_Delivery';
+        if (!item.selectedLocation) {
+            item.selectedLocation = '';
+        }
+    } else if (item.deliveryType === '可选AGV配送') {
+    // 可选AGV配送:默认人工,可切换为AGV
+        item.deliveryMethod = item.deliveryMethod || 'Manual_Delivery';
+        item.selectedLocation = item.selectedLocation || '';
+    }
+};
+
+// 获取配送类型徽章样式
+const getDeliveryTypeBadgeClass = deliveryType => {
+    const classMap = {
+        '人工配送': 'badge-manual',
+        '强制AGV配送': 'badge-agv-force',
+        '可选AGV配送': 'badge-agv-optional',
+    };
+    return classMap[deliveryType] || '';
+};
+
+// 配送方式变更处理
+const changeDeliveryMethod = (item, method) => {
+    item.deliveryMethod = method;
+    // 当选择人工配送时,清空配送位置
+    if (method === 'Manual_Delivery') {
+        item.selectedLocation = '';
+    }
+    console.log('配送方式变更:', item.inventoryName, item.deliveryMethod);
+};
+
 // 打开领料车
 const openStockOutCar = () => {
     router.push('/stock-picking-car?isRegular=true');
@@ -223,8 +340,16 @@ const toggleSelect = id => {
     const index = selectedIds.value.indexOf(id);
     if (index > -1) {
         selectedIds.value.splice(index, 1);
+        selectedRows.value = selectedRows.value.filter(item => item.id !== id);
     } else {
         selectedIds.value.push(id);
+        // 添加到 selectedRows 并初始化配送字段
+        const item = stockRequisitionList.value.find(item => item.id === id);
+        if (item) {
+            // 初始化配送字段
+            initDeliveryMethod(item);
+            selectedRows.value.push(item);
+        }
     }
 };
 
@@ -234,11 +359,14 @@ const toggleSelectAll = checked => {
         stockRequisitionList.value.forEach(item => {
             if (!selectedIds.value.includes(item.id)) {
                 selectedIds.value.push(item.id);
+                initDeliveryMethod(item);
+                selectedRows.value.push(item);
             }
         });
     } else {
         const currentListIds = stockRequisitionList.value.map(item => item.id);
         selectedIds.value = selectedIds.value.filter(id => !currentListIds.includes(id));
+        selectedRows.value = selectedRows.value.filter(item => !currentListIds.includes(item.id));
     }
 };
 
@@ -300,7 +428,36 @@ const loadMore = async () => {
         const res = await queryCommonUse(params);
         if (res.errorCode == 0) {
             if (res.datas && res.datas.length > 0) {
-                stockRequisitionList.value = [...stockRequisitionList.value, ...res.datas];
+                // 处理新加载的数据,保留已选中物料的配送方式设置
+                const newData = res.datas.map(item => {
+                    const selectedItem = selectedRows.value.find(selected => selected.id === item.id);
+                    
+                    if (selectedItem) {
+                        // 如果已选中,保留其配送方式和位置设置
+                        const updatedItem = {
+                            ...item,
+                            deliveryMethod: selectedItem.deliveryMethod,
+                            selectedLocation: selectedItem.selectedLocation,
+                        };
+                        
+                        // 更新 selectedRows 中对应项的引用
+                        const selectedIndex = selectedRows.value.findIndex(selected => selected.id === item.id);
+                        if (selectedIndex !== -1) {
+                            selectedRows.value[selectedIndex] = updatedItem;
+                        }
+                        
+                        return updatedItem;
+                    } else {
+                        // 未选中,初始化为空
+                        return {
+                            ...item,
+                            deliveryMethod: '',
+                            selectedLocation: '',
+                        };
+                    }
+                });
+                
+                stockRequisitionList.value = [...stockRequisitionList.value, ...newData];
                 currentStart.value += res.datas.length;
                 total.value = res.total;
                 
@@ -349,7 +506,35 @@ const getStockRequisitionList = async () => {
         const res = await queryCommonUse(params);
         if (res.errorCode == 0) {
             if (res.datas && res.datas.length > 0) {
-                stockRequisitionList.value = res.datas;
+                // 保留已选中物料的配送方式设置
+                stockRequisitionList.value = res.datas.map(item => {
+                    // 查找该物料是否在已选中列表中
+                    const selectedItem = selectedRows.value.find(selected => selected.id === item.id);
+
+                    if (selectedItem) {
+                        // 如果已选中,保留其配送方式和位置设置,并更新 selectedRows 中的引用
+                        const updatedItem = {
+                            ...item,
+                            deliveryMethod: selectedItem.deliveryMethod,
+                            selectedLocation: selectedItem.selectedLocation,
+                        };
+
+                        // 更新 selectedRows 中对应项的引用
+                        const selectedIndex = selectedRows.value.findIndex(selected => selected.id === item.id);
+                        if (selectedIndex !== -1) {
+                            selectedRows.value[selectedIndex] = updatedItem;
+                        }
+
+                        return updatedItem;
+                    } else {
+                        // 未选中,初始化为空
+                        return {
+                            ...item,
+                            deliveryMethod: '',
+                            selectedLocation: '',
+                        };
+                    }
+                });
                 currentStart.value = res.datas.length;
                 total.value = res.total;
                 
@@ -435,6 +620,7 @@ const submitStock = async () => {
         const res = await createStockOutPrepareLine(params);
         if (res.errorCode == 0) {
             selectedIds.value = [];
+            selectedRows.value = [];
             showNotify({ type: 'success', message: '已添加到领料车' });
             // 先等待提交完成,再刷新列表
             await getDatas();
@@ -448,6 +634,62 @@ const submitStock = async () => {
     }
 };
 
+// 领料申请,直接验证并提交
+const handleComplete = async () => {
+    if (selectedIds.value.length === 0) {
+        showNotify({ type: 'danger', message: '请至少选择一个物料' });
+        return;
+    }
+
+    // 验证选择了 AGV 配送的物料是否都选择了配送位置
+    const agvItems = selectedRows.value.filter(item => item.deliveryMethod === 'AGV_Delivery');
+    const hasEmptyLocation = agvItems.some(item => !item.selectedLocation);
+
+    if (hasEmptyLocation) {
+        showNotify({ type: 'danger', message: '请为所有 AGV 配送的物料选择配送位置' });
+        return;
+    }
+
+    const params = [];
+    selectedRows.value.forEach(item => {
+        params.push({
+            inventoryId: item.id,
+            deliveryMethod: item.deliveryMethod,
+            positionEndNo: item.selectedLocation.value,
+        });
+    });
+
+    console.log('提交参数:', params);
+    const res = await generatePickPaper(params);
+    if (res.errorCode == 0) {
+        showNotify({ type: 'success', message: '领料申请成功' });
+        selectedIds.value = [];
+        selectedRows.value = [];
+        // 显示完成确认弹窗
+        gateController('SHOTOPEN');
+        router.push('/');
+    } else {
+        showNotify({ type: 'danger', message: res.errorMessage });
+    }
+};
+
+// 处理完成确认
+const handleCompleteConfirm = () => {
+    // 调用开门操作
+    gateController('SHOTOPEN');
+    completeModalVisible.value = false;
+    router.push('/home');
+};
+
+// 处理完成取消
+const handleCompleteCancel = () => {
+    completeModalVisible.value = false;
+    // 清理选择状态并刷新列表
+    selectedIds.value = [];
+    selectedRows.value = [];
+    getDatas();
+};
+
 /**
      * 查询领料车中的数量
      */
@@ -463,16 +705,43 @@ const queryPickingCarCount = async () => {
         } else {
             showNotify({ type: 'danger', message: res.errorMessage });
         }
-
     } catch (error) {
         console.error('查询领料车数量失败:', error);
         showNotify({ type: 'danger', message: '查询领料车数量失败' });
     }
 };
+
+// 获取送料区货位
+const getFeedAreaLocator = async () => {
+    try {
+        const res = await queryFeedAreaStatus();
+
+        if (res.errorCode === 0) {
+            if (res.datas && res.datas.length > 0) {
+                locator.value = res.datas.map(item => {
+                    return {
+                        label: item.positionName,
+                        value: item.positionNo,
+                    };
+                });
+            } else {
+                locator.value = [];
+            }
+        } else {
+            showNotify({ type: 'danger', message: res.errorMessage });
+        }
+    } catch (error) {
+        console.error('获取送料区货位API调用失败:', error);
+        showNotify({ type: 'danger', message: '获取送料区货位API调用失败' });
+    }
+};
+
+// 页面加载时初始化数据
 onMounted(() => {
     getDatas();
     getWarehouses();
     queryPickingCarCount();
+    getFeedAreaLocator();
 });
 </script>
 
@@ -874,12 +1143,14 @@ onMounted(() => {
   background: rgba(4, 28, 61, 0.95);
   z-index: 20;
   flex-shrink: 0;
+  display: flex;
+  gap: 20px;
 }
 
+.pick-btn,
 .submit-btn {
-  width: 100%;
+  flex: 1;
   padding: 18px 0;
-  background: #4a99e2;
   border: none;
   border-radius: 12px;
   font-size: 24px;
@@ -891,17 +1162,284 @@ onMounted(() => {
   text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
 }
 
+.pick-btn {
+  background: linear-gradient(90deg, #26dc8a 0%, #44efa2 100%);
+}
+
+.pick-btn:hover:not(:disabled) {
+  box-shadow: 0 0 30px rgba(42, 220, 138, 0.6);
+  transform: translateY(-2px);
+}
+
+.submit-btn {
+  background: #4a99e2;
+}
+
 .submit-btn:hover:not(:disabled) {
   box-shadow: 0 0 30px rgba(74, 153, 226, 0.6);
   transform: translateY(-2px);
 }
+ .location-select {
+    flex: 1;
+    min-width: 0;
+  }
 
+.pick-btn:disabled,
 .submit-btn:disabled {
   opacity: 0.5;
   cursor: not-allowed;
   background: linear-gradient(90deg, #6b7280 0%, #9ca3af 100%);
 }
 
+/* ========== 配送方式选择区域(固定高度) ========== */
+.card-delivery-section {
+  padding: 10px 15px;
+  border-top: 1px solid rgba(30, 144, 255, 0.3);
+  background: rgba(9, 61, 140, 0.3);
+  min-height: 94px;
+  /* 固定最小高度 */
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  margin-bottom: 6px;
+}
+
+/* 未选中时的占位样式 */
+.delivery-placeholder {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  color: #5a8abf;
+  font-size: 12px;
+  opacity: 0.7;
+}
+
+.delivery-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.delivery-row:last-child {
+  margin-bottom: 0;
+}
+
+.delivery-location-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  height: 30px;
+  margin-top: 6px;
+}
+
+.delivery-label {
+  font-size: 12px;
+  color: #7ec8ff;
+  white-space: nowrap;
+}
+
+/* 配送方式按钮 */
+.delivery-method-buttons {
+  display: flex;
+  gap: 4px;
+}
+
+.delivery-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 3px;
+  padding: 4px 8px;
+  font-size: 11px;
+  font-weight: 500;
+  background: rgba(13, 58, 106, 0.8);
+  border: 1px solid #2a7fff;
+  color: #7ec8ff;
+  cursor: pointer;
+  transition: all 0.3s;
+  border-radius: 4px;
+}
+
+.delivery-btn i {
+  font-size: 10px;
+}
+
+.delivery-btn.active {
+  background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
+  color: #fff;
+  border-color: #00bfff;
+}
+
+.delivery-btn.disabled {
+  opacity: 0.4;
+  cursor: not-allowed;
+}
+
+.delivery-btn:hover:not(.disabled):not(.active) {
+  background: rgba(30, 144, 255, 0.3);
+}
+
+/* 配送类型徽章 */
+.delivery-type-badge {
+  padding: 3px 10px;
+  border-radius: 12px;
+  font-size: 10px;
+  font-weight: 500;
+  display: inline-block;
+}
+
+.delivery-type-badge.badge-manual {
+  background: rgba(107, 114, 128, 0.4);
+  border: 1px solid #6b7280;
+  color: #d1d5db;
+}
+
+.delivery-type-badge.badge-agv-force {
+  background: rgba(234, 88, 12, 0.4);
+  border: 1px solid #ea580c;
+  color: #fdba74;
+}
+
+.delivery-type-badge.badge-agv-optional {
+  background: rgba(16, 185, 129, 0.4);
+  border: 1px solid #10b981;
+  color: #6ee7b7;
+}
+
+/* ========== 科技感弹窗样式 ========== */
+.tech-modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(4, 28, 61, 0.8);
+  backdrop-filter: blur(8px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.tech-modal {
+  background: linear-gradient(135deg, rgba(9, 61, 140, 0.95) 0%, rgba(4, 28, 61, 0.98) 100%);
+  border: 2px solid #049FD8;
+  border-radius: 20px;
+  padding: 40px;
+  min-width: 500px;
+  max-width: 90vw;
+  box-shadow: 0 0 50px rgba(4, 159, 216, 0.4);
+  position: relative;
+}
+
+.tech-modal.success-modal {
+  border-color: #10b981;
+  box-shadow: 0 0 50px rgba(16, 185, 129, 0.4);
+}
+
+.modal-content-row {
+  display: flex;
+  align-items: center;
+  gap: 30px;
+  margin-bottom: 40px;
+}
+
+.modal-text {
+  flex: 1;
+}
+
+.modal-text h3 {
+  font-size: 32px;
+  font-weight: bold;
+  color: #fff;
+  margin: 0 0 15px 0;
+}
+
+.modal-text p {
+  font-size: 18px;
+  color: #7ec8ff;
+  margin: 0;
+  line-height: 1.6;
+}
+
+.modal-icon {
+  flex-shrink: 0;
+}
+
+.icon-box {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 40px;
+  color: #fff;
+  position: relative;
+  box-shadow: 0 0 20px rgba(0, 191, 255, 0.4);
+}
+
+.icon-box.success-box {
+  background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+  box-shadow: 0 0 20px rgba(16, 185, 129, 0.4);
+}
+
+.check-icon {
+  position: absolute;
+  top: -5px;
+  right: -5px;
+  width: 30px;
+  height: 30px;
+  background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  color: #fff;
+}
+
+.modal-footer {
+  display: flex;
+  gap: 20px;
+  justify-content: flex-end;
+}
+
+.modal-btn {
+  padding: 15px 40px;
+  border: none;
+  border-radius: 12px;
+  font-size: 18px;
+  font-weight: bold;
+  cursor: pointer;
+  transition: all 0.3s;
+  min-width: 120px;
+}
+
+.cancel-btn {
+  background: rgba(107, 114, 128, 0.8);
+  color: #fff;
+  border: 1px solid #6b7280;
+}
+
+.cancel-btn:hover {
+  background: rgba(75, 85, 99, 0.9);
+  box-shadow: 0 0 15px rgba(107, 114, 128, 0.4);
+}
+
+.confirm-btn {
+  background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
+  color: #fff;
+}
+
+.confirm-btn:hover {
+  box-shadow: 0 0 25px rgba(30, 144, 255, 0.6);
+  transform: translateY(-2px);
+}
+
 /* ========== 响应式 - 1920x1080 横屏 ========== */
 @media screen and (orientation: landscape) {
   .stock-requisition-page {
@@ -1124,5 +1662,14 @@ onMounted(() => {
   .card-grid-wrapper::-webkit-scrollbar {
     display: none;
   }
+    :deep(.dark-select .vs__dropdown-toggle) {
+    min-height: 48px;
+    padding: 8px 14px;
+  }
+
+  :deep(.dark-select .vs__dropdown-option) {
+    padding: 12px 18px;
+    font-size: 16px;
+  }
 }
 </style>