فهرست منبع

feat:优化物料卡片等
- 出库确认改名为借用,入库确认改名归还
- 统一卡片样式
- 修改借用逻辑,点击借用直接借用
- 优化指纹录入-登录
- 修改首页飞机图
- 增加若三分钟无操作即跳到指纹登录页

liuyanpeng 5 ماه پیش
والد
کامیت
faac618edd

+ 10 - 17
src/App.vue

@@ -4,24 +4,17 @@
   <!-- </a-config-provider> -->
 </template>
 
-<script>
-// import zhCN from 'ant-design-vue/es/locale/zh_CN';
-// import dayjs from 'dayjs';
-// import 'dayjs/locale/zh-cn';
-// dayjs.locale('zh-cn');
+<script setup>
+import { useIdleTimeout } from './composables/useIdleTimeout.js';
 
-export default {
-
-    components: {
-
-    },
-    data() {
-        return {
-            // locale: zhCN,
-
-        };
-    },
-};
+// 启用全局3分钟无操作自动跳转功能
+// 排除登录相关页面:首页(/)、指纹登录(/fingerprint-login)
+useIdleTimeout(
+    3 * 60 * 1000, // 3分钟
+    // 20000, // 20s
+    '/fingerprint-login', // 跳转到指纹登录页
+    ['/', '/fingerprint-login'], // 排除的页面
+);
 </script>
 
 <style></style>

+ 3 - 7
src/Fingerprint/FingerprintEnroll.vue

@@ -281,15 +281,11 @@ const handleFingerprintResponse = data => {
 
     if (code === 0) {
         statusText.value = msg || '指纹录入成功!';
-
-        // 返回登录界面
-        router.push('/');
-
-
-
-
+        
         if (msg === '上传成功' || msg === '当前用户已经录入过指纹,更新成功。') {
             isScanning.value = false;
+            // 返回登录界面
+            router.push('/');
         }
         if (msg === '连接成功') {
             isConnected.value = true;

BIN
src/assets/images/9898.png


+ 164 - 0
src/composables/useIdleTimeout.js

@@ -0,0 +1,164 @@
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useRouter } from 'vue-router';
+
+/**
+ * 全局用户空闲超时处理
+ * @param {number} timeout - 超时时间(毫秒),默认3分钟
+ * @param {string} redirectPath - 超时后跳转的路径,默认指纹登录页
+ * @param {Array} excludePaths - 排除的路径列表,不启用超时功能的页面
+ */
+export function useIdleTimeout(
+    timeout = 3 * 60 * 1000, // 3分钟
+    redirectPath = '/fingerprint-login',
+    excludePaths = ['/', '/fingerprint-login', '/fingerprint-enroll'],
+) {
+    const router = useRouter();
+    const isIdle = ref(false);
+    let idleTimer = null;
+
+    // 检查当前路径是否需要排除
+    const shouldExclude = () => {
+        const currentPath = router.currentRoute.value.path;
+        return excludePaths.includes(currentPath);
+    };
+
+    // 重置空闲计时器
+    const resetIdleTimer = () => {
+    // 如果当前页面需要排除,则不启动计时器
+        if (shouldExclude()) {
+            return;
+        }
+
+        // 清除现有计时器
+        if (idleTimer) {
+            clearTimeout(idleTimer);
+            idleTimer = null;
+        }
+
+        isIdle.value = false;
+
+        // 设置新的计时器
+        idleTimer = setTimeout(() => {
+            // 再次检查是否需要排除,避免在计时期间切换到登录页
+            if (!shouldExclude()) {
+                isIdle.value = true;
+                handleIdleTimeout();
+            }
+        }, timeout);
+    };
+
+    // 处理空闲超时
+    const handleIdleTimeout = () => {
+        console.log('用户空闲超时,跳转到指纹登录页');
+    
+        // 清理计时器
+        clearIdleTimer();
+    
+        // 跳转到指纹登录页
+        router.push(redirectPath);
+    };
+
+    // 清理空闲计时器
+    const clearIdleTimer = () => {
+        if (idleTimer) {
+            clearTimeout(idleTimer);
+            idleTimer = null;
+        }
+        isIdle.value = false;
+    };
+
+    // 需要监听的用户活动事件
+    const events = [
+        'mousedown',
+        'mousemove', 
+        'keypress',
+        'scroll',
+        'touchstart',
+        'click',
+        'contextmenu',
+        'dblclick',
+        'wheel',
+    ];
+
+    // 事件处理函数(使用防抖,避免频繁重置)
+    let debounceTimer = null;
+    const handleUserActivity = () => {
+        if (debounceTimer) {
+            clearTimeout(debounceTimer);
+        }
+    
+        debounceTimer = setTimeout(() => {
+            resetIdleTimer();
+        }, 100); // 100ms防抖
+    };
+
+    // 启动空闲检测
+    const startIdleDetection = () => {
+    // 如果当前页面需要排除,则不启动
+        if (shouldExclude()) {
+            return;
+        }
+
+        // 添加事件监听器
+        events.forEach(event => {
+            document.addEventListener(event, handleUserActivity, true);
+        });
+
+        // 启动初始计时器
+        resetIdleTimer();
+    };
+
+    // 停止空闲检测
+    const stopIdleDetection = () => {
+    // 移除事件监听器
+        events.forEach(event => {
+            document.removeEventListener(event, handleUserActivity, true);
+        });
+
+        // 清理计时器
+        clearIdleTimer();
+    
+        if (debounceTimer) {
+            clearTimeout(debounceTimer);
+            debounceTimer = null;
+        }
+    };
+
+    // 监听路由变化,动态启停空闲检测
+    const setupRouteWatcher = () => {
+    // 监听路由变化
+        router.beforeEach((to, from, next) => {
+            // 如果跳转到排除的页面,停止检测
+            if (excludePaths.includes(to.path)) {
+                stopIdleDetection();
+            } else {
+                // 如果从排除页面跳转到普通页面,启动检测
+                if (excludePaths.includes(from.path)) {
+                    // 延迟启动,确保页面已完全加载
+                    setTimeout(() => {
+                        startIdleDetection();
+                    }, 1000);
+                }
+            }
+            next();
+        });
+    };
+
+    // 组件挂载时启动
+    onMounted(() => {
+        setupRouteWatcher();
+        startIdleDetection();
+    });
+
+    // 组件卸载时清理
+    onUnmounted(() => {
+        stopIdleDetection();
+    });
+
+    return {
+        isIdle,
+        resetIdleTimer,
+        startIdleDetection,
+        stopIdleDetection,
+    };
+}

+ 7 - 1
src/login/FingerprintLogin.vue

@@ -172,10 +172,13 @@ const handleFingerprintResponse = data => {
     if (code === 0) {
         // 有数据说明登录成功
         userInfo.value = responseData;
-        if (msg === '连接成功' || msg === '请按下手指') {
+        if (msg === '连接成功' || msg === '请按下手指' || msg === '同步指纹成功') {
             isConnected.value = true;
             updateStatus('recognizing', '请按下手指');
         }
+        if (msg === '同步指纹成功') {
+            startRecognize();
+        }
         if (msg === '正在同步指纹,请稍后进行指纹登录') {
             updateStatus('waiting', '正在同步指纹,请稍后进行指纹登录');
         }
@@ -241,6 +244,9 @@ const handleRestart = () => {
                 startRecognize();
             }, 500);
         }
+        if (typeof(FINGERPRINT_CONFIG) == 'undefined'){
+            updateStatus('failed', '设备不支持指纹识别功能');
+        }
     }, 1000);
 };
 

+ 31 - 15
src/login/UserHome.vue

@@ -118,7 +118,7 @@
 
       <!-- 表格区域 -->
       <div class="table-section">
-        <dv-scroll-board :config="config" style="width:1080px;height:780px" />
+        <dv-scroll-board ref="scrollBoard" :config="config" style="width:1080px;height:780px" />
       </div>
     </div>
 
@@ -296,14 +296,14 @@ import { showNotify } from 'vant';
 import { useRouter } from 'vue-router';
 import { getQueryString } from '../util/common.js';
 import { controlGateByBigInv} from '../api/gate.js';
-import { ref, reactive, onMounted, onUnmounted } from 'vue';
+import { ref, reactive, onMounted, onUnmounted,nextTick } from 'vue';
 import { getWarehouseId, updateUserPassword, queryRemindTime } from '../api/login.js';
 
 // 图片资源 - 从 assets/images 目录加载
 import cardIcon1 from '../assets/images/card1.png';
 import cardIcon2 from '../assets/images/card2.png';
 import cardIcon3 from '../assets/images/card3.png';
-import airplaneImg from '../assets/images/airplane.png';
+import airplaneImg from '../assets/images/9898.png';
 import buttonBg from '../assets/images/buttonBg.png';
 import bgImg from '../assets/images/bj.png';
 import settingsIcon from '../assets/images/setting.png';
@@ -366,6 +366,7 @@ const showSettings = ref(false);
 const pickingConfirmVisible = ref(false);
 const settingsDropdown = ref(null);
 
+const scrollBoard = ref(null);
 const config = ref({
     header: ['名称', '图号', '类型', '操作', '时间', '操作人员'],
     data: [],
@@ -502,9 +503,9 @@ const outButtons = reactive([
 
 // 内侧屏幕操作按钮
 const inButtons = reactive([
-    // { label: '出库\n确认', action: 'materialOutLeave' },
+    { label: '借用', action: 'materialOutLeave' },
     // { label: '入库\n离开', action: 'materialInLeave' },
-    { label: '离开', action: 'materialInOutLeave' },
+    // { label: '离开', action: 'materialInOutLeave' },
     { label: '成品\n出库', action: 'finishedProductOut' },
     { label: '大件\n运输', action: 'bigProductTransport' },
     // { label: '料箱\n上架', action: 'feedingArea' },
@@ -609,9 +610,9 @@ const handleAction = action => {
         router.push('/finish-product-in');
         break;
     // 内侧屏幕操作
-    // case 'materialOutLeave':
-    //     router.push('/outbound-confirm');
-    //     break;
+    case 'materialOutLeave':
+        router.push('/outbound-confirm');
+        break;
     // case 'materialInLeave':
     //     router.push('/returned-leave');
     //     break;
@@ -664,10 +665,25 @@ const getInfo = async () => {
             statistics.value[2].value = stockOutQuantity;
             if (cfInAndOutResponses && cfInAndOutResponses.length > 0) {
                 config.value.data = cfInAndOutResponses.map(item => {
-                    const type = item.type === '借用' ? '<span style="color:#FF4D4F;">借用</span>' :
-                        '<span style="color:#25EC87;">归还</span>';
-                    return [item.name, item.no, item.inventoryType, type, item.time, item.userName];
+                    let typeEl = '';
+                    // const type = item.type === '借用' ? '<span style="color:#FF4D4F;">借用</span>' :
+                    //     '<span style="color:#25EC87;">归还</span>';
+                    if(item.type === '借用') {
+                        typeEl = '<span style="color:#FF4D4F;">借用</span>';
+                    } else if(item.type === '入库') {
+                        typeEl = '<span style="color:#25EC87;">入库</span>';
+                    } else if(item.type === '出库') {
+                        typeEl = '<span style="color:#FF4D4F;">出库</span>';
+                    } else if(item.type === '归还') {
+                        typeEl = '<span style="color:#25EC87;">归还</span>';
+                    }
+                    return [item.name, item.no, item.inventoryType, typeEl, item.time, item.userName];
                 });
+                // nextTick(() => { 
+                //     if(scrollBoard.value) {
+                //         scrollBoard.value.updateRows(config.value.data);
+                //     }
+                // });
             }
         } else {
             console.log(res.errorMessage);
@@ -950,10 +966,10 @@ const confirmBigTransport = async () => {
 /* 飞机图片 - 背景层,位于内容下方 */
 .airplane-layer {
   position: fixed;
-  top: 100px;
-  left: 0;
-  width: 100%;
-  height: 55%;
+  top: 64px;
+  left: -290px;
+  width: 160%;
+  /* height: 55%; */
   display: flex;
   align-items: center;
   justify-content: center;

+ 3 - 3
src/router/routes.js

@@ -13,7 +13,7 @@ const StockPickingCar = () => import('../stock/StockPickingCar.vue');
 // 拣货管理
 const OrderPicking = () => import('../stock-out/OrderPicking.vue');
 // 出库确认
-// const OutboundConfirm = () => import('../stock-out/OutboundConfirm.vue');
+const OutboundConfirm = () => import('../stock-out/OutboundConfirm.vue');
 // 还料离开
 // const ReturnedLeave = () => import('../stock-in/ReturnedLeave.vue');
 // 离开
@@ -56,7 +56,7 @@ const routes = [
     { path: '/login', component: UserLogin, meta: { title: '用户登录' } },
     { path: '/home', component: UserHome, meta: { title: '首页' } },
     { path: '/order-picking', component: OrderPicking, meta: { title: '拣货管理' } },
-    // { path: '/outbound-confirm', component: OutboundConfirm, meta: { title: '出库确认' } },
+    { path: '/outbound-confirm', component: OutboundConfirm, meta: { title: '借用' } },
     { path: '/material-in-out-leave', component: MaterialInOutConfirm, meta: { title: '离开' } },
 
     // { path: '/returned-leave', component: ReturnedLeave, meta: { title: '还料离开' } },    
@@ -65,7 +65,7 @@ const routes = [
     { path: '/apply-material', component: ApplyMaterial, meta: { title: '领料' } },
     { path: '/regular-requisition', component: RegularRequisition, meta: { title: '常用领料' } },
     { path: '/agv-rfid-recognition', component: AgvRfidRecognition, meta: { title: 'AGV RFID识别' } },
-    { path: '/in-confirm', component: InConfirm, meta: { title: '入库确认' } },
+    { path: '/in-confirm', component: InConfirm, meta: { title: '归还' } },
     { path: '/return-management', component: ReturnManagement, meta: { title: '还料区管理' } },
     { path: '/delivery-management', component: DeliveryManagement, meta: { title: '送料区管理' } },
     { path: '/abnormal-anchorage-area', component: AbnormalManagement, meta: { title: '异常停泊区管理' } },

+ 90 - 43
src/stock-in/InConfirm.vue

@@ -1,4 +1,4 @@
-<!-- 还料入库 - 智能仓储风格 -->
+<!-- 还料归还 - 智能仓储风格 -->
 <template>
   <div class="stock-requisition-page">
     <!-- 背景层 -->
@@ -10,7 +10,7 @@
         <i class="fas fa-home" />
         <span>主页</span>
       </button>
-      <h1 class="page-title">入库</h1>
+      <h1 class="page-title">归还</h1>
       <!-- 右侧操作按钮 -->
       <div class="header-actions">
         <button class="action-btn refresh-btn" @click="resetView">
@@ -36,26 +36,14 @@
 
         <!-- 卡片网格 -->
         <div v-else class="card-grid">
-          <template v-for="(item, index) in materialList" :key="item.id || index">
-            <div
-              v-if="item.inventoryId != null"              
+          <template v-for="(item, index) in validMaterialList" :key="item.id || index">
+            <div              
               class="inventory-card"
-              :class="getCardClass(item.remarks)"
+              :class="[{ 'selected': item.selected }, getCardClass(item.remarks)]"
               @click="handleSelect(item)"
             >
               <!-- 卡片序号 -->
-              <div
-                v-if="item.selected" class="card-index" 
-                style="background: linear-gradient(135deg, #a8f63b 0%, #60faee 100%);" 
-                :class="getCardClass(item.remarks)"
-              />
-
-              <!-- 图片区域 -->
-              <div class="card-image">
-                <div class="image-placeholder" :class="getCardClass(item.remarks)">
-                  <i :class="getInventoryIcon(item.inventoryType)" />
-                </div>
-              </div>
+              <div class="card-index" :class="getCardClass(item.remarks)">{{ index + 1 }}</div>
 
               <!-- 信息区域 -->
               <div class="card-info">
@@ -67,10 +55,23 @@
                   <span class="info-label">编号:</span>
                   <span class="info-value">{{ item.inventoryNo || '-' }}</span>
                 </div>
+                <div class="info-row">
+                  <span class="info-label">类型:</span>
+                  <span class="info-value">{{ item.inventoryType || '-' }}</span>
+                  <i :class="getInventoryIcon(item.inventoryType)" class="type-icon" />
+                </div>
                 <div class="info-row location-row">
                   <i class="fas fa-map-marker-alt location-icon" />
                   <span class="info-value">{{ item.positionName || '-' }} / {{ item.warehouseName || '-' }}</span>
                 </div>
+                <!-- remarks为不在库的内容区增加借用人字段 -->
+                <div v-if="item.remarks === '不在库'" class="info-row borrower-row">
+                  <span class="info-label">借用人:</span>
+                  <span class="info-value">{{ item.borrower || '-' }}</span>
+                </div>
+                <div v-else class="info-row borrower-row">
+                  <span class="info-label">-</span>
+                </div>
               </div>
 
               <!-- 状态区域(固定高度) -->
@@ -80,6 +81,11 @@
                   {{ item.remarks || '-' }}
                 </div>
               </div>
+
+              <!-- 选中状态指示 -->
+              <div v-if="item.selected" class="selected-indicator">
+                <i class="fas fa-check" />
+              </div>
             </div>
           </template>
         </div>
@@ -88,9 +94,9 @@
 
     <!-- 底部操作按钮 -->
     <div class="bottom-actions">
-      <button class="submit-btn" @click="handleComplete">
-        确认入库 
-        <span v-if="validStockCount != 0">{{ validStockCount }}</span>
+      <button class="submit-btn" @click="hasValidMaterials ? handleComplete() : enterConfirm()">
+        {{ hasValidMaterials ? '确认归还' : '直接进' }}
+        <span v-if="hasValidMaterials && validStockCount != 0">({{ validStockCount }})</span>
       </button>
     </div>
 
@@ -166,6 +172,16 @@ const validStockCount = computed(() => {
     return materialList.value.filter(item => item.remarks === '不在库' && item.selected === true).length;
 });
 
+// 判断是否有有效物料(含有inventoryId)
+const hasValidMaterials = computed(() => {
+    return materialList.value.length > 0 && materialList.value.some(item => item.inventoryId != null);
+});
+
+// 过滤出有效的物料项目(用于连续序号显示)
+const validMaterialList = computed(() => {
+    return materialList.value.filter(item => item.inventoryId != null);
+});
+
 // 根据 remarks 返回卡片样式类
 const getCardClass = remarks => {
     if (remarks === '不在库') {
@@ -189,14 +205,14 @@ const getInventoryIcon = type => {
 // 获取状态颜色
 // const getStatusColor = remarks => {
 //     if (remarks === '不在库') {
-//         return '#3b82f6'; // 蓝色 - 需要入库
+//         return '#3b82f6'; // 蓝色 - 需要归还
 //     } else if (remarks === '在库') {
 //         return '#10b981'; // 绿色 - 已在库
 //     }
 //     return '#6b7280'; // 灰色 - 默认
 // };
 
-// 还料入库
+// 还料归还
 const handleComplete = async () => {
     // 只处理不在库的数据
     const notInStockItems = materialList.value.filter(item => item.selected === true);
@@ -261,6 +277,7 @@ const getList = async () => {
                             j.warehouseName = i.warehouseName;
                             j.positionName = i.positionName;
                             j.inventoryId = i.inventoryId;
+                            j.borrower = i.borrower;
                             j.queryStatus = 2;
                             j.selected = false;
                             j.remarks = i.remarks;
@@ -287,7 +304,7 @@ const getList = async () => {
     }
 };
 
-// 生成CF入库
+// 生成CF归还
 const generateCFStockIn = async params => {
     loading.value = true;
     try {
@@ -298,7 +315,7 @@ const generateCFStockIn = async params => {
             // 调用开门操作
             gateController('SHOTOPEN');
             
-            showNotify({ type: 'success', message: '入库申请已完成,还料任务已创建!' });
+            showNotify({ type: 'success', message: '归还申请已完成,还料任务已创建!' });
             // 跳转回主页
             router.push('/');
         } else {
@@ -306,8 +323,8 @@ const generateCFStockIn = async params => {
         }
 
     } catch (error) {
-        console.error('生成CF入库单API调用失败:', error);
-        showNotify({ type: 'danger', message: '生成CF入库单API调用失败' });
+        console.error('生成CF归还单API调用失败:', error);
+        showNotify({ type: 'danger', message: '生成CF归还单API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -356,10 +373,7 @@ onMounted(() => {
         // 将临时数组的数据赋值给epcs
         // 执行获取列表
 
-        //addEpc([{
-        //    'epc': 'FFF000000000000000000001',
-        // 'epc': 'AAA000000000000000000002',
-        //}]);
+        // addEpc([ { 'epc': 'FFF000000000000000000001' }, { 'epc': 'FFF000000000000000000002' }]);
 
         getList();
     }, 1000);
@@ -381,6 +395,10 @@ const unrecognizedCount = computed(() => {
 });
 
 const handleSelect = item => {
+    if(item.remarks === '在库'){
+        showNotify({ type: 'danger', message: '该物料已在库,不可选择,请选择不在库的物料!' });
+        return;
+    }
     if(item.remarks === '不在库'){
         item.selected = !item.selected;
     }
@@ -516,9 +534,9 @@ const enterConfirm = () => {
   transform: translateY(-3px);
 }
 
-/* 不在库卡片 - 蓝色(需入库) */
+/* 不在库卡片 - 绿色(需归还) */
 .inventory-card.not-in-stock-card {
-  border-color: #3b82f6;
+  border-color: #10b981;
 }
 
 /* 在库卡片 - 红色警告 */
@@ -547,8 +565,8 @@ const enterConfirm = () => {
 }
 
 .card-index.not-in-stock-card {
-  background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
-  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);
+  background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+  box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
 }
 
 .card-index.in-stock-card {
@@ -556,6 +574,25 @@ const enterConfirm = () => {
   box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
 }
 
+/* 选中状态指示 - 右上角 */
+.selected-indicator {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  width: 28px;
+  height: 28px;
+  background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 12px;
+  font-weight: bold;
+  box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
+  z-index: 10;
+}
+
 /* 图片区域 */
 .card-image {
   width: 100%;
@@ -593,18 +630,28 @@ const enterConfirm = () => {
 
 /* 信息区域 */
 .card-info {
-  padding: 10px 15px 15px;
+  padding: 50px 15px 15px;
 }
 
 .info-row {
   display: flex;
-  align-items: baseline;
-  margin-bottom: 6px;
+  align-items: center;
+  padding: 6px 0;
   font-size: 14px;
+  min-height: 28px;
+  border-bottom: 1px solid rgba(126, 200, 255, 0.2);
+  margin-bottom: 0;
 }
 
 .info-row:last-child {
-  margin-bottom: 0;
+  border-bottom: none;
+}
+
+/* 类型图标样式 */
+.type-icon {
+  color: #fbbf24;
+  margin-left: 8px;
+  font-size: 16px;
 }
 
 .info-label {
@@ -659,9 +706,9 @@ const enterConfirm = () => {
 }
 
 .status-badge.not-in-stock-card {
-  background: rgba(59, 130, 246, 0.3);
-  border: 1px solid #3b82f6;
-  color: #93c5fd;
+  background: rgba(16, 185, 129, 0.3);
+  border: 1px solid #10b981;
+  color: #6ee7b7;
 }
 
 .status-badge.in-stock-card {
@@ -777,7 +824,7 @@ const enterConfirm = () => {
   }
 
   .card-info {
-    padding: 6px 10px 8px;
+    padding: 32px 10px 8px;
   }
 
   .info-row {

+ 391 - 135
src/stock-out/OutboundConfirm.vue

@@ -1,4 +1,4 @@
-<!-- 出库确认 - 智能仓储风格 -->
+<!-- 借用 - 智能仓储风格 -->
 <template>
   <dv-border-box-8>
     <div class="stock-requisition-page">
@@ -11,10 +11,10 @@
           <i class="fas fa-home" />
           <span>主页</span>
         </button>
-        <h1 class="page-title">出库确认</h1>
+        <h1 class="page-title">借用</h1>
         <!-- 右侧操作按钮 -->
         <div class="header-actions">
-          <button class="action-btn refresh-btn" @click="getStockOutList(false)">
+          <button class="action-btn refresh-btn" @click="resetView">
             <i class="fas fa-sync-alt" />
             <span>重新校验</span>
           </button>
@@ -25,7 +25,8 @@
       <main class="main-content">
         <!-- 统计信息 -->
         <div v-if="materialList.length > 0" class="stats-section">
-          <span class="stats-text">共 {{ materialList.length }} 条数据,已完成 {{ completedCount }} 条</span>
+          <!-- <span class="stats-text">共 {{ materialList.length }} 条数据,已完成 {{ completedCount }} 条</span> -->
+          <span class="stats-text">共 {{ materialList.length }} 条数据,不识别 {{ unrecognizedCount }} 条</span>
         </div>
 
         <!-- 卡片网格区域 -->
@@ -38,64 +39,82 @@
 
           <!-- 卡片网格 -->
           <div v-else class="card-grid">
-            <div
-              v-for="(item, index) in materialList"
-              :key="item.inventoryId || index"
-              class="inventory-card"
-              :class="{ 'completed': item.status === 1 }"
-            >
-              <!-- 卡片序号 -->
-              <div class="card-index" :class="{ 'completed': item.status === 1 }">{{ index + 1 }}</div>
-
-              <!-- 图片区域 -->
-              <div class="card-image">
-                <div class="image-placeholder" :style="{ backgroundColor: getStatusColor(item.status) }">
-                  <i :class="getInventoryIcon(item.inventoryType)" />
+            <template v-for="(item, index) in validMaterialList" :key="item.inventoryId || index">
+              <div
+                class="inventory-card"
+                :class="{ 
+                  'completed': item.status === 1 || item.status === 3, 
+                  'not-selectable': item.positionName === '不在库' || item.inventoryName === '未识别epc'
+                }"
+              >
+                <!-- @click="handleSelect(item)" -->
+                <!-- 'selected': item.selected, -->
+                <!-- 'warning': item.status === 2 || item.status === 3, -->
+                <!-- 卡片序号 -->
+                <div class="card-index" :class="{ 'completed': item.status === 1 }">{{ index + 1 }}</div>
+
+                <!-- 信息区域 -->
+                <div class="card-info">
+                  <div class="info-row">
+                    <span class="info-label">名称:</span>
+                    <span class="info-value">{{ item.inventoryName || '-' }}</span>
+                  </div>
+                  <div class="info-row">
+                    <span class="info-label">编号:</span>
+                    <span class="info-value">{{ item.inventoryNo || '-' }}</span>
+                  </div>
+                  <div class="info-row">
+                    <span class="info-label">类型:</span>
+                    <span class="info-value">{{ item.inventoryType || '-' }}</span>
+                    <i :class="getInventoryIcon(item.inventoryType)" class="type-icon" />
+                  </div>
+                  <div class="info-row location-row">
+                    <i class="fas fa-map-marker-alt location-icon" />
+                    <span class="info-value">{{ item.positionName || '-' }} / {{ item.warehouseName || '-' }}</span>
+                  </div>
+                  <!-- 不在库的借用人信息显示在内容区域 -->
+                  <div v-if="item.positionName === '不在库'" class="info-row borrower-row">
+                    <span class="info-label">借用人:</span>
+                    <span class="info-value">{{ item.borrower || '-' }}</span>
+                  </div>
+                  <div v-else class="borrower-row">
+                    <span class="info-label">-</span>
+                  </div>
                 </div>
-              </div>
 
-              <!-- 信息区域 -->
-              <div class="card-info">
-                <div class="info-row">
-                  <span class="info-label">名称:</span>
-                  <span class="info-value">{{ item.inventoryName || '-' }}</span>
-                </div>
-                <div class="info-row">
-                  <span class="info-label">编号:</span>
-                  <span class="info-value">{{ item.inventoryNo || '-' }}</span>
+                <!-- 操作区域(固定高度) -->
+                <div class="card-action-section">
+                  <template v-if="item.positionName !== '不在库' && item.inventoryName !== '未识别epc'">
+                    <button v-if="item.status === 1 || item.status === 3" class="status-btn apply-btn" @click.stop="handleApply(item)">
+                      <i class="fas fa-hand-paper" /> 借用
+                    </button>
+                  </template>
+                  <template v-else-if="item.positionName === '不在库'">
+                    <div class="status-badge borrowed-badge">
+                      <i class="fas fa-exclamation-circle" />
+                      已借用
+                    </div>
+                  </template>
+                  <template v-else>
+                    <span class="status-placeholder">无需操作</span>
+                  </template>
                 </div>
-                <div class="info-row location-row">
-                  <i class="fas fa-map-marker-alt location-icon" />
-                  <span class="info-value">{{ item.positionName || '-' }} / {{ item.warehouseName || '-' }}</span>
-                </div>
-              </div>
 
-              <!-- 操作区域(固定高度) -->
-              <div class="card-action-section">
-                <template v-if="item.positionName !== '不在库' && item.inventoryName !== '未识别epc'">
-                  <button v-if="item.status === 1" class="status-btn completed-btn" disabled>
-                    <i class="fas fa-check" /> 领料完成
-                  </button>
-                  <button v-if="item.status === 2" class="status-btn start-btn" @click.stop="handleStart(item)">
-                    <i class="fas fa-play" /> 开始领料
-                  </button>
-                  <button v-if="item.status === 3" class="status-btn apply-btn" @click.stop="handleApply(item)">
-                    <i class="fas fa-hand-paper" /> 申请领料
-                  </button>
-                </template>
-                <template v-else>
-                  <span class="status-placeholder">无需操作</span>
-                </template>
+                <!-- 选中状态指示 -->
+                <!-- <div v-if="item.selected" class="selected-indicator">
+                  <i class="fas fa-check" />
+                </div> -->
               </div>
-            </div>
+            </template>
           </div>
         </div>
       </main>
 
       <!-- 底部操作按钮 -->
       <div class="bottom-actions">
-        <button class="submit-btn" :disabled="!isCanLeave" @click="handleLeave">
-          领料离开
+        <button class="submit-btn" @click="hasValidMaterials ? handleLeaveClick() : directExit()">
+          {{ hasValidMaterials ? '借用离开' : '直接出仓库' }}
+          <span v-if="hasValidMaterials && selectedCount > 0">({{ selectedCount }})</span>
         </button>
       </div>
 
@@ -106,7 +125,28 @@
           <div class="dot" />
           <div class="dot" />
         </div>
-        <span class="loading-text">加载中...</span>
+        <span class="loading-text">执行中...</span>
+      </div>
+
+      <!-- 直接出仓库确认弹窗 -->
+      <div v-if="exitConfirmVisible" class="tech-modal-overlay" @click.self="exitConfirmVisible = false">
+        <div class="tech-modal">
+          <div class="modal-content-row">
+            <div class="modal-text">
+              <h3>请确认您要直接出仓库</h3>
+              <p>系统检测到您未携带物料,或者未勾选任何物料,是否确认直接出仓库?</p>
+            </div>
+            <div class="modal-icon">
+              <div class="icon-box">
+                <i class="fas fa-clipboard-check" />
+              </div>
+            </div>
+          </div>
+          <div class="modal-footer">
+            <button class="modal-btn cancel-btn" @click="directExit">直接出仓库</button>
+            <button class="modal-btn confirm-btn" @click="exitConfirmVisible = false">取消</button>
+          </div>
+        </div>
       </div>
     </div>
   </dv-border-box-8>
@@ -187,97 +227,200 @@ const isCanLeave = computed(() => {
     });
 });
 
-// 开始领料
+const unrecognizedCount = computed(() => {
+    return materialList.value.filter(item => item.inventoryId == null).length;
+});
+
+// 选中数量统计
+const selectedCount = computed(() => {
+    return materialList.value.filter(item => item.selected === true).length;
+});
+
+// 判断是否有有效物料(含有inventoryId)
+const hasValidMaterials = computed(() => {
+    return materialList.value.length > 0 && materialList.value.some(item => item.inventoryId != null);
+});
+
+// 过滤出有效的物料项目(用于连续序号显示)
+const validMaterialList = computed(() => {
+    return materialList.value.filter(item => item.inventoryId != null);
+});
+
+// 卡片选择处理
+const handleSelect = item => {
+    // 不在库和未识别epc的卡片不可选中
+    if (item.positionName === '不在库' || item.inventoryName === '未识别epc') {
+        showNotify({ type: 'danger', message: '不在库或尚未借用的卡片不可选中' });
+        return;
+    }
+    // 只有已借用状态(status === 1)的卡片才可被勾选
+    if (item.status !== 1) {
+        showNotify({ type: 'danger', message: '只有已借用状态的卡片才可被勾选' });
+        return;
+    }
+    item.selected = !item.selected;
+};
+
+// 弹窗状态
+const exitConfirmVisible = ref(false);
+
+// 处理离开按钮点击
+const handleLeaveClick = () => {
+    gateController('SHOTOPEN');
+    router.push('/');
+    // if (selectedCount.value === 0) {
+    //     exitConfirmVisible.value = true;
+    //     return;
+    // }
+    // handleLeave();
+};
+
+// 直接出仓库
+const directExit = () => {
+    exitConfirmVisible.value = false;
+    // 直接开门离开
+    gateController('SHOTOPEN');
+    router.push('/');
+};
+
+// 开始借用
 const handleStart = record => {
     operationId.value = record.inventoryId;
     generateCFStockOut(record.stockOutPrepareLineId);
 };
 
-// 申请领料
+// 借用
 const handleApply = async record => {
-    showNotify({ type: 'success', message: '申请领料成功' });
+    showNotify({ type: 'success', message: '申请借用成功' });
     try {
         const res = await leaveCFWarehouse(record.inventoryId);
 
         if (res.errorCode === 0) {
-            showNotify({ type: 'success', message: '申请领料成功' });
+            showNotify({ type: 'success', message: '申请借用成功' });
             operationId.value = record.inventoryId;
-            getStockOutList(true);
+            if(res.data){
+                materialList.value.forEach(item =>{
+                    if(item.inventoryId == res.data.inventoryId){
+                        // item.status = res.data.status;
+                        // item.stockOutId = res.data.stockOutId;
+                        // item.stockOutPrepareLineId = res.data.stockOutPrepareLineId;
+                        item.borrower = res.data.borrower;
+                        item.epc = res.data.epc;
+                        item.inventoryName = res.data.inventoryName;
+                        item.inventoryType = res.data.inventoryType;
+                        item.warehouseName = res.data.warehouseName;
+                        item.positionName = res.data.positionName;
+                    }
+                });
+            }
         } else {
             showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
-        console.error('申请领料API调用失败:', error);
-        showNotify({ type: 'danger', message: '申请领料API调用失败' });
+        console.error('申请借用API调用失败:', error);
+        showNotify({ type: 'danger', message: '申请借用API调用失败' });
     } finally {
         loading.value = false;
     }
 };
 
-// 领料完成后离开
+// 借用完成后离开
 const handleLeave = async () => {
-    const params = materialList.value.map(item => {
-        return {
-            stockOutPrepareLineId: item.stockOutPrepareLineId,
-            stockOutId: item.stockOutId,
-            inventoryId: item.inventoryId,
-        };
+    const params = [];
+    materialList.value.map(item => {
+        if(item.inventoryId != null && item.selected === true){
+            params.push({
+                stockOutPrepareLineId: item.stockOutPrepareLineId,
+                stockOutId: item.stockOutId,
+                inventoryId: item.inventoryId,
+            });
+        }
     });
+    if(params.length == 0){
+        // 直接开门离开
+        gateController('SHOTOPEN');
+        router.push('/');
+        return;
+    }
+
+
     loading.value = true;
     try {
         const res = await cfStockOutLeave(params);
 
         if (res.errorCode === 0) {
             gateController('SHOTOPEN');
-            showNotify({ type: 'success', message: '领料离开成功' });
+            showNotify({ type: 'success', message: '借用离开成功' });
             router.push('/');
         } else {
             showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
-        console.error('领料离开API调用失败:', error);
-        showNotify({ type: 'danger', message: '领料离开API调用失败' });
+        console.error('借用离开API调用失败:', error);
+        showNotify({ type: 'danger', message: '借用离开API调用失败' });
     } finally {
         loading.value = false;
     }
 };
 
-// 获取扫描到的领料数据
-const getStockOutList = async (isOne = false) => {
+const resetView = () => {
+    materialList.value = [];
+};
+
+// 获取扫描到的借用数据
+const getStockOutList = async () => {
     // loading.value = true;
     const params = {
-        epcList: epcs.value,
+        epcList: [],
     };
+
+    const tempMaterialList = [];
+
+    for(let i = 0; i < materialList.value.length; i++) {
+        if(materialList.value[i].queryStatus === 0) {
+            params.epcList.push(materialList.value[i].epc);
+            materialList.value[i].queryStatus = 1;
+            tempMaterialList.push(materialList.value[i]);
+        }
+    }
+
+    if(params.epcList.length == 0){
+        return;
+    }
     try {
         const res = await cfStockOut(params);
 
         if (res.errorCode === 0) {
             if (res.datas && res.datas.length > 0) {
-                if (!isOne) {
-                    materialList.value = [];
-                    materialList.value = res.datas;
-                } else {
-                    res.datas.forEach(i => {
-                        materialList.value.forEach(j => {
-                            if (j.inventoryId === operationId.value) {
-                                j.status = 1;
-                                j.stockOutId = i.stockOutId;
-                                j.positionName = i.positionName;
-                                j.warehouseName = i.warehouseName;
-                                j.stockOutPrepareLineId = i.stockOutPrepareLineId;
-                            }
-                        });
+                res.datas.forEach(i => {
+                    materialList.value.forEach(j => {
+                        if (j.epc === i.epc) {
+                            j.stockOutId = i.stockOutId;
+                            j.inventoryName = i.inventoryName;
+                            j.inventoryNo = i.inventoryNo;
+                            j.inventoryType = i.inventoryType;
+                            j.borrower = i.borrower;
+                            j.warehouseName = i.warehouseName;
+                            j.positionName = i.positionName;
+                            j.stockOutPrepareNo = i.stockOutPrepareNo;
+                            j.stockOutNo = i.stockOutNo;
+                            j.inventoryId = i.inventoryId;
+                            j.stockOutPrepareLineId = i.stockOutPrepareLineId;
+                            j.queryStatus = 2;
+                            j.status = i.status;
+                        }
                     });
-                }
-            } else {
-                materialList.value = [];
+                });
             }
         } else {
             showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
-        console.error('获取扫描到的领料数据API调用失败:', error);
-        showNotify({ type: 'danger', message: '获取扫描到的领料数据API调用失败' });
+        console.error('获取扫描到的借用数据API调用失败:', error);
+        showNotify({ type: 'danger', message: '获取扫描到的借用数据API调用失败' });
+        tempMaterialList.forEach(item => {
+            item.queryStatus = 0;
+        });
     } finally {
         loading.value = false;
     }
@@ -296,7 +439,8 @@ const generateCFStockOut = async id => {
         const res = await createStockOut(params);
 
         if (res.errorCode === 0) {
-            getStockOutList(true);
+            // getStockOutList();
+            resetView();
         } else {
             showNotify({ type: 'danger', message: res.errorMessage });
         }
@@ -315,12 +459,29 @@ const addEpc = data => {
     const newEpcs = data.map(item => item.epc);
     
     // 将新的EPC数据添加到临时数组中,使用Set进行去重
-    const uniqueEpcs = new Set([...tempEpcs.value, ...newEpcs]);
-    tempEpcs.value = [...uniqueEpcs];
+    const uniqueEpcs = new Set([...newEpcs]);
+
+    if(newEpcs != null && newEpcs.length > 0) {
+        newEpcs.forEach(item => {
+            let exist = false;
+            for(let i =0; i < materialList.value.length; i++) {
+                if(materialList.value[i].epc === item) {
+                    exist = true;
+                    break;
+                }
+            }
+            if(exist == false) {
+                materialList.value.push({
+                    epc: item,
+                    queryStatus: 0, // 0: 未查询, 1: 查询中, 2: 已查询, 3: 未查询到
+                    selected: false, // 初始化选中状态
+                });
+            }
+        });
+    }
 };
 
 onMounted(() => {
-    // getStockOutList(false);
     plugin.gateConfig.sendEpc = function(data){
         if (typeof(GATE_CONFIG) == 'undefined') {
             console.log('设备不支持读写器功能。');
@@ -331,13 +492,27 @@ onMounted(() => {
 
     // 创建全局唯一定时器,每2秒执行一次
     timer = setInterval(() => {
-        // 将临时数组的数据赋值给epcs
-        epcs.value = [...tempEpcs.value];
-        // 执行获取出库列表
-        getStockOutList(false);
-        // 清空临时数组
-        tempEpcs.value = [];
-    }, 3000);
+        getStockOutList();
+        // addEpc([{
+        //     'epc': 'FFF000000000000000000001',
+        // },
+        // {
+        //     'epc': 'AAA000000000000000000002',
+        // },
+        // {
+        //     'epc': 'EEE000000637713102889024',
+        // },
+        // {
+        //     'epc': 'EEE000000640804317954113',
+        // },
+        // {
+        //     'epc': 'EEE000000639032505671744',
+        // },
+        // {
+        //     'epc': 'EEE000000640804317955613',
+        // },
+        // ]);
+    }, 1000);
 });
 
 onUnmounted(() => {
@@ -472,6 +647,36 @@ onUnmounted(() => {
   box-shadow: 0 0 15px rgba(16, 185, 129, 0.3);
 }
 
+.inventory-card.warning {
+  border-color: #fbbf24 !important;
+  box-shadow: 0 0 15px rgba(251, 191, 36, 0.4) !important;
+}
+
+.inventory-card.selected {
+  border-color: #fbbf24;
+  box-shadow: 0 0 20px rgba(251, 191, 36, 0.4);
+  background: rgba(251, 191, 36, 0.1);
+}
+
+/* 选中状态指示 - 右上角 */
+.selected-indicator {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  width: 28px;
+  height: 28px;
+  background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 12px;
+  font-weight: bold;
+  box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
+  z-index: 10;
+}
+
 /* 卡片序号 - 左上角 */
 .card-index {
   position: absolute;
@@ -492,49 +697,74 @@ onUnmounted(() => {
 }
 
 .card-index.completed {
-  background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+  /* background: linear-gradient(135deg, #10b981 0%, #34d399 100%); */
   box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
 }
 
-/* 图片区域 */
-.card-image {
-  width: 100%;
-  aspect-ratio: 3 / 4;
+/* 类型图标样式 */
+.type-icon {
+  color: #fbbf24;
+  margin-left: 8px;
+  font-size: 16px;
+}
+
+/* 不可选择的卡片 - 红色边框 */
+.inventory-card.not-selectable {
+  border-color: #ef4444 !important;
+  box-shadow: 0 0 15px rgba(239, 68, 68, 0.3) !important;
+  cursor: not-allowed;
+}
+
+.inventory-card.not-selectable:hover {
+  border-color: #dc2626 !important;
+  box-shadow: 0 0 20px rgba(220, 38, 38, 0.4) !important;
+  transform: none;
+}
+
+/* 借用人信息样式 */
+.borrower-info {
   display: flex;
-  align-items: center;
-  justify-content: center;
-  overflow: hidden;
-  margin: 15px;
-  margin-bottom: 10px;
-  border-radius: 8px;
-  width: calc(100% - 30px);
+  flex-direction: column;
+  gap: 8px;
+  padding: 8px 0;
+  min-height: 48px; /* 确保与按钮区域高度一致 */
 }
 
-.image-placeholder {
+.borrower-row {
   display: flex;
   align-items: center;
-  justify-content: center;
-  width: 100%;
-  height: 100%;
-  background: linear-gradient(135deg, #f0f4f8 0%, #e8ecf0 100%);
+  font-size: 14px;
+  border-bottom: 1px solid rgba(126, 200, 255, 0.2) !important;
+}
+
+.borrower-label {
+  color: #7ec8ff;
+  white-space: nowrap;
+  margin-right: 5px;
+}
+
+.borrower-value {
   color: #fff;
-  font-size: 48px;
+  flex: 1;
 }
 
 /* 信息区域 */
 .card-info {
-  padding: 10px 15px 15px;
+  padding: 50px 15px 15px;
 }
 
 .info-row {
   display: flex;
-  align-items: baseline;
-  margin-bottom: 6px;
+  align-items: center;
+  padding: 6px 0; /* 增加内容块高度 */
   font-size: 14px;
+  min-height: 28px; /* 增加最小高度 */
+  border-bottom: 1px solid rgba(126, 200, 255, 0.2); /* 添加分割线 */
+  margin-bottom: 0;
 }
 
 .info-row:last-child {
-  margin-bottom: 0;
+  border-bottom: none; /* 最后一行不显示分割线 */
 }
 
 .info-label {
@@ -556,8 +786,9 @@ onUnmounted(() => {
   display: flex;
   align-items: center;
   margin-top: 4px;
-  padding-top: 4px;
-  border-top: 1px solid rgba(30, 144, 255, 0.2);
+  /* padding-top: 4px; */
+  /* border-top: 1px solid rgba(30, 144, 255, 0.2); */
+  border-bottom: 1px solid rgba(30, 144, 255, 0.2) !important;
 }
 
 .location-icon {
@@ -568,19 +799,19 @@ onUnmounted(() => {
 
 /* ========== 操作区域(固定高度) ========== */
 .card-action-section {
-  padding: 12px 15px;
-  border-top: 1px solid rgba(30, 144, 255, 0.3);
-  background: rgba(9, 61, 140, 0.3);
-  min-height: 50px;
+  padding: 8px 15px;
+  min-height: 48px;
   display: flex;
   align-items: center;
   justify-content: center;
+  /* 移除边框和背景色差异,与无需操作区域保持一致 */
 }
 
 .status-placeholder {
   color: #5a8abf;
-  font-size: 12px;
-  opacity: 0.7;
+  font-size: 14px;
+  opacity: 0.8;
+  margin-top: -10px;
 }
 
 /* 状态按钮 */
@@ -624,6 +855,31 @@ onUnmounted(() => {
   box-shadow: 0 0 15px rgba(30, 144, 255, 0.5);
 }
 
+.in-stock-btn {
+  background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
+  color: #fff;
+  opacity: 0.8;
+  cursor: default;
+}
+
+/* 状态徽章样式 */
+.status-badge {
+  padding: 8px 20px;
+  border-radius: 20px;
+  font-size: 13px;
+  font-weight: 500;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.borrowed-badge {
+  background: rgba(239, 68, 68, 0.3);
+  border: 1px solid #ef4444;
+  color: #fca5a5;
+  margin-top: -10px;
+}
+
 /* ========== 底部操作按钮 ========== */
 .bottom-actions {
   width: 100%;
@@ -731,7 +987,7 @@ onUnmounted(() => {
   }
 
   .card-info {
-    padding: 6px 10px 8px;
+    padding: 32px 10px 8px;
   }
 
   .info-row {
@@ -749,13 +1005,13 @@ onUnmounted(() => {
   }
 
   .status-btn {
-    padding: 6px 10px;
+    padding:  10px;
     font-size: 11px;
     gap: 4px;
   }
 
   .status-placeholder {
-    font-size: 10px;
+    font-size: 14px;
   }
 
   .bottom-actions {

+ 24 - 15
src/stock/ApplyMaterial.vue

@@ -113,14 +113,6 @@
             <!-- 卡片序号 -->
             <div class="card-index">{{ index + 1 }}</div>
 
-            <!-- 图片区域 -->
-            <div class="card-image">
-              <img v-if="item.imageUrl" :src="item.imageUrl" alt="设备图片" />
-              <div v-else class="image-placeholder">
-                <i :class="getInventoryIcon(item.inventoryType)" />
-              </div>
-            </div>
-
             <!-- 信息区域 -->
             <div class="card-info">
               <div class="info-row">
@@ -131,6 +123,11 @@
                 <span class="info-label">编号:</span>
                 <span class="info-value">{{ item.inventoryNo || '-' }}</span>
               </div>
+              <div class="info-row">
+                <span class="info-label">类型:</span>
+                <span class="info-value">{{ item.inventoryType || '-' }}</span>
+                <i :class="getInventoryIcon(item.inventoryType)" class="type-icon" />
+              </div>
               <div class="info-row location-row">
                 <i class="fas fa-map-marker-alt location-icon" />
                 <span class="info-value">{{ item.inventoryActulPosition || item.inventoryPosition || '-' }} / {{
@@ -231,7 +228,9 @@
         加入领料车
       </button>
       <button class="pick-btn" :disabled="selectedIds.length === 0" @click="handleComplete">
-        领料 ({{ selectedIds.length }})
+        领料 
+        <!-- ({{ selectedIds.length }}) -->
+        <span v-if="selectedIds && selectedIds.length > 0">({{ selectedIds.length }})</span>
       </button>
     </div>
 
@@ -1044,18 +1043,28 @@ onMounted(() => {
 
 /* 信息区域 */
 .card-info {
-  padding: 10px 15px 15px;
+  padding: 45px 15px 15px;
 }
 
 .info-row {
   display: flex;
-  align-items: baseline;
-  margin-bottom: 6px;
+  align-items: center;
+  padding: 6px 0;
   font-size: 14px;
+  min-height: 28px;
+  border-bottom: 1px solid rgba(126, 200, 255, 0.2);
+  margin-bottom: 0;
 }
 
 .info-row:last-child {
-  margin-bottom: 0;
+  border-bottom: none;
+}
+
+/* 类型图标样式 */
+.type-icon {
+  color: #fbbf24;
+  margin-left: 8px;
+  font-size: 16px;
 }
 
 .info-label {
@@ -1413,7 +1422,7 @@ onMounted(() => {
   }
 
   .card-info {
-    padding: 6px 8px 8px;
+    padding: 30px 8px 8px;
   }
 
   .info-row {
@@ -1715,7 +1724,7 @@ onMounted(() => {
   }
 
   .card-info {
-    padding: 12px 18px 18px;
+    padding: 52px 18px 18px;
   }
 
   .info-row {

+ 25 - 14
src/stock/RegularRequisition.vue

@@ -93,20 +93,21 @@
             <!-- 卡片序号 -->
             <div class="card-index">{{ index + 1 }}</div>
             
-            <!-- 图片区域 -->
-            <div class="card-image">
-              <img v-if="item.imageUrl" :src="item.imageUrl" alt="设备图片" />
-              <div v-else class="image-placeholder">
-                <i :class="getInventoryIcon(item.inventoryType)" />
-              </div>
-            </div>
-            
             <!-- 信息区域 -->
             <div class="card-info">
               <div class="info-row">
                 <span class="info-label">名称:</span>
                 <span class="info-value">{{ item.inventoryName || '-' }}</span>
               </div>
+              <div class="info-row">
+                <span class="info-label">编号:</span>
+                <span class="info-value">{{ item.inventoryNo || '-' }}</span>
+              </div>
+              <div class="info-row">
+                <span class="info-label">类型:</span>
+                <span class="info-value">{{ item.inventoryType || '-' }}</span>
+                <i :class="getInventoryIcon(item.inventoryType)" class="type-icon" />
+              </div>
               <div class="info-row stats-row">
                 <span class="info-label">次数:</span>
                 <i class="fas fa-history stats-icon" />
@@ -395,7 +396,7 @@ const getWarehouses = async () => {
 
 // 返回领料
 const goBack = () => {
-    router.push('/stock-requisition');
+    router.push('/apply-material');
 };
 
 // 重置筛选条件
@@ -780,18 +781,28 @@ onMounted(() => {
 
 /* 信息区域 */
 .card-info {
-  padding: 10px 15px 15px;
+  padding: 47px 15px 15px;
 }
 
 .info-row {
   display: flex;
-  align-items: baseline;
-  margin-bottom: 6px;
+  align-items: center;
+  padding: 6px 0;
   font-size: 14px;
+  min-height: 28px;
+  border-bottom: 1px solid rgba(126, 200, 255, 0.2);
+  margin-bottom: 0;
 }
 
 .info-row:last-child {
-  margin-bottom: 0;
+  border-bottom: none;
+}
+
+/* 类型图标样式 */
+.type-icon {
+  color: #fbbf24;
+  margin-left: 8px;
+  font-size: 16px;
 }
 
 .info-label {
@@ -1016,7 +1027,7 @@ onMounted(() => {
   }
 
   .card-info {
-    padding: 6px 8px 8px;
+    padding: 30px 8px 8px;
   }
 
   .info-row {

+ 23 - 14
src/stock/StockPickingCar.vue

@@ -87,14 +87,6 @@
             <!-- 卡片序号 -->
             <div class="card-index">{{ index + 1 }}</div>
             
-            <!-- 图片区域 -->
-            <div class="card-image">
-              <img v-if="item.imageUrl" :src="item.imageUrl" alt="设备图片" />
-              <div v-else class="image-placeholder">
-                <i :class="getInventoryIcon(item.inventoryType)" />
-              </div>
-            </div>
-            
             <!-- 信息区域 -->
             <div class="card-info">
               <div class="info-row">
@@ -105,6 +97,11 @@
                 <span class="info-label">编号:</span>
                 <span class="info-value">{{ item.inventoryNo || '-' }}</span>
               </div>
+              <div class="info-row">
+                <span class="info-label">类型:</span>
+                <span class="info-value">{{ item.inventoryType || '-' }}</span>
+                <i :class="getInventoryIcon(item.inventoryType)" class="type-icon" />
+              </div>
               <div class="info-row location-row">
                 <i class="fas fa-map-marker-alt location-icon" />
                 <span class="info-value">{{ item.positionName || '-' }} / {{ item.warehouseName || '-' }}</span>
@@ -212,7 +209,9 @@
         :disabled="selectedIds.length === 0"
         @click="handleComplete"
       >
-        领料 ({{ selectedIds.length }})
+        领料 
+        <!-- ({{ selectedIds.length }}) -->
+        <span v-if="selectedIds && selectedIds.length > 0">({{ selectedIds.length }})</span>
       </button>
     </div>
 
@@ -1106,18 +1105,28 @@ onMounted(() => {
 
 /* 信息区域 */
 .card-info {
-  padding: 10px 15px 15px;
+  padding: 47px 15px 15px;
 }
 
 .info-row {
   display: flex;
-  align-items: baseline;
-  margin-bottom: 6px;
+  align-items: center;
+  padding: 6px 0;
   font-size: 14px;
+  min-height: 28px;
+  border-bottom: 1px solid rgba(126, 200, 255, 0.2);
+  margin-bottom: 0;
 }
 
 .info-row:last-child {
-  margin-bottom: 0;
+  border-bottom: none;
+}
+
+/* 类型图标样式 */
+.type-icon {
+  color: #fbbf24;
+  margin-left: 8px;
+  font-size: 16px;
 }
 
 .info-label {
@@ -1507,7 +1516,7 @@ onMounted(() => {
   }
 
   .card-info {
-    padding: 6px 8px 8px;
+    padding: 30px 8px 8px;
   }
 
   .info-row {