Преглед изворни кода

将 antd 控件修改为 vant 控件

liuyanpeng пре 7 месеци
родитељ
комит
3b42127cc6

+ 3 - 2
package.json

@@ -45,15 +45,16 @@
     "webpack-merge": "^5.8.0"
   },
   "dependencies": {
-    "ant-design-vue": "^4.2.1",
+    "@popperjs/core": "^2.11.8",
     "axios": "^1.2.2",
     "brace-expansion": "^4.0.1",
     "dayjs": "^1.10.7",
     "jquery": "^3.6.0",
+    "vant": "^4.9.0",
     "vue": "^3.4.25",
     "vue-request": "^1.2.3",
     "vue-router": "^4.0.12",
-    "vant": "^4.9.0"
+    "vue-select": "^4.0.0-beta.6"
   },
   "publishConfig": {
     "access": "public",

+ 8 - 8
src/App.vue

@@ -1,14 +1,14 @@
 <template>
-  <a-config-provider :locale="locale">
-    <router-view />
-  </a-config-provider>
+  <!-- <a-config-provider :locale="locale"> -->
+  <router-view />
+  <!-- </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');
+// import zhCN from 'ant-design-vue/es/locale/zh_CN';
+// import dayjs from 'dayjs';
+// import 'dayjs/locale/zh-cn';
+// dayjs.locale('zh-cn');
 
 export default {
 
@@ -17,7 +17,7 @@ export default {
     },
     data() {
         return {
-            locale: zhCN,
+            // locale: zhCN,
 
         };
     },

+ 126 - 50
src/Fingerprint/FingerprintEnroll.vue

@@ -1,3 +1,4 @@
+<!-- 指纹录入 -->
 <template>
   <div class="fingerprint-wrapper">
     <PageHeader :show-back="true" :is-go-home="false" :is-custom-back="true" @back="handleBack" />
@@ -24,18 +25,27 @@
                 </van-cell-group>
               </div>
 
-              <a-button
-                type="primary" size="large" class="next-button" :loading="checking" block
+              <van-button
+                type="primary" size="large" class="next-button" :loading="checking" loading-text="验证中..." block
                 @click="handleNextStep"
               >
-                <span v-if="!checking">验证并继续</span>
-                <span v-else>验证中...</span>
-              </a-button>
+                验证并继续
+              </van-button>
             </div>
           </div>
 
           <!-- 第二部 指纹录入 -->
           <div v-else class="step-card step-two">
+            <!-- 设备状态 - 右上角 -->
+            <div class="device-status-top" @click="handleDeviceClick">
+              <div class="status-indicator" :class="{ connected: isConnected }">
+                <div class="status-dot" />
+                <span class="status-text">
+                  {{ isConnected ? '设备已连接' : '设备未连接' }}
+                </span>
+              </div>
+            </div>
+
             <div class="card-header">
               <div class="step-icon">
                 <i class="icon-fingerprint">👆</i>
@@ -52,16 +62,6 @@
             </div>
 
             <div class="fingerprint-section">
-              <!-- 设备状态 -->
-              <div class="device-status" @click="handleDeviceClick">
-                <div class="status-indicator" :class="{ connected: isConnected }">
-                  <div class="status-dot" />
-                  <span class="status-text">
-                    {{ isConnected ? '设备已连接' : '设备未连接' }}
-                  </span>
-                </div>
-              </div>
-
               <!-- 指纹 -->
               <div class="fingerprint-display">
                 <div class="fingerprint-scanner" :class="{ active: isConnected, scanning: isScanning }">
@@ -133,26 +133,20 @@
 
               <!-- Action Buttons -->
               <div class="action-section">
-                <a-button
+                <van-button
                   type="primary" size="large" class="enroll-button btn" :disabled="!isConnected"
-                  :style="isConnected ? 'color: #fff' : 'color: #534a93'" :loading="isScanning" block
+                  :loading="isScanning" loading-text="正在录入中..." block
                   @click="handleEnroll"
                 >
-                  <template v-if="!isScanning">
-                    <!-- <span class="button-icon"></span> -->
-                    指纹录入
-                  </template>
-                  <template v-else>
-                    正在录入中...
-                  </template>
-                </a-button>
-
-                <a-button size="large" class="reset-button btn" :disabled="!isConnected" block @click="handleReEnroll">
+                  指纹录入
+                </van-button>
+
+                <van-button size="large" class="reset-button btn" :disabled="!isConnected" block @click="handleReEnroll">
                   重新录入
-                </a-button>
-                <!-- <a-button size="large" class="reset-button" block @click="handleDisableConnect">
+                </van-button>
+                <!-- <van-button size="large" class="reset-button" block @click="handleDisableConnect">
                   断开连接
-                </a-button> -->
+                </van-button> -->
               </div>
             </div>
           </div>
@@ -528,7 +522,10 @@ body {
   color: #667eea;
   font-weight: 700;
   background: rgba(102, 126, 234, 0.1);
-  padding: 0.25rem 0.5rem;
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
+  padding-left: 0.5rem;
+  padding-right: 0.5rem;
   border-radius: 6px;
 }
 
@@ -556,16 +553,28 @@ body {
   cursor: pointer;
 }
 
+.device-status-top {
+  position: absolute;
+  top: clamp(1rem, 2vh, 1.5rem);
+  right: clamp(1rem, 2vw, 1.5rem);
+  cursor: pointer;
+  z-index: 10;
+}
+
 .status-indicator {
   display: flex;
   align-items: center;
-  gap: 0.75rem;
-  padding: 0.75rem 1.5rem;
+  gap: 0.8rem;
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+  padding-left: 1.8rem;
+  padding-right: 1.8rem;
   border-radius: 50px;
   background: rgba(239, 68, 68, 0.1);
   color: #dc2626;
-  font-weight: 600;
+  font-weight: 700;
   transition: all 0.3s ease;
+  font-size: 1.5rem;
 }
 
 .status-indicator.connected {
@@ -574,8 +583,8 @@ body {
 }
 
 .status-dot {
-  width: 0.75rem;
-  height: 0.75rem;
+  width: 12px;
+  height: 12px;
   border-radius: 50%;
   background: currentColor;
   animation: pulse 2s infinite;
@@ -598,13 +607,17 @@ body {
   display: flex;
   flex-direction: column;
   align-items: center;
-  gap: 1rem;
+  gap: 1.5rem;
+  margin-top: 1.5rem;
+  margin-bottom: 1.5rem;
+  margin-left: 0;
+  margin-right: 0;
 }
 
 .fingerprint-scanner {
   position: relative;
-  width: clamp(140px, 25vw, 200px);
-  height: clamp(140px, 25vw, 200px);
+  width: clamp(160px, 28vw, 230px);
+  height: clamp(160px, 28vw, 230px);
   border-radius: 50%;
   background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
   display: flex;
@@ -652,8 +665,8 @@ body {
 }
 
 .fingerprint-icon {
-  width: clamp(100px, 18vw, 140px);
-  height: clamp(100px, 18vw, 140px);
+  width: clamp(110px, 20vw, 160px);
+  height: clamp(110px, 20vw, 160px);
   color: #9ca3af;
   transition: color 0.3s ease;
 }
@@ -684,7 +697,7 @@ body {
 
 /* 动态扫描动画 */
 .fingerprint-scanner {
-  --scanner-size: clamp(140px, 25vw, 200px);
+  --scanner-size: clamp(150px, 28vw, 220px);
 }
 
 /* 横屏扫描动画 */
@@ -697,18 +710,21 @@ body {
 /* 竖屏大屏幕扫描动画 */
 @media (orientation: portrait) and (min-width: 768px) {
   .fingerprint-scanner {
-    --scanner-size: clamp(150px, 28vw, 220px);
+    --scanner-size: clamp(170px, 28vw, 220px);
   }
 }
 
 .instruction-text {
-  font-size: clamp(0.9rem, 2.5vw, 1.2rem);
+  font-size: 2rem;
   color: #6b7280;
   text-align: center;
-  font-weight: 500;
+  font-weight: 700;
   max-width: clamp(250px, 50vw, 400px);
-  line-height: 1.5;
-  margin: 10px 0;
+  line-height: 1.6;
+  margin-top: 15px;
+  margin-bottom: 15px;
+  margin-left: 0;
+  margin-right: 0;
 }
 
 /* 按钮 */
@@ -722,12 +738,13 @@ body {
 
 .enroll-button {
   height: clamp(3.5rem, 8vh, 5rem);
-  font-size: clamp(1rem, 2.5vw, 1.3rem);
+  font-size: 2rem;
   font-weight: 600;
   background: linear-gradient(135deg, #667eea, #764ba2);
   border: none;
   border-radius: clamp(8px, 1.5vw, 12px);
   transition: all 0.3s ease;
+  color: #fff;
 }
 
 .enroll-button:hover:not(:disabled) {
@@ -738,7 +755,7 @@ body {
 
 .reset-button {
   height: clamp(2.75rem, 5vh, 3.5rem);
-  font-size: clamp(0.9rem, 2vw, 1.1rem);
+  font-size: 2rem;
   font-weight: 500;
   background: white;
   border: 2px solid #e5e7eb;
@@ -749,6 +766,7 @@ body {
   align-items: center;
   justify-content: center;
   gap: 0.5rem;
+  padding:2rem 0;
 }
 
 .reset-button:hover:not(:disabled) {
@@ -870,11 +888,69 @@ body {
 }
 
 .btn {
-  margin: 10px 0;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  margin-left: 0;
+  margin-right: 0;
 }
 
 :deep(.van-field) {
   border: 1px solid #ccc;
   border-radius: 6px;
 }
+
+/* vant 按钮样式覆盖 */
+:deep(.van-button--block) {
+  display: block;
+  width: 100%;
+}
+
+:deep(.van-button__content) {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 验证按钮样式 */
+.next-button:deep(.van-button) {
+  height: clamp(3.5rem, 8vh, 5rem);
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  border: none;
+  padding: 0 1.5rem;
+}
+
+.next-button:deep(.van-button):active {
+  background: linear-gradient(135deg, #5a67d8, #6b46c1);
+}
+
+/* 指纹录入按钮样式 */
+.enroll-button:deep(.van-button) {
+  height: clamp(3.5rem, 8vh, 5rem);
+  background: linear-gradient(135deg, #667eea, #764ba2);
+  border: none;
+  padding: 0 1.5rem;
+}
+
+.enroll-button:deep(.van-button):active {
+  background: linear-gradient(135deg, #5a67d8, #6b46c1);
+}
+
+/* 重新录入按钮样式 */
+.reset-button:deep(.van-button) {
+  height: clamp(2.75rem, 5vh, 3.5rem);
+  background: white;
+  border: 2px solid #e5e7eb !important;
+  color: #6b7280;
+  padding: 0 1.5rem;
+}
+
+.reset-button:deep(.van-button):active {
+  border-color: #667eea !important;
+  color: #667eea;
+  background: rgba(102, 126, 234, 0.05);
+}
+
+.reset-button:deep(.van-button--disabled) {
+  opacity: 0.5;
+}
 </style>

+ 86 - 36
src/agv-process/DeliveryManagement.vue

@@ -1,15 +1,11 @@
+<!-- 取料区管理 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="取料区管理" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
-      <!-- 页面标题 -->
-      <div class="page-title">
-        <h2>取料区管理</h2>
-      </div>
-
       <div class="stations-container">
         <div class="stations-grid">
           <div
@@ -19,15 +15,15 @@
           >
             <div class="card-header">
               <div class="card-title">{{ station.positionName }}</div>
-              <a-tag v-if="station.positionStatus === '空闲中'" class="card-status" color="green">
+              <van-tag v-if="station.positionStatus === '空闲中'" class="card-status" type="success">
                 {{ station.positionStatus }}
-              </a-tag>
-              <a-tag v-if="station.positionStatus === '已占用'" class="card-status" color="red">
+              </van-tag>
+              <van-tag v-if="station.positionStatus === '已占用'" class="card-status" type="danger">
                 {{ station.positionStatus }}
-              </a-tag>
-              <a-tag v-if="station.positionStatus === '待入库'" class="card-status" color="orange">
+              </van-tag>
+              <van-tag v-if="station.positionStatus === '待入库'" class="card-status" type="warning">
                 {{ station.positionStatus }}
-              </a-tag>
+              </van-tag>
             </div>
 
             <!-- 卡片内容 -->
@@ -44,15 +40,21 @@
               </div>
 
               <!-- 空闲状态 - 显示空闲图标 -->
-              <a-empty v-else description="暂无物料车" />
+              <van-empty v-else description="暂无物料车" />
             </div>
           </div>
         </div>
       </div>
     </main>
-    <a-modal v-model:open="visible" title="提示" @ok="takeAway" @cancel="cancelTask">
-      <p>请确认您是否要取走【{{ positionName }}】的料车,如果确认请点击【确认】按钮。</p>
-    </a-modal>
+    <!-- 确认取走弹窗 -->
+    <van-dialog
+      v-model:show="visible" title="确认取走" show-cancel-button class-name="large-dialog"
+      confirm-button-text="确认" cancel-button-text="取消" @confirm="takeAway" @cancel="cancelTask"
+    >
+      <div class="large-dialog-content">
+        <p>请确认您是否要取走【{{ positionName }}】的料车?</p>
+      </div>
+    </van-dialog>
 
     <Loading v-if="loading" />
   </div>
@@ -60,7 +62,7 @@
 
 <script setup>
 import { ref, onMounted } from 'vue';
-import { message } from 'ant-design-vue';
+import { showNotify } from 'vant';
 import PageHeader from '../common/PageHeader.vue';
 import { getLocatorFeedAreaStatus, takeAwaySkip } from '../api/agv.js';
 
@@ -79,7 +81,7 @@ const positionNo = ref('');
 // 打开弹窗
 const openStation = station => {
     if (station.positionStatus === '空闲中' || station.positionStatus === '待入库') {
-        message.warning('请选择占用中的还料区');
+        showNotify({ type: 'warning', message: '请选择占用中的还料区' });
     } else {
         visible.value = true;
         positionName.value = station.positionName;
@@ -94,15 +96,15 @@ const takeAway = async () => {
     try {
         const res = await takeAwaySkip(positionNo.value);
         if (res.errorCode === 0) {
-            message.success('料车取走成功');
+            showNotify({ type: 'success', message: '料车取走成功' });
             visible.value = false;
             await getStations();
         } else {
-            message.warning(res.errorMessage || '料车取走失败');
+            showNotify({ type: 'warning', message: res.errorMessage || '料车取走失败' });
         }
     } catch (error) {
         console.error('料车取走失败:', error);
-        message.error('料车取走失败');
+        showNotify({ type: 'danger', message: '料车取走失败' });
     } finally {
         loading.value = false;
     }
@@ -124,12 +126,12 @@ const getStations = async () => {
                 stations.value = [];
             }
         } else {
-            message.warn(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('获取还料位数据失败:', error);
-        message.error('获取还料位数据失败');
+        showNotify({ type: 'danger', message: '获取还料位数据失败' });
     } finally {
         loading.value = false;
     }
@@ -154,23 +156,11 @@ onMounted(() => {
 .main-content {
     flex: 1;
     overflow-y: auto;
-    padding: 1.5rem;
+    padding: 1rem;
     display: flex;
     flex-direction: column;
 }
 
-/* 页面标题 */
-.page-title {
-    margin-bottom: 1.5rem;
-}
-
-.page-title h2 {
-    font-size: 1.5rem;
-    font-weight: 700;
-    color: #111827;
-    margin: 0;
-}
-
 /* 还料位容器 */
 .stations-container {
     flex: 1;
@@ -334,4 +324,64 @@ onMounted(() => {
 .ml-2 {
     margin-left: 0.5rem;
 }
+
+/* 大字体防误触弹窗样式 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  padding: 1.5rem 1.5rem 1rem !important;
+  color: #111827 !important;
+}
+
+:deep(.large-dialog .van-dialog__content) {
+  padding: 1rem 1.5rem 1.5rem !important;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding: 1rem 1.5rem 1.5rem !important;
+  display: flex !important;
+  gap: 1rem !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1 !important;
+  height: 48px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #3b82f6 !important;
+  color: white !important;
+  border: none !important;
+}
+
+.large-dialog-content {
+  min-height: 80px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.large-dialog-content p {
+  margin: 0;
+  font-size: 1.125rem;
+  line-height: 1.8;
+  color: #374151;
+  text-align: center;
+}
 </style>

+ 146 - 48
src/agv-process/ReturnManagement.vue

@@ -1,15 +1,11 @@
+<!-- 还料区管理 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="还料区管理" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
-      <!-- 页面标题 -->
-      <div class="page-title">
-        <h2>还料区管理</h2>
-      </div>
-
       <div class="stations-container">
         <div class="stations-grid">
           <div
@@ -19,12 +15,12 @@
           >
             <div class="card-header">
               <div class="card-title">{{ station.positionName }}</div>
-              <a-tag v-if="station.positionStatus === '空闲中'" class="card-status" color="green">
+              <van-tag v-if="station.positionStatus === '空闲中'" class="card-status" type="success">
                 {{ station.positionStatus }}
-              </a-tag>
-              <a-tag v-if="station.positionStatus === '已占用'" class="card-status" color="red">
+              </van-tag>
+              <van-tag v-if="station.positionStatus === '已占用'" class="card-status" type="danger">
                 {{ station.positionStatus }}
-              </a-tag>
+              </van-tag>
             </div>
 
             <!-- 卡片内容 -->
@@ -41,21 +37,33 @@
               </div>
 
               <!-- 空闲状态 - 显示空闲图标 -->
-              <a-empty v-else description="暂无物料车" />
+              <van-empty v-else description="暂无物料车" />
             </div>
           </div>
         </div>
       </div>
     </main>
-    <a-modal v-model:open="visible" title="提示" @ok="generateTask" @cancel="cancelTask">
-      <p>请确认您已经把待归还的料车推到【 {{ positionName }} 】上,请输入料车编号,完成以后点击【确认】按钮。</p>
-      <br />
-      <a-select
-        v-model:value="truckNo" show-search placeholder="请输入料车编号搜索" :default-active-first-option="false"
-        :filter-option="false" :not-found-content="null" :options="truckNos" style="width: 100%;"
-        @search="handleSearch" @change="handleChange"
-      />
-    </a-modal>
+    <!-- 确认还料弹窗 -->
+    <van-dialog
+      v-model:show="visible" title="确认还料" show-cancel-button class-name="large-dialog"
+      confirm-button-text="确认" cancel-button-text="取消" @confirm="generateTask" @cancel="cancelTask"
+    >
+      <div class="large-dialog-content">
+        <p>请确认您已经把待归还的料车推到【{{ positionName }}】上,请输入料车编号:</p>
+        <div class="dialog-form-item">
+          <v-select
+            v-model="truckNo" :options="truckNos" :reduce="item => item.value" label="label"
+            placeholder="请输入料车编号搜索" :clearable="true" :filterable="true"
+            class="dialog-select"
+            @search="handleSearch"
+          >
+            <template #no-options>
+              <span>{{ hasSearched ? '无该编号相关数据' : '请输入料车编号进行搜索' }}</span>
+            </template>
+          </v-select>
+        </div>
+      </div>
+    </van-dialog>
 
     <Loading v-if="loading" />
   </div>
@@ -63,9 +71,11 @@
 
 <script setup>
 import { ref, onMounted } from 'vue';
-import { message } from 'ant-design-vue';
+import { showNotify } from 'vant';
 import PageHeader from '../common/PageHeader.vue';
 import { getLocatorStatus, getNoInStorageTruck, generateTaskByBlankNo } from '../api/agv.js';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
 
 
 // 加载状态
@@ -82,12 +92,14 @@ const positionNo = ref('');
 const truckNo = ref('');
 
 const truckNos = ref([]);
+const hasSearched = ref(false);
 let searchTimeout = null;
 
 // 远程搜索料车(带防抖)
 const handleSearch = value => {
     if (!value || value.trim() === '') {
         truckNos.value = [];
+        hasSearched.value = false;
         return;
     }
 
@@ -100,6 +112,7 @@ const handleSearch = value => {
     searchTimeout = setTimeout(async () => {
         try {
             const res = await getNoInStorageTruck(value);
+            hasSearched.value = true;
             if (res.errorCode === 0) {
                 if (res.datas && res.datas.length > 0) {
                     // 将数据转换为 a-select 需要的格式
@@ -111,7 +124,7 @@ const handleSearch = value => {
                     truckNos.value = [];
                 }
             } else {
-                message.warning(res.errorMessage || '搜索料车失败');
+                showNotify({ type: 'warning', message: res.errorMessage || '搜索料车失败' });
                 truckNos.value = [];
             }
         } catch (error) {
@@ -121,16 +134,10 @@ const handleSearch = value => {
     }, 500);
 };
 
-// 选择料车
-const handleChange = value => {
-    console.log('选择的料车编号:', value);
-    truckNo.value = value;
-};
-
 // 打开弹窗
 const openStation = station => {
     if (station.positionStatus === '已占用') {
-        message.warning('请选择空闲的还料区');
+        showNotify({ type: 'warning', message: '请选择空闲的还料区' });
     } else {
         visible.value = true;
         positionName.value = station.positionName;
@@ -138,13 +145,14 @@ const openStation = station => {
         // 重置选择
         truckNo.value = '';
         truckNos.value = [];
+        hasSearched.value = false;
     }
 };
 
 // 根据还料货位和料车编号生成归还单和调度任务
 const generateTask = async () => {
     if (!truckNo.value) {
-        message.warning('请输入料车编号');
+        showNotify({ type: 'warning', message: '请输入料车编号' });
         return;
     }
 
@@ -157,17 +165,17 @@ const generateTask = async () => {
     try {
         const res = await generateTaskByBlankNo(params);
         if (res.errorCode === 0) {
-            message.success('绑定成功,等待AGV归还');
+            showNotify({ type: 'success', message: '绑定成功,等待AGV归还' });
             visible.value = false;
             truckNo.value = '';
             truckNos.value = [];
             await getStations();
         } else {
-            message.warning(res.errorMessage || '绑定失败');
+            showNotify({ type: 'warning', message: res.errorMessage || '绑定失败' });
         }
     } catch (error) {
         console.error('绑定失败:', error);
-        message.error('绑定失败');
+        showNotify({ type: 'danger', message: '绑定失败' });
     } finally {
         loading.value = false;
     }
@@ -177,6 +185,7 @@ const generateTask = async () => {
 const cancelTask = () => {
     truckNo.value = '';
     truckNos.value = [];
+    hasSearched.value = false;
     visible.value = false;
 };
 // 获取还料位数据
@@ -191,12 +200,12 @@ const getStations = async () => {
                 stations.value = [];
             }
         } else {
-            message.warn(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('获取还料位数据失败:', error);
-        message.error('获取还料位数据失败');
+        showNotify({ type: 'danger', message: '获取还料位数据失败' });
     } finally {
         loading.value = false;
     }
@@ -221,23 +230,11 @@ onMounted(() => {
 .main-content {
     flex: 1;
     overflow-y: auto;
-    padding: 1.5rem;
+    padding: 1rem;
     display: flex;
     flex-direction: column;
 }
 
-/* 页面标题 */
-.page-title {
-    margin-bottom: 1.5rem;
-}
-
-.page-title h2 {
-    font-size: 1.5rem;
-    font-weight: 700;
-    color: #111827;
-    margin: 0;
-}
-
 /* 还料位容器 */
 .stations-container {
     flex: 1;
@@ -401,4 +398,105 @@ onMounted(() => {
 .ml-2 {
     margin-left: 0.5rem;
 }
+
+/* 大字体防误触弹窗样式 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  padding: 1.5rem 1.5rem 1rem !important;
+  color: #111827 !important;
+}
+
+:deep(.large-dialog .van-dialog__content) {
+  padding: 1rem 1.5rem 1.5rem !important;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding: 1rem 1.5rem 1.5rem !important;
+  display: flex !important;
+  gap: 1rem !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1 !important;
+  height: 48px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #3b82f6 !important;
+  color: white !important;
+  border: none !important;
+}
+
+.large-dialog-content {
+  min-height: 100px;
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+}
+
+.large-dialog-content p {
+  margin: 0;
+  font-size: 1.125rem;
+  line-height: 1.8;
+  color: #374151;
+  text-align: center;
+}
+
+.dialog-form-item {
+  width: 100%;
+  margin-top: 0.5rem;
+}
+
+/* v-select 样式 */
+.dialog-select {
+  font-size: 1rem;
+}
+
+:deep(.dialog-select .vs__dropdown-toggle) {
+  border: 1px solid #d1d5db;
+  border-radius: 6px;
+  padding: 8px 12px;
+  min-height: 44px;
+  background: white;
+}
+
+:deep(.dialog-select .vs__search) {
+  font-size: 1rem;
+  padding: 0;
+  margin: 0;
+}
+
+:deep(.dialog-select .vs__selected) {
+  margin: 2px;
+  padding: 0 4px;
+  font-size: 1rem;
+}
+
+:deep(.dialog-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+  border: 1px solid #d1d5db;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+  font-size: 1rem;
+}
+
+:deep(.dialog-select .vs__dropdown-option) {
+  padding: 10px 12px;
+}
 </style>

+ 30 - 21
src/common/Common.js

@@ -1,5 +1,5 @@
 import { Notify } from 'pc-component-v3';
-import { notification } from 'ant-design-vue';
+import { showNotify } from 'vant';
 
 export default {
     pageSize: 20,
@@ -23,11 +23,17 @@ export default {
                 const appName = this.getRouteParam('appName');
                 const corpId = this.getRouteParam('corpId');
 
+                // 根据环境判断使用不同的 URL
+                const isProduction = process.env.APP_ENV === 'production';
+                const loginPath = isProduction ? '/board.html#/login' : '#/login';
+                
                 let newUrl;
                 if(clientId != null && clientId.length > 0 && appName != null && appName.length > 0 && corpId != null && corpId.length > 0){
-                    newUrl = _self.getRedirectUrl('#/login?clientId=' + clientId + '&appName=' + appName + '&corpId=' + corpId + '&redirectUrl=' + encodeURIComponent(currentUrl));
+                    newUrl = _self.getRedirectUrl(loginPath + '?clientId=' + clientId + '&appName=' + appName + '&corpId=' + corpId + '&redirectUrl=' +
+                         encodeURIComponent(currentUrl));
                 }else{
-                    newUrl = _self.getRedirectUrl('#/login?redirectUrl=' + encodeURIComponent(currentUrl));
+                    alert('未获取到钉钉免登陆参数');
+                    newUrl = _self.getRedirectUrl(loginPath + '?redirectUrl=' + encodeURIComponent(currentUrl));
                 }
                 window.location = newUrl;
             }
@@ -37,7 +43,9 @@ export default {
             if (XMLHttpRequest.responseText.indexOf('登录超时') > 0) {
                 // 如果异常信息包含“登录超时”,则2秒后跳转到登录页面
                 setTimeout(function () {
-                    window.location = _self.getRedirectUrl('#/login');
+                    const isProduction = process.env.APP_ENV === 'production';
+                    const loginPath = isProduction ? '/board.html#/login' : '#/login';
+                    window.location = _self.getRedirectUrl(loginPath);
                 }, 2 * 1000);
             }
         } else {
@@ -439,10 +447,11 @@ export default {
  */
 export function requestFailed(err) {
     console.error(err);
-    notification['error']({
-        message: '错误',
-        description: ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试',
-        duration: 8,
+    const errorMessage = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试';
+    showNotify({
+        type: 'danger',
+        message: `错误: ${errorMessage}`,
+        duration: 3000,
     });
 }
 
@@ -452,10 +461,10 @@ export function requestFailed(err) {
  */
 export function requestSuccess(response) {
     if (response.errorCode !== 0) {
-        notification['error']({
-            message: '错误',
-            description: response.errorMessage,
-            duration: 8,
+        showNotify({
+            type: 'danger',
+            message: `错误: ${response.errorMessage}`,
+            duration: 3000,
         });
     }
 }
@@ -465,23 +474,23 @@ export function requestSuccess(response) {
  * @param {} response 
  */
 export function notificationError(message, title) {
-    notification['error']({
-        message: title || '操作失败',
-        description: message,
-        duration: 8,
+    showNotify({
+        type: 'danger',
+        message: title ? `${title}: ${message}` : message,
+        duration: 3000,
     });
 }
 
 
 /**
- * 错误提示
+ * 成功提示
  * @param {} response 
  */
 export function notificationSuccess(message, title) {
-    notification['success']({
-        message: title || '操作成功',
-        description: message,
-        duration: 8,
+    showNotify({
+        type: 'success',
+        message: title ? `${title}: ${message}` : message,
+        duration: 3000,
     });
 }
 

+ 12 - 3
src/common/FilterPanel.vue

@@ -1,6 +1,7 @@
+<!-- 筛选区域 -->
 <template>
   <div class="filter-panel">
-    <div class="filter-header" @click="toggleCollapse">
+    <div v-if="enableCollapse" class="filter-header" @click="toggleCollapse">
       <div class="filter-title">
         <i class="fas fa-filter mr-2" />
         <span>筛选条件</span>
@@ -12,7 +13,7 @@
       </div>
     </div>
     
-    <div v-show="!collapsed" class="filter-content">
+    <div v-show="!collapsed" class="filter-content" :class="{ 'no-border': !enableCollapse }">
       <slot />
     </div>
   </div>
@@ -30,6 +31,10 @@ const props = defineProps({
         type: Number,
         default: 0,
     },
+    enableCollapse: {
+        type: Boolean,
+        default: true,
+    },
 });
 
 const collapsed = ref(props.defaultCollapsed);
@@ -47,7 +52,7 @@ const toggleCollapse = () => {
   border-radius: 0.5rem;
   box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
   margin-bottom: 1.5rem;
-  overflow: hidden;
+  overflow: visible;
 }
 
 .filter-header {
@@ -85,6 +90,10 @@ const toggleCollapse = () => {
   animation: slideDown 0.3s ease-out;
 }
 
+.filter-content.no-border {
+  border-top: none;
+}
+
 @keyframes slideDown {
   from {
     opacity: 0;

+ 0 - 164
src/common/InventoryCard.vue

@@ -1,164 +0,0 @@
-<template>
-  <a-card
-    :hoverable="hoverable"
-    :class="{ 'selected-card': isSelected }"
-    class="inventory-card"
-    @click="$emit('card-click')"
-  >
-    <template #title>
-      <div class="flex items-center justify-between">
-        <div class="flex items-center flex-1">
-          <a-checkbox
-            v-if="selectable"
-            :checked="isSelected"
-            @click.stop
-            @change="e => $emit('checkbox-change', e)"
-          />
-          <div :class="selectable ? 'ml-3' : ''" class="flex flex-col">
-            <div class="card-title-line">
-              <span class="card-name">{{ item.inventoryName || item.name }}</span>
-            </div>
-            <div class="card-subtitle">
-              编号: {{ item.inventoryNo || item.no }}
-            </div>
-          </div>
-        </div>
-        <a-avatar :size="36" :style="{ backgroundColor: iconColor }">
-          <template #icon>
-            <i :class="icon" style="font-size: 22px;" />
-          </template>
-        </a-avatar>
-      </div>
-    </template>
-
-    <div class="card-content">
-      <div class="card-main-info">
-        <i class="fas fa-map-marker-alt text-blue-500 mr-2" />
-        <span class="location-text">
-          {{ location }}
-        </span>
-      </div>
-      <slot></slot>
-    </div>
-  </a-card>
-</template>
-
-<script setup>
-import { defineProps, defineEmits, computed } from 'vue';
-
-const props = defineProps({
-  item: {
-    type: Object,
-    required: true,
-  },
-  isSelected: {
-    type: Boolean,
-    default: false,
-  },
-  selectable: {
-    type: Boolean,
-    default: true,
-  },
-  hoverable: {
-    type: Boolean,
-    default: true,
-  },
-  iconColor: {
-    type: String,
-    default: '#3b82f6',
-  },
-});
-
-const emit = defineEmits(['card-click', 'checkbox-change']);
-
-const icon = computed(() => {
-  const type = props.item.inventoryType || props.item.type;
-  const iconMap = {
-    '工装': 'fas fa-cube',
-    '设备': 'fas fa-cogs',
-    '成品': 'fas fa-box',
-  };
-  return iconMap[type] || 'fas fa-cube';
-});
-
-const location = computed(() => {
-  const position = props.item.inventoryActulPosition || props.item.inventoryPosition || props.item.positionName || '-';
-  const warehouse = props.item.inventoryWarehouse || props.item.warehouseName || '-';
-  return `${position} / ${warehouse}`;
-});
-</script>
-
-<style scoped>
-.inventory-card {
-  cursor: pointer;
-  transition: all 0.3s ease;
-  border: 2px solid transparent;
-  background-color: #eef4ff;
-}
-
-.inventory-card:hover {
-  transform: translateY(-2px);
-}
-
-.inventory-card.selected-card {
-  border-color: #3b82f6 !important;
-  background-color: #dbeafe;
-  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
-}
-
-.card-content {
-  margin-top: -8px;
-}
-
-.card-title-line {
-  display: flex;
-  align-items: baseline;
-  gap: 8px;
-}
-
-.card-name {
-  font-size: 1.25rem;
-  font-weight: 700;
-  color: #1f2937;
-  line-height: 1.4;
-}
-
-.card-subtitle {
-  font-size: 0.8rem;
-  color: #9ca3af;
-  margin-top: 2px;
-  line-height: 1.2;
-}
-
-.card-main-info {
-  display: flex;
-  align-items: center;
-  padding: 8px 0;
-  color: #6b7280;
-}
-
-.card-main-info i {
-  font-size: 14px;
-  flex-shrink: 0;
-}
-
-.location-text {
-  font-size: 0.875rem;
-  color: #6b7280;
-  line-height: 1.5;
-  margin-left: 6px;
-}
-
-:deep(.ant-card-head-title) {
-  padding: 16px 0 2px 0;
-}
-
-:deep(.ant-card-body) {
-  padding: 12px 20px 16px 26px;
-}
-
-:deep(.ant-card-head) {
-  border-bottom: none;
-  min-height: auto;
-}
-</style>

+ 64 - 10
src/common/PageHeader.vue

@@ -1,3 +1,4 @@
+<!-- 顶部信息栏 -->
 <template>
   <header class="page-header">
     <div class="header-left">
@@ -10,6 +11,9 @@
         <!-- <span class="font-medium">操作员:{{ operatorName }} (ID: {{ operatorId }})</span> -->
       </div>
     </div>
+    <div v-if="title" class="header-center">
+      <h1 class="page-header-title">{{ title }}</h1>
+    </div>
     <div class="header-right">
       <!-- 自定义插槽,用于插入额外按钮(如领料车) -->
       <slot name="actions" />
@@ -51,6 +55,10 @@ const props = defineProps({
         type: Boolean,
         default: false,
     },
+    title: {
+        type: String,
+        default: '',
+    },
 });
 
 const emit = defineEmits(['back', 'logout']);
@@ -129,7 +137,10 @@ const goToFingerprintEnroll = () => {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 1rem 1.5rem;
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+  padding-left: 1.5rem;
+  padding-right: 1.5rem;
   background-color: white;
   box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
   position: sticky;
@@ -140,13 +151,44 @@ const goToFingerprintEnroll = () => {
 .header-left {
   display: flex;
   align-items: center;
-  gap: 1rem;
+  flex: 1;
+}
+
+.header-left > * {
+  margin-right: 1rem;
+}
+
+.header-left > *:last-child {
+  margin-right: 0;
+}
+
+.header-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+}
+
+.page-header-title {
+  font-size: 1.5rem;
+  font-weight: 700;
+  color: #111827;
+  margin: 0;
 }
 
 .header-right {
   display: flex;
   align-items: center;
-  gap: 0.75rem;
+  flex: 1;
+  justify-content: flex-end;
+}
+
+.header-right > * {
+  margin-left: 0.75rem;
+}
+
+.header-right > *:first-child {
+  margin-left: 0;
 }
 
 .user-info {
@@ -155,7 +197,10 @@ const goToFingerprintEnroll = () => {
 }
 
 .action-btn {
-  padding: 0.65rem 1rem;
+  padding-top: 0.65rem;
+  padding-bottom: 0.65rem;
+  padding-left: 1rem;
+  padding-right: 1rem;
   border-radius: 0.5rem;
   font-weight: 500;
   transition: all 0.2s;
@@ -192,7 +237,11 @@ const goToFingerprintEnroll = () => {
 .settings-btn {
   background-color: #f3f4f6;
   color: #374151;
-  padding: 0.8rem;
+  padding-top: 0.8rem;
+  padding-bottom: 0.8rem;
+  padding-left: 0.8rem;
+  padding-right: 0.8rem;
+  /* margin-right: 0.75rem; */
 }
 
 .settings-btn:hover {
@@ -200,7 +249,7 @@ const goToFingerprintEnroll = () => {
 }
 
 .settings-btn i {
-  font-size: 16px;
+  font-size: 24px;
 }
 
 .dropdown-menu {
@@ -210,7 +259,7 @@ const goToFingerprintEnroll = () => {
   background-color: white;
   border-radius: 0.5rem;
   box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
-  min-width: 160px;
+  min-width: 200px;
   z-index: 1000;
   overflow: hidden;
   animation: dropdownFadeIn 0.2s ease;
@@ -228,13 +277,17 @@ const goToFingerprintEnroll = () => {
 }
 
 .dropdown-item {
-  padding: 0.75rem 1rem;
+  padding-top: 1.2rem;
+  padding-bottom: 1.2rem;
+  padding-left: 1.5rem;
+  padding-right: 1.5rem;
   display: flex;
   align-items: center;
   cursor: pointer;
   transition: background-color 0.2s;
   color: #374151;
-  font-size: 14px;
+  font-size: 1.3rem;
+  font-weight: 600;
 }
 
 .dropdown-item:hover {
@@ -243,6 +296,7 @@ const goToFingerprintEnroll = () => {
 
 .dropdown-item i {
   color: #3b82f6;
-  font-size: 14px;
+  font-size: 1.2rem;
+  margin-right: 0.75rem;
 }
 </style>

+ 384 - 116
src/finishProduct/FinishProductIn.vue

@@ -1,21 +1,20 @@
+<!-- 成品入库 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="成品入库" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
       <!-- 页面标题和操作按钮 -->
       <div class="page-header-row">
         <div class="page-title">
-          <h2>成品入库</h2>
+          <h2 />
         </div>
-        <a-button type="primary" @click="handleSubmit">
-          <template #icon>
-            <i class="fas fa-upload mr-1" />
-          </template>
+        <van-button type="primary" style="width: 109px" @click="handleSubmit">
+          <i class="fas fa-upload mr-1" />
           提交
-        </a-button>
+        </van-button>
       </div>
 
       <!-- 顶部统计与新增按钮 -->
@@ -23,50 +22,42 @@
         <div class="top-actions-info">
           当前已添加 <span class="font-semibold">{{ products.length }}</span> 条成品记录
         </div>
-        <a-button @click="openModal">
-          <template #icon>
-            <i class="fas fa-plus-circle mr-1" />
-          </template>
+        <van-button @click="openModal">
+          <i class="fas fa-plus-circle mr-1" />
           添加成品
-        </a-button>
+        </van-button>
       </div>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
-          <a-empty v-if="products.length === 0" description="请添加成品" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="products.length === 0" description="请添加成品" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card v-for="(product, index) in products" :key="product.code || index" class="inventory-card">
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }">
-                      <template #icon>
-                        <i class="fas fa-box" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ product.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ product.inventoryNo }}</div>
-                    </div>
+            <div v-for="(product, index) in products" :key="product.code || index" class="inventory-card">
+              <div class="card-header">
+                <div class="card-header-left">
+                  <div class="custom-avatar">
+                    <i class="fas fa-box" />
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ product.positionName }}</span>
-                    </div>
-                    <a-button type="text" danger size="small" class="delete-btn" @click.stop="removeProduct(index)">
-                      <template #icon>
-                        <i class="fas fa-trash" />
-                      </template>
-                    </a-button>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ product.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ product.inventoryNo }}</div>
                   </div>
                 </div>
-              </template>
-            </a-card>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ product.positionName }}</span>
+                  </div>
+                  <van-button type="danger" size="small" class="delete-btn" @click.stop="openDeleteDialog(index)">
+                    <i class="fas fa-trash" />
+                  </van-button>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
 
@@ -78,57 +69,80 @@
     </main>
 
     <!-- 新增成品弹窗 -->
-    <a-modal v-model:open="isModalOpen" title="新增成品" :mask-closable="false" width="480px" @cancel="closeModal">
-      <a-form layout="vertical" @submit.prevent>
-        <a-form-item label="成品名称">
-          <a-input v-model:value="newProduct.inventoryName" placeholder="请输入成品名称" allow-clear />
-        </a-form-item>
-        <a-form-item label="成品编号">
-          <a-input v-model:value="newProduct.inventoryNo" placeholder="请输入成品编号" allow-clear />
-        </a-form-item>
-        <a-form-item label="货位选择">
-          <a-select
-            v-model:value="newProduct.positionId" placeholder="请选择货位" show-search
-            :options="warehouseLocationOptions" allow-clear
-            :field-names="{ label: 'positionName', value: 'positionId' }" option-filter-prop="positionName"
-          />
-        </a-form-item>
-      </a-form>
-
-      <template #footer>
-        <a-button @click="closeModal">
-          取消
-        </a-button>
-        <a-button type="primary" :disabled="!isFormValid" @click="saveProduct">
-          保存
-        </a-button>
-      </template>
-    </a-modal>
-
-    <a-modal
-      v-model:open="showConfirmModal" title="确认入库" :mask-closable="false" width="420px"
-      @cancel="showConfirmModal = false"
+    <van-dialog
+      v-model:show="isModalOpen" title="新增成品" show-cancel-button
+      confirm-button-text="保存" cancel-button-text="取消"
+      :confirm-button-disabled="!isFormValid"
+      class-name="large-dialog"
+      @confirm="saveProduct" @cancel="closeModal"
+    >
+      <div class="large-dialog-form">
+        <div class="large-form-item">
+          <label class="large-form-label">成品名称</label>
+          <van-field v-model="newProduct.inventoryName" placeholder="请输入成品名称" clearable class="large-input" />
+        </div>
+        <div class="large-form-item">
+          <label class="large-form-label">成品编号</label>
+          <van-field v-model="newProduct.inventoryNo" placeholder="请输入成品编号" clearable class="large-input" />
+        </div>
+        <div class="large-form-item">
+          <label class="large-form-label">货位选择</label>
+          <v-select
+            v-model="newProduct.positionId" :options="warehouseLocationOptions"
+            :reduce="item => item.positionId" label="positionName"
+            placeholder="请选择货位" :clearable="true" :filterable="true"
+            class="large-select"
+            append-to-body
+          >
+            <template #no-options>
+              <span>无该选项数据</span>
+            </template>
+          </v-select>
+        </div>
+      </div>
+    </van-dialog>
+
+    <!-- 确认入库弹窗 -->
+    <van-dialog
+      v-model:show="showConfirmModal" title="确认入库" show-cancel-button
+      confirm-button-text="确认入库" cancel-button-text="取消"
+      class-name="large-dialog"
+      @confirm="executeInbound" @cancel="showConfirmModal = false"
+    >
+      <div class="dialog-content">
+        <p>
+          您确定要将选中的
+          <span class="font-semibold">{{ products.length }}</span>
+          个成品进行入库操作吗?
+        </p>
+      </div>
+    </van-dialog>
+
+    <!-- 删除成品确认弹窗 -->
+    <van-dialog
+      v-model:show="showDeleteModal" title="确认删除" show-cancel-button
+      confirm-button-text="确认" cancel-button-text="取消"
+      class-name="large-dialog"
+      @confirm="confirmDelete" @cancel="showDeleteModal = false"
     >
-      <p class="text-gray-700 mb-4">
-        您确定要将选中的
-        <span class="font-semibold">{{ products.length }}</span>
-        个成品进行入库操作吗?
-      </p>
-      <template #footer>
-        <a-button @click="showConfirmModal = false">取消</a-button>
-        <a-button type="primary" @click="executeInbound">确认入库</a-button>
-      </template>
-    </a-modal>
+      <div class="dialog-content">
+        <p>
+          你确认要删除掉该成品吗
+        </p>
+      </div>
+    </van-dialog>
 
     <Loading v-if="loading" />
   </div>
 </template>
 
 <script setup>
-import { ref, reactive, computed, onMounted } from 'vue';
-import { message, Empty, Modal } from 'ant-design-vue';
+import { ref, reactive, onMounted, computed } from 'vue';
+import { showNotify } from 'vant';
 import PageHeader from '../common/PageHeader.vue';
 import { generateStockIn, getLocatorList } from '../api/finishProduct';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
 
 // 加载状态(预留给后续真实接口调用)
 const loading = ref(false);
@@ -139,6 +153,8 @@ const warehouseLocationOptions = ref([]);
 // 弹窗显示状态
 const isModalOpen = ref(false);
 const showConfirmModal = ref(false);
+const showDeleteModal = ref(false);
+const deleteIndex = ref(-1);
 
 // 成品列表
 const products = ref([]);
@@ -180,7 +196,7 @@ const resetForm = () => {
 // 保存成品
 const saveProduct = () => {
     if (!isFormValid.value) {
-        message.warning('请完整填写成品信息');
+        showNotify({ type: 'warning', message: '请完整填写成品信息' });
         return;
     }
 
@@ -197,14 +213,14 @@ const saveProduct = () => {
         positionName: location ? location.positionName + ' / ' + location.warehouseName : '',
     });
 
-    message.success('成品已添加');
+    showNotify({ type: 'success', message: '成品已添加' });
     isModalOpen.value = false;
 };
 
 // 执行入库
 const handleSubmit = () => {
     if (products.value.length === 0) {
-        message.warning('暂无可提交的成品,请添加成品');
+        showNotify({ type: 'warning', message: '暂无可提交的成品,请添加成品' });
         return;
     }
     showConfirmModal.value = true;
@@ -225,36 +241,34 @@ const executeInbound = async () => {
     try {
         const res = await generateStockIn(params);
         if (res.errorCode === 0) {
-            message.success(`已提交 ${count} 条成品入库记录`);
+            showNotify({ type: 'success', message: `已提交 ${count} 条成品入库记录` });
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage || '添加失败' });
         }
         products.value = [];
         showConfirmModal.value = false;
     } catch (error) {
         console.log(error);
-        message.error('提交入库失败');
+        showNotify({ type: 'danger', message: '添加成品失败' });
     } finally {
         loading.value = false;
     }
 };
 
-// 删除成品
-const removeProduct = index => {
-    Modal.confirm({
-        title: '确定删除这条成品吗?',
-        content: '如果确认的话请点击【确定】',
-        okText: '确定',
-        okType: 'danger',
-        cancelText: '取消',
-        onOk() {
-            products.value.splice(index, 1);
-            message.success('成品已删除');
-        },
-        onCancel() {
-            console.log('Cancel');
-        },
-    });
+// 打开删除确认弹窗
+const openDeleteDialog = index => {
+    deleteIndex.value = index;
+    showDeleteModal.value = true;
+};
+
+// 确认删除成品
+const confirmDelete = () => {
+    if (deleteIndex.value >= 0 && deleteIndex.value < products.value.length) {
+        products.value.splice(deleteIndex.value, 1);
+        showNotify({ type: 'success', message: '成品已删除' });
+    }
+    showDeleteModal.value = false;
+    deleteIndex.value = -1;
 };
 
 const getLocators = async () => {
@@ -263,11 +277,11 @@ const getLocators = async () => {
         if (res && res.length > 0) {
             warehouseLocationOptions.value = res;
         } else {
-            message.warning('获取货位列表失败');
+            showNotify({ type: 'warning', message: '获取货位列表失败' });
         }
     } catch (error) {
         console.log(error);
-        message.error('获取货位列表失败');
+        showNotify({ type: 'danger', message: '获取货位列表失败' });
     }
 };
 onMounted(() => {
@@ -289,7 +303,7 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
@@ -299,7 +313,7 @@ onMounted(() => {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 1.5rem;
+  margin-bottom: 0.5rem;
 }
 
 /* 页面标题 */
@@ -315,7 +329,7 @@ onMounted(() => {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 1rem;
+  margin-bottom: 0.5rem;
 }
 
 .top-actions-info {
@@ -367,6 +381,7 @@ onMounted(() => {
   border: 2px solid #e5e7eb;
   background-color: #ffffff;
   border-radius: 0.5rem;
+  padding: 1rem 1.5rem;
 }
 
 .inventory-card:hover {
@@ -380,7 +395,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header > * {
+  margin-right: 1.5rem;
+}
+
+.card-header > *:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -393,17 +415,31 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right > * {
+  margin-left: 1rem;
+}
+
+.card-header-right > *:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info > * {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info > *:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
   font-size: 1.125rem;
   font-weight: 600;
@@ -469,12 +505,244 @@ onMounted(() => {
 
 /* 删除按钮样式 */
 .delete-btn {
-  margin-left: 0.5rem;
-  opacity: 0.7;
-  transition: opacity 0.2s ease;
+  background-color: white !important;
+  border-color: #ef4444 !important;
+  color: #ef4444 !important;
+  transition: all 0.2s ease;
+}
+
+:deep(.delete-btn.van-button--danger) {
+  background-color: white !important;
+  border-color: #ef4444 !important;
+  color: #ef4444 !important;
+}
+
+.delete-btn:hover,
+:deep(.delete-btn.van-button--danger:active) {
+  background-color: #fef2f2 !important;
+  border-color: #dc2626 !important;
+  color: #dc2626 !important;
+}
+
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
+}
+
+/* 大字体防误触弹窗样式 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+  z-index: 2000 !important;
+}
+
+/* 确保 Dialog 遮罩层不会覆盖下拉菜单 */
+:deep(.van-overlay) {
+  z-index: 1999 !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  padding-top: 2rem !important;
+  padding-bottom: 1rem !important;
+  padding-left: 1.5rem !important;
+  padding-right: 1.5rem !important;
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  color: #111827 !important;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding-top: 1rem !important;
+  padding-bottom: 1.5rem !important;
+  padding-left: 1.5rem !important;
+  padding-right: 1.5rem !important;
+  display: flex;
+}
+
+:deep(.large-dialog .van-dialog__footer > *) {
+  margin-right: 1rem;
+}
+
+:deep(.large-dialog .van-dialog__footer > *:last-child) {
+  margin-right: 0;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1;
+  height: 56px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel:active) {
+  background-color: #e5e7eb !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #10b981 !important;
+  color: white !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:active) {
+  background-color: #059669 !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:disabled) {
+  background-color: #d1d5db !important;
+  color: #9ca3af !important;
+}
+
+/* 大弹窗表单样式 */
+.large-dialog-form {
+  padding: 1.5rem 2rem 2rem 2rem;
+}
+
+.large-form-item {
+  margin-bottom: 1.5rem;
+}
+
+.large-form-item:last-child {
+  margin-bottom: 0;
+}
+
+.large-form-label {
+  display: block;
+  margin-bottom: 0.75rem;
+  font-weight: 600;
+  color: #111827;
+  font-size: 1.125rem;
+}
+
+:deep(.large-input .van-cell) {
+  padding: 0.875rem 1rem;
+  font-size: 1rem;
+  border: 2px solid #e5e7eb;
+  border-radius: 8px;
+}
+
+:deep(.large-input .van-field__control) {
+  font-size: 1rem;
+}
+
+.large-select {
+  width: 100%;
+  font-size: 1rem;
+}
+
+:deep(.large-select .vs__dropdown-toggle) {
+  padding: 0.75rem 1rem;
+  border: 2px solid #e5e7eb;
+  border-radius: 8px;
+  min-height: 50px;
+}
+
+:deep(.large-select .vs__search) {
+  font-size: 1rem;
+  padding: 0;
+}
+
+:deep(.large-select .vs__selected) {
+  font-size: 1rem;
+  padding: 0;
+  margin: 0;
+}
+
+:deep(.large-select .vs__dropdown-menu) {
+  font-size: 1rem;
+  border: 2px solid #e5e7eb;
+  border-radius: 8px;
+  z-index: 99999 !important;
+  position: fixed !important;
+}
+
+/* 确保下拉菜单显示在最上层 */
+:deep(.v-select.vs--open .vs__dropdown-menu) {
+  z-index: 99999 !important;
+}
+
+:deep(.large-select .vs__dropdown-option) {
+  padding: 0.875rem 1rem;
+}
+
+/* 全局确保 v-select 下拉菜单在最上层 */
+:deep(.vs__dropdown-menu) {
+  z-index: 99999 !important;
+}
+
+/* 确认弹窗内容样式 */
+.dialog-content {
+  padding: 1.5rem 2rem 2rem 2rem;
+}
+
+.dialog-content p {
+  margin: 0;
+  font-size: 1.125rem;
+  line-height: 1.8;
+  color: #374151;
+  text-align: center;
+}
+
+.dialog-content p.hint-text {
+  margin-top: 0.5rem;
+  font-size: 0.95rem;
+  color: #6b7280;
+}
+
+:deep(.van-field) {
+  border: 1px solid #ccc;
+  border-radius: 6px;
+}
+:deep(.van-cell) {
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  padding: 6px 8px !important;
+}
+</style>
+
+<style>
+/* 全局样式:确保 v-select 下拉菜单在模态框之上 */
+.vs__dropdown-menu {
+  z-index: 99999 !important;
+}
+
+.v-select .vs__dropdown-menu {
+  z-index: 99999 !important;
+}
+
+/* 确保附加到 body 的下拉菜单在最上层 */
+body > .v-select-menu {
+  z-index: 99999 !important;
+}
+
+body > .vs__dropdown-menu {
+  z-index: 99999 !important;
+}
+
+/* vue-select 的下拉菜单容器 */
+.vs--open .vs__dropdown-menu {
+  z-index: 99999 !important;
 }
 
-.delete-btn:hover {
-  opacity: 1;
+/* 大选择器的下拉菜单 */
+.large-select .vs__dropdown-menu {
+  z-index: 99999 !important;
 }
 </style>

+ 586 - 133
src/finishProduct/FinishProductOut.vue

@@ -1,146 +1,182 @@
+<!-- 成品出库 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="成品出库管理" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
-      <!-- 页面标题 -->
-      <div class="page-title">
-        <h2>成品出库管理</h2>
-      </div>
-
-      <!-- 筛选区域(沿用 FilterPanel 风格) -->
-      <FilterPanel :default-collapsed="false" :active-count="activeFilterCount">
-        <a-form layout="inline" class="filter-form">
-          <a-form-item label="名称">
-            <a-input
-              v-model:value="searchForm.inventoryName" placeholder="输入名称" style="width: 150px"
-              @keyup.enter="handleSearch"
-            />
-          </a-form-item>
-
-          <a-form-item label="编号">
-            <a-input
-              v-model:value="searchForm.inventoryNo" placeholder="输入编号" style="width: 150px"
-              @keyup.enter="handleSearch"
-            />
-          </a-form-item>
-
-          <a-form-item label="仓库">
-            <a-select
-              v-model:value="searchForm.warehouseId" :options="warehouseList" placeholder="选择仓库"
-              option-filter-prop="name" show-search allow-clear :field-names="{ label: 'name', value: 'id' }"
-              style="width: 150px" @change="handleSearch"
-            />
-          </a-form-item>
-
-          <a-form-item>
-            <a-button type="primary" @click="handleSearch">
+      <!-- 筛选区域 -->
+      <FilterPanel :enable-collapse="false" :active-count="activeFilterCount">
+        <div class="filter-form">
+          <div class="filter-item">
+            <label class="filter-label">名称:</label>
+            <van-field v-model="searchForm.inventoryName" placeholder="输入名称" class="filter-input" @keyup.enter="handleSearch" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">编号:</label>
+            <van-field v-model="searchForm.inventoryNo" placeholder="输入编号" class="filter-input" @keyup.enter="handleSearch" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">仓库:</label>
+            <v-select
+              v-model="searchForm.warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
+              placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="handleSearch"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item filter-buttons">
+            <van-button type="primary" @click="handleSearch">
               <i class="fas fa-search mr-1" /> 搜索
-            </a-button>
-            <a-button class="ml-2" @click="handleReset">
+            </van-button>
+            <van-button class="ml-2" @click="handleReset">
               <i class="fas fa-redo mr-1" /> 重置
-            </a-button>
-          </a-form-item>
-        </a-form>
+            </van-button>
+          </div>
+        </div>
       </FilterPanel>
 
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 全选控制栏 -->
+        <div v-if="finishProductList.length > 0" class="select-all-bar">
+          <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
+            <span class="select-all-text">全选当前页(已选 {{ selectedProducts.length }} 项)</span>
+          </van-checkbox>
+        </div>
+
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
-          <a-empty v-if="finishProductList.length === 0" description="暂无成品" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="finishProductList.length === 0" description="暂无成品" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
-              v-for="product in finishProductList" :key="product.id" :hoverable="true"
+            <div
+              v-for="product in finishProductList" :key="product.id"
               :class="{ 'selected-card': selectedProducts.includes(product.id) }" class="inventory-card"
               @click="toggleSelect(product.id)"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-checkbox
-                      :checked="selectedProducts.includes(product.id)" @click.stop
-                      @change="e => handleCheckboxChange(e, product.id)"
-                    />
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }" class="ml-3">
-                      <template #icon>
-                        <i class="fas fa-box" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ product.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ product.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <van-checkbox
+                    :model-value="selectedProducts.includes(product.id)" @click.stop
+                    @update:model-value="checked => handleCheckboxChange(checked, product.id)"
+                  />
+                  <div class="custom-avatar ml-3">
+                    <i class="fas fa-box" />
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ product.inventoryPosition }}</span>
-                      <span class="mx-2">/</span>
-                      <span>{{ product.inventoryWarehouse }}</span>
-                    </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ product.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ product.inventoryNo }}</div>
                   </div>
                 </div>
-              </template>
-            </a-card>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ product.inventoryPosition }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ product.inventoryWarehouse }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
 
         <!-- 分页区域(固定底部) -->
         <div v-if="finishProductList.length > 0" class="pagination-wrapper">
-          <AntdPagination
-            ref="paginationRef" :pagination="pagination" :options="options"
-            @get-page-params="getPageParams"
-          />
+          <div class="pagination-info">
+            第{{ (pagination.current_page - 1) * pagination.per_page + 1 }}-{{ Math.min(pagination.current_page *
+              pagination.per_page, pagination.total) }}条,共{{ pagination.total }}条
+          </div>
+          <div class="pagination-controls">
+            <button
+              class="pagination-btn" :disabled="pagination.current_page === 1"
+              @click="handlePageChange(pagination.current_page - 1)"
+            >
+              &lt;
+            </button>
+            <template v-for="page in getPageNumbers()" :key="page">
+              <button
+                v-if="page !== '...'" class="pagination-btn" :class="{ active: page === pagination.current_page }"
+                @click="handlePageChange(page)"
+              >
+                {{ page }}
+              </button>
+              <span v-else class="pagination-ellipsis">...</span>
+            </template>
+            <button
+              class="pagination-btn"
+              :disabled="pagination.current_page === Math.ceil(pagination.total / pagination.per_page)"
+              @click="handlePageChange(pagination.current_page + 1)"
+            >
+              &gt;
+            </button>
+          </div>
+          <div class="pagination-size">
+            <v-select
+              v-model="pagination.per_page" :options="pageSizeOptions" :clearable="false" :searchable="false"
+              append-to-body :calculate-position="withPopper" class="size-select"
+              @update:model-value="handlePageSizeChange"
+            >
+              <template #selected-option="{ label }">
+                {{ label }}条/页
+              </template>
+              <template #option="{ label }">
+                {{ label }}条/页
+              </template>
+            </v-select>
+          </div>
         </div>
       </div>
     </main>
 
     <!-- 底部操作按钮 -->
     <div class="bottom-actions">
-      <a-button
+      <van-button
         type="primary" size="large" :disabled="selectedProducts.length === 0" class="add-to-cart-btn"
         @click="confirmOutbound"
       >
         <i class="fas fa-check-circle mr-2" />
         确认出库
-      </a-button>
+      </van-button>
     </div>
 
     <!-- 确认出库弹窗 -->
-    <a-modal
-      v-model:open="showConfirmModal" title="确认出库" :mask-closable="false" width="420px"
-      @cancel="showConfirmModal = false"
+    <van-dialog
+      v-model:show="showConfirmModal" title="确认出库" show-cancel-button
+      confirm-button-text="确认出库" cancel-button-text="取消"
+      class-name="large-dialog"
+      @confirm="executeOutbound" @cancel="showConfirmModal = false"
     >
-      <p class="text-gray-700 mb-4">
-        您确定要将选中的
-        <span class="font-semibold">{{ selectedProducts.length }}</span>
-        个成品进行出库操作吗?
-      </p>
-      <template #footer>
-        <a-button @click="showConfirmModal = false">取消</a-button>
-        <a-button type="primary" @click="executeOutbound">确认出库</a-button>
-      </template>
-    </a-modal>
-
-    <Loading v-if="loading" />
+      <div class="dialog-content">
+        <p>
+          您确定要将选中的
+          <span class="font-semibold">{{ selectedProducts.length }}</span>
+          个成品进行出库操作吗?
+        </p>
+      </div>
+    </van-dialog>
   </div>
 </template>
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
-import { message, Empty } from 'ant-design-vue';
+import { showNotify } from 'vant';
 import PageHeader from '../common/PageHeader.vue';
 import FilterPanel from '../common/FilterPanel.vue';
 import { getWarehouseList } from '../api/stock.js';
 import { queryFinishProduct, generateStockOut } from '../api/finishProduct.js';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
+import { createPopper } from '@popperjs/core';
 
-// 加载状态(预留给后续真实接口调用)
+// 加载状态
 const loading = ref(false);
 
 // 仓库列表
@@ -159,18 +195,19 @@ const pagination = ref({
     current_page: 1,
     per_page: 20,
 });
-const options = {
-    showTotal: true,
-    showSizeChanger: true,
-    showQuickJumper: false,
-    pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
-};
+const pageSizeOptions = [10, 20, 50, 100, 200, 500];
 
 const finishProductList = ref([]);
 
 // 选中项
 const selectedProducts = ref([]); // 存储选中的 product.id
 
+// 计算是否全选
+const isAllSelected = computed(() => {
+    if (finishProductList.value.length === 0) return false;
+    return finishProductList.value.every(item => selectedProducts.value.includes(item.id));
+});
+
 // 弹窗与提示
 const showConfirmModal = ref(false);
 
@@ -184,12 +221,71 @@ const activeFilterCount = computed(() => {
 });
 
 // 分页改变
-const getPageParams = (page, pageSize) => {
+const handlePageChange = page => {
     pagination.value.current_page = page;
+    getList();
+};
+
+// 每页大小改变
+const handlePageSizeChange = pageSize => {
     pagination.value.per_page = pageSize;
+    pagination.value.current_page = 1;
     getList();
 };
 
+// 获取分页页码数组
+const getPageNumbers = () => {
+    const totalPages = Math.ceil(pagination.value.total / pagination.value.per_page);
+    const current = pagination.value.current_page;
+    const pages = [];
+
+    if (totalPages <= 7) {
+        for (let i = 1; i <= totalPages; i++) {
+            pages.push(i);
+        }
+    } else {
+        if (current <= 3) {
+            for (let i = 1; i <= 5; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        } else if (current >= totalPages - 2) {
+            pages.push(1);
+            pages.push('...');
+            for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
+        } else {
+            pages.push(1);
+            pages.push('...');
+            for (let i = current - 1; i <= current + 1; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        }
+    }
+    return pages;
+};
+
+// Popper 位置计算函数(用于 v-select 下拉菜单)
+const withPopper = (dropdownList, component, { width }) => {
+    dropdownList.style.width = width;
+    const popper = createPopper(component.$refs.toggle, dropdownList, {
+        placement: 'bottom',
+        modifiers: [
+            {
+                name: 'offset',
+                options: { offset: [0, -1] },
+            },
+            {
+                name: 'toggleClass',
+                enabled: true,
+                phase: 'write',
+                fn({ state }) {
+                    component.$el.classList.toggle('drop-up', state.placement === 'top');
+                },
+            },
+        ],
+    });
+    return () => popper.destroy();
+};
+
 // 搜索
 const handleSearch = () => {
     pagination.value.current_page = 1;
@@ -218,8 +314,8 @@ const toggleSelect = id => {
 };
 
 // 处理复选框变化
-const handleCheckboxChange = (e, id) => {
-    if (e.target.checked) {
+const handleCheckboxChange = (checked, id) => {
+    if (checked) {
         if (!selectedProducts.value.includes(id)) {
             selectedProducts.value.push(id);
         }
@@ -231,10 +327,26 @@ const handleCheckboxChange = (e, id) => {
     }
 };
 
+// 全选/取消全选
+const toggleSelectAll = checked => {
+    if (checked) {
+        // 全选当前页所有项(累加模式)
+        finishProductList.value.forEach(item => {
+            if (!selectedProducts.value.includes(item.id)) {
+                selectedProducts.value.push(item.id);
+            }
+        });
+    } else {
+        // 取消全选当前页
+        const currentPageIds = finishProductList.value.map(item => item.id);
+        selectedProducts.value = selectedProducts.value.filter(id => !currentPageIds.includes(id));
+    }
+};
+
 // 打开发起出库确认
 const confirmOutbound = () => {
     if (selectedProducts.value.length === 0) {
-        message.warning('请先选择需要出库的成品');
+        showNotify({ type: 'warning', message: '请选择要确认出库的成品' });
         return;
     }
     showConfirmModal.value = true;
@@ -250,20 +362,21 @@ const executeOutbound = async () => {
     try {
         const res = await generateStockOut(selectedProducts.value);
         if (res.errorCode === 0) {
-            message.success(`已成功出库 ${count} 条成品出库记录`);
+            showNotify({ type: 'success', message: '出库成功' });
             selectedProducts.value = [];
             pagination.value.current_page = 1;
             getList();
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
     } catch (error) {
         console.log(error);
-        message.error('提交出库失败');
+        showNotify({ type: 'danger', message: '出库失败' });
     } finally {
         loading.value = false;
     }
 };
+
 // 获取成品列表
 const getList = async () => {
 
@@ -288,11 +401,11 @@ const getList = async () => {
         } else {
             finishProductList.value = [];
             pagination.value.total = 0;
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取成品列表API调用失败:', error);
-        message.error('获取成品列表API调用失败');
+        showNotify({ type: 'danger', message: '获取成品列表失败' });
     } finally {
         loading.value = false;
     }
@@ -310,11 +423,11 @@ const getWarehouse = async () => {
                 warehouseList.value = [];
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取仓库列表API调用失败:', error);
-        message.error('获取仓库列表API调用失败');
+        showNotify({ type: 'danger', message: '获取仓库数据失败' });
     } finally {
         loading.value = false;
     }
@@ -339,43 +452,117 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem 1.5rem 1rem 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
 
-/* 页面标题 */
-.page-title {
-  margin-bottom: 1.5rem;
+/* 筛选表单 */
+.filter-form {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
 }
 
-.page-title h2 {
-  font-size: 1.5rem;
-  font-weight: 700;
-  color: #111827;
-  margin: 0;
+.filter-form > * {
+  margin-right: 1rem;
+  margin-bottom: 0.4rem;
 }
 
-/* 筛选表单 */
-.filter-form {
+.filter-item {
   display: flex;
-  flex-wrap: wrap;
-  gap: 0.5rem;
+  align-items: center;
 }
 
-:deep(.ant-form-item) {
-  margin-bottom: 0 !important;
+.filter-item > * {
+  margin-right: 0.5rem;
 }
 
-:deep(.ant-form-item-label > label) {
-  font-size: 14px !important;
-  font-weight: 600 !important;
+.filter-item > *:last-child {
+  margin-right: 0;
 }
 
+.filter-label {
+  font-size: 14px;
+  font-weight: 600;
+  white-space: nowrap;
+  color: #374151;
+}
 
-.top-actions-info {
-  font-size: 0.875rem;
-  color: #4b5563;
+.filter-select {
+  width: 200px;
+  position: relative;
+  z-index: 100;
+}
+
+.filter-input {
+  width: 200px;
+}
+
+:deep(.filter-input .van-cell) {
+  padding: 8px 12px !important;
+  border: 1px solid #ccc !important;
+  border-radius: 4px !important;
+  background-color: white !important;
+  min-height: 32px !important;
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.filter-input .van-cell::after) {
+  display: none !important;
+}
+
+:deep(.filter-input .van-field__body) {
+  border: none !important;
+}
+
+:deep(.filter-input .van-field__control) {
+  font-size: 14px;
+}
+
+/* Vue Select 样式 */
+:deep(.v-select) {
+  font-size: 14px;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  border: 1px solid #d1d5db;
+  border-radius: 4px;
+  padding: 4px 8px;
+  min-height: 32px;
+  background: white;
+}
+
+:deep(.v-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+  border: 1px solid #d1d5db;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.v-select .vs__selected) {
+  margin: 2px;
+  padding: 0 4px;
+}
+
+:deep(.v-select .vs__search) {
+  padding: 0;
+  margin: 0;
+}
+
+:deep(.v-select .vs__actions) {
+  padding: 0 4px;
+}
+
+.filter-buttons {
+  display: flex;
+}
+
+.filter-buttons > * {
+  margin-right: 0.5rem;
+}
+
+.filter-buttons > *:last-child {
+  margin-right: 0;
 }
 
 /* 卡片容器 */
@@ -390,6 +577,21 @@ onMounted(() => {
   min-height: 0;
 }
 
+/* 全选控制栏 */
+.select-all-bar {
+  background-color: white;
+  padding: 1rem 1.5rem;
+  border-bottom: 1px solid #e5e7eb;
+  display: flex;
+  align-items: center;
+}
+
+.select-all-text {
+  font-size: 0.95rem;
+  color: #374151;
+  font-weight: 500;
+}
+
 /* 卡片列表包装器(可滚动区域) */
 .card-list-wrapper {
   flex: 1;
@@ -402,7 +604,14 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+}
+
+.card-list > * {
+  margin-bottom: 1rem;
+}
+
+.card-list > *:last-child {
+  margin-bottom: 0;
 }
 
 /* 卡片样式 */
@@ -412,6 +621,7 @@ onMounted(() => {
   border: 2px solid #e5e7eb;
   background-color: #ffffff;
   border-radius: 0.5rem;
+  padding: 1rem 1.5rem;
 }
 
 .inventory-card:hover {
@@ -431,7 +641,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header > * {
+  margin-right: 1.5rem;
+}
+
+.card-header > *:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -444,17 +661,31 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right > * {
+  margin-left: 1rem;
+}
+
+.card-header-right > *:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info > * {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info > *:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
   font-size: 1.125rem;
   font-weight: 600;
@@ -520,6 +751,92 @@ onMounted(() => {
   display: flex;
   justify-content: flex-end;
   align-items: center;
+  position: relative;
+  z-index: 100;
+}
+
+.pagination-wrapper > * {
+  margin-left: 1rem;
+}
+
+.pagination-wrapper > *:first-child {
+  margin-left: 0;
+}
+
+.pagination-info {
+  font-size: 14px;
+  color: #6b7280;
+  white-space: nowrap;
+}
+
+.pagination-controls {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-controls > * {
+  margin-right: 0.25rem;
+}
+
+.pagination-controls > *:last-child {
+  margin-right: 0;
+}
+
+.pagination-btn {
+  min-width: 32px;
+  height: 32px;
+  padding: 0 8px;
+  border: 1px solid #d1d5db;
+  background: white;
+  color: #374151;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.2s;
+}
+
+.pagination-btn:hover:not(:disabled) {
+  border-color: #3b82f6;
+  color: #3b82f6;
+}
+
+.pagination-btn.active {
+  background: #3b82f6;
+  border-color: #3b82f6;
+  color: white;
+}
+
+.pagination-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.pagination-ellipsis {
+  padding: 0 4px;
+  color: #6b7280;
+}
+
+.pagination-size {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-size > * {
+  margin-right: 0.5rem;
+}
+
+.pagination-size > *:last-child {
+  margin-right: 0;
+}
+
+.size-select {
+  width: 120px;
+  position: relative;
+  z-index: 50;
+}
+
+:deep(.size-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
 }
 
 /* 底部操作按钮 */
@@ -536,9 +853,16 @@ onMounted(() => {
 .add-to-cart-btn {
   background-color: #10b981 !important;
   border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
 }
 
-.add-to-cart-btn:hover {
+:deep(.add-to-cart-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+}
+
+:deep(.add-to-cart-btn.van-button--primary:active) {
   background-color: #059669 !important;
   border-color: #059669 !important;
 }
@@ -581,4 +905,133 @@ onMounted(() => {
   opacity: 0.5;
   cursor: not-allowed;
 }
+
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
+}
+
+/* 大字体防误触弹窗样式 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+  z-index: 2000 !important;
+}
+
+:deep(.van-overlay) {
+  z-index: 1999 !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  padding-top: 2rem !important;
+  padding-bottom: 1rem !important;
+  padding-left: 1.5rem !important;
+  padding-right: 1.5rem !important;
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  color: #111827 !important;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding-top: 1rem !important;
+  padding-bottom: 1.5rem !important;
+  padding-left: 1.5rem !important;
+  padding-right: 1.5rem !important;
+  display: flex;
+}
+
+:deep(.large-dialog .van-dialog__footer > *) {
+  margin-right: 1rem;
+}
+
+:deep(.large-dialog .van-dialog__footer > *:last-child) {
+  margin-right: 0;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1;
+  height: 56px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel:active) {
+  background-color: #e5e7eb !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #10b981 !important;
+  color: white !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:active) {
+  background-color: #059669 !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:disabled) {
+  background-color: #d1d5db !important;
+  color: #9ca3af !important;
+}
+
+/* 弹窗内容样式 */
+.dialog-content {
+  padding: 1.5rem 2rem 2rem 2rem;
+}
+
+.dialog-content p {
+  margin: 0;
+  font-size: 1.125rem;
+  line-height: 1.8;
+  color: #374151;
+  text-align: center;
+}
+
+:deep(.van-cell) {
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  padding: 6px 8px !important;
+}
+</style>
+
+<style>
+/* 全局样式:确保 v-select 下拉菜单在模态框之上 */
+.vs__dropdown-menu {
+  z-index: 99999 !important;
+}
+
+.v-select .vs__dropdown-menu {
+  z-index: 99999 !important;
+}
+
+/* 确保附加到 body 的下拉菜单在最上层 */
+body > .v-select-menu {
+  z-index: 99999 !important;
+}
+
+body > .vs__dropdown-menu {
+  z-index: 99999 !important;
+}
+
+/* vue-select 的下拉菜单容器 */
+.vs--open .vs__dropdown-menu {
+  z-index: 99999 !important;
+}
 </style>

+ 100 - 118
src/login/FingerprintLogin.vue

@@ -1,5 +1,28 @@
+<!-- 指纹登录 -->
 <template>
   <div class="recognize-wrapper">
+    <!-- 操作按钮 - 右上角 -->
+    <div class="action-buttons-top">
+      <van-button
+        type="default" size="small" round class="action-btn-top password-btn-top"
+        @click="goToPasswordLogin"
+      >
+        <!-- <template #icon>
+          <i class="fas fa-key" />
+        </template> -->
+        密码登录
+      </van-button>
+      <van-button
+        type="default" size="small" round class="action-btn-top restart-btn-top"
+        @click="handleRestart"
+      >
+        <!-- <template #icon>
+          <i class="fas fa-redo-alt" />
+        </template> -->
+        重启识别
+      </van-button>
+    </div>
+
     <div class="recognize-container">
       <!-- 状态圆形区域 -->
       <div class="status-circle" :class="statusClass">
@@ -90,28 +113,6 @@
         {{ isConnected ? statusText : '设备未连接' }}
       </div>
 
-      <!-- 操作按钮 -->
-      <div class="action-buttons">
-        <van-button
-          type="default" size="large" round block class="action-btn password-btn"
-          @click="goToPasswordLogin"
-        >
-          <template #icon>
-            <i class="fas fa-key" />
-          </template>
-          使用密码登录
-        </van-button>
-        <van-button
-          type="default" size="large" round block class="action-btn restart-btn"
-          @click="handleRestart"
-        >
-          <template #icon>
-            <i class="fas fa-redo-alt" />
-          </template>
-          重启指纹识别
-        </van-button>
-      </div>
-
       <!-- 用户信息(识别成功后显示) -->
       <!-- <div v-if="status === 'success' && userInfo" class="user-info">
         <div class="user-avatar">
@@ -129,7 +130,7 @@
 <script setup>
 import { useRouter, useRoute } from 'vue-router';
 import { ref, onMounted, onUnmounted } from 'vue';
-import { showSuccessToast, showFailToast, Button as VanButton } from 'vant';
+import { showNotify } from 'vant';
 
 const route = useRoute();
 const router = useRouter();
@@ -169,7 +170,7 @@ const handleFingerprintResponse = data => {
 
     if (!data) {
         updateStatus('failed', '识别失败,未接收到数据');
-        showFailToast('识别失败');
+        showNotify({ type: 'danger', message: '识别失败' });
         return;
     }
 
@@ -193,7 +194,7 @@ const handleFingerprintResponse = data => {
                 localStorage.setItem('#token', responseData.token);
                 localStorage.setItem('#accountId', responseData.accountId);
                 localStorage.setItem('#LoginInfo', JSON.stringify(responseData));
-                showSuccessToast('欢迎登录,' + responseData.userName);
+                showNotify({ type: 'success', message: '欢迎登录,' + responseData.userName });
                 const redirectUrl = window.location.href.split('redirectUrl=')[1];
 
                 if (redirectUrl) {
@@ -216,7 +217,7 @@ const handleFingerprintResponse = data => {
 const startRecognize = () => {
     if (!plugin.fingerprintConfig) {
         updateStatus('failed', '指纹设备插件未就绪');
-        showFailToast('设备不支持指纹识别功能');
+        showNotify({ type: 'danger', message: '设备不支持指纹识别功能' });
         return;
     }
 
@@ -225,7 +226,7 @@ const startRecognize = () => {
     } catch (error) {
         console.error('启动指纹识别失败', error);
         updateStatus('failed', '启动识别失败');
-        showFailToast('启动识别失败');
+        showNotify({ type: 'danger', message: '启动识别失败' });
     }
 };
 
@@ -328,8 +329,11 @@ onUnmounted(() => {
     flex-direction: column;
     align-items: center;
     justify-content: center;
-    gap: 2rem;
-    padding: 2rem;
+    margin-bottom: 2rem;
+    padding-top: 2rem;
+    padding-bottom: 2rem;
+    padding-left: 2rem;
+    padding-right: 2rem;
     max-width: 500px;
     width: 90%;
 }
@@ -607,12 +611,16 @@ onUnmounted(() => {
 
 /* 状态文本 */
 .status-text {
-    font-size: clamp(1.2rem, 3vw, 1.8rem);
-    font-weight: 600;
-    color: white;
-    text-align: center;
-    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
-    animation: textFadeIn 0.5s ease;
+    font-size:4rem;
+  color: white;
+  text-align: center;
+  font-weight: 700;
+  max-width: clamp(250px, 50vw, 400px);
+  line-height: 1.6;
+  margin-top: 15px;
+  margin-bottom: 15px;
+  margin-left: 0;
+  margin-right: 0;
 }
 
 @keyframes textFadeIn {
@@ -631,7 +639,10 @@ onUnmounted(() => {
 .user-info {
     background: rgba(255, 255, 255, 0.95);
     border-radius: 16px;
-    padding: 1.5rem 2rem;
+    padding-top: 1.5rem;
+    padding-bottom: 1.5rem;
+    padding-left: 2rem;
+    padding-right: 2rem;
     display: flex;
     align-items: center;
     gap: 1rem;
@@ -688,90 +699,18 @@ onUnmounted(() => {
 
 /* 操作按钮 */
 .action-buttons {
-    display: flex;
-    gap: 1rem;
-    animation: slideUp 0.5s ease;
-}
-
-.retry-button {
-    height: 3.5rem;
-    padding: 0 2.5rem;
-    font-size: 1.1rem;
-    font-weight: 600;
-    background: white;
-    color: #667eea;
-    border: 2px solid white;
-    border-radius: 50px;
-    transition: all 0.3s ease;
-    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
-}
-
-.retry-button:hover {
-    background: rgba(255, 255, 255, 0.9);
-    transform: translateY(-2px);
-    box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
-}
-
-/* 响应式设计 */
-@media (max-width: 768px) {
-    .recognize-container {
-        gap: 1.5rem;
-        padding: 1.5rem;
-    }
-
-    .status-circle {
-        width: clamp(200px, 50vw, 280px);
-        height: clamp(200px, 50vw, 280px);
-    }
-
-    .user-info {
-        padding: 1rem 1.5rem;
-    }
-
-    .user-avatar {
-        width: 50px;
-        height: 50px;
-    }
-
-    .avatar-text {
-        font-size: 1.2rem;
-    }
-
-    .user-name {
-        font-size: 1.1rem;
-    }
-
-    .user-role {
-        font-size: 0.9rem;
-    }
+    display: none;
 }
 
-/* 暗色模式支持 */
-@media (prefers-color-scheme: dark) {
-    .status-circle {
-        background: rgba(31, 41, 55, 0.95);
-    }
-
-    .user-info {
-        background: rgba(31, 41, 55, 0.95);
-    }
-
-    .user-name {
-        color: #f9fafb;
-    }
-
-    .user-role {
-        color: #d1d5db;
-    }
-}
-
-/* 操作按钮 */
-.action-buttons {
+/* 操作按钮 - 右上角定位 */
+.action-buttons-top {
+    position: absolute;
+    top: clamp(1rem, 2vh, 1.5rem);
+    right: clamp(1rem, 2vw, 1.5rem);
     display: flex;
-    flex-direction: column;
-    gap: 1rem;
-    width: 100%;
-    max-width: 350px;
+    flex-direction: row;
+    gap: 0.5rem;
+    z-index: 100;
     animation: textFadeIn 0.5s ease;
 }
 
@@ -787,6 +726,25 @@ onUnmounted(() => {
     font-size: 16px;
 }
 
+/* 右上角按钮样式 */
+.action-btn-top {
+    font-size:2rem;
+    font-weight: 600;
+    height: auto !important;
+    padding-top: 0.5rem !important;
+    padding-bottom: 0.5rem !important;
+    padding-left: 0.8rem !important;
+    padding-right: 0.8rem !important;
+    transition: all 0.3s ease;
+    white-space: nowrap;
+    margin-left: 1rem;
+}
+
+.action-btn-top i {
+    margin-right: 4px;
+    font-size: 12px;
+}
+
 /* 密码登录按钮样式 */
 :deep(.password-btn) {
     background: rgba(255, 255, 255, 0.15) !important;
@@ -811,10 +769,34 @@ onUnmounted(() => {
     background: rgba(255, 255, 255, 0.2) !important;
 }
 
+/* 右上角密码登录按钮样式 */
+:deep(.password-btn-top) {
+    background: rgba(255, 255, 255, 0.2) !important;
+    border: 1px solid rgba(255, 255, 255, 0.6) !important;
+    color: white !important;
+    backdrop-filter: blur(10px);
+}
+
+:deep(.password-btn-top:active) {
+    background: rgba(255, 255, 255, 0.3) !important;
+}
+
+/* 右上角重启按钮样式 */
+:deep(.restart-btn-top) {
+    background: rgba(255, 255, 255, 0.15) !important;
+    border: 1px solid rgba(255, 255, 255, 0.4) !important;
+    color: rgba(255, 255, 255, 0.95) !important;
+    backdrop-filter: blur(10px);
+}
+
+:deep(.restart-btn-top:active) {
+    background: rgba(255, 255, 255, 0.25) !important;
+}
+
 /* 响应式设计 */
 @media (max-width: 768px) {
     .action-buttons {
-        max-width: 100%;
+        display: none;
         padding: 0 1rem;
     }
 }

+ 194 - 29
src/login/UserHome.vue

@@ -60,20 +60,33 @@
           </button>
         </div>
         <div class="bg-white rounded-xl shadow-sm overflow-hidden">
-          <a-table :columns="columns" :data-source="recentRecords" :pagination="false" :scroll="{ x: true }">
-            <template #bodyCell="{ column, record }">
-              <template v-if="column.key === 'operationType'">
-                <span class="px-3 py-1 rounded-full text-sm" :class="getOperationTypeClass(record.operationType)">
-                  {{ record.operationType }}
-                </span>
-              </template>
-              <template v-if="column.key === 'status'">
-                <span class="text-green-500">
-                  <i class="fas fa-check-circle mr-1" />{{ record.status }}
-                </span>
-              </template>
-            </template>
-          </a-table>
+          <div class="simple-table">
+            <div class="table-header">
+              <div class="table-row">
+                <div v-for="col in columns" :key="col.key" class="table-cell">
+                  {{ col.title }}
+                </div>
+              </div>
+            </div>
+            <div class="table-body">
+              <div v-for="(record, index) in recentRecords" :key="index" class="table-row">
+                <div class="table-cell">{{ record.time }}</div>
+                <div class="table-cell">{{ record.operator }}</div>
+                <div class="table-cell">
+                  <span class="px-3 py-1 rounded-full text-sm" :class="getOperationTypeClass(record.operationType)">
+                    {{ record.operationType }}
+                  </span>
+                </div>
+                <div class="table-cell">{{ record.material }}</div>
+                <div class="table-cell">{{ record.quantity }}</div>
+                <div class="table-cell">
+                  <span class="text-green-500">
+                    <i class="fas fa-check-circle mr-1" />{{ record.status }}
+                  </span>
+                </div>
+              </div>
+            </div>
+          </div>
         </div>
       </div>
     </main>
@@ -105,16 +118,14 @@
     </footer>
 
     <!-- 拣货确认弹窗 -->
-    <a-modal
-      v-model:open="pickingModalVisible" title="请确认您要进行的操作?" :closable="true" :mask-closable="false"
-      @ok="handlePickingConfirm" @cancel="pickingModalVisible = false"
+    <van-dialog
+      v-model:show="pickingModalVisible" title="请确认您要进行的操作" show-cancel-button class-name="large-dialog"
+      confirm-button-text="是" cancel-button-text="否" @confirm="handlePickingConfirm" @cancel="handlePickingCancel"
     >
-      <template #footer>
-        <a-button @click="handlePickingCancel">否</a-button>
-        <a-button type="primary" danger @click="handlePickingConfirm">是</a-button>
-      </template>
-      <p>您是否已经申请了领料。</p>
-    </a-modal>
+      <div class="large-dialog-content">
+        <p>您是否已经申请了领料?</p>
+      </div>
+    </van-dialog>
   </div>
   <Loading v-if="loading" />
 </template>
@@ -124,7 +135,7 @@ import { ref, reactive, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import PageHeader from '../common/PageHeader.vue';
 import { cfStockInLeave } from '../api/stockIn.js';
-import { message } from 'ant-design-vue';
+import { Dialog as VanDialog, showNotify } from 'vant';
 
 // 路由
 const router = useRouter();
@@ -420,7 +431,8 @@ const handleAction = action => {
         break;
     case 'materialInLeave':
         // 还料出库离开
-        validateMaterialInLeave();
+        // validateMaterialInLeave();
+        router.push('/returned-leave');
         break;
     case 'finishedProductIn':
         // 成品入库
@@ -452,14 +464,14 @@ const validateMaterialInLeave = async () => {
             if (res.datas && res.datas.length > 0) {
                 router.push('/in-confirm?isLeave=true');
             } else {
-                message.success('您已完成还料操作,请在闸机开门后离开');
+                showNotify({ type: 'success', message: '您已完成还料操作,请在闸机开门后离开' });
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('校验还料离开时是否带有工装设备信息失败:', error);
-        message.error('校验还料离开时是否带有工装设备信息失败');
+        showNotify({ type: 'danger', message: '校验还料离开时是否带有工装设备信息失败' });
     } finally {
         loading.value = false;
     }
@@ -516,5 +528,158 @@ const getStatColorClasses = (color, type) => {
 </script>
 
 <style scoped>
-/* Additional component-specific styles if needed */
+/* 大弹窗样式,适合触摸屏 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  padding-top: 2rem !important;
+  padding-bottom: 1rem !important;
+  padding-left: 1.5rem !important;
+  padding-right: 1.5rem !important;
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  color: #111827 !important;
+}
+
+.large-dialog-content {
+  padding-top: 1.5rem !important;
+  padding-bottom: 2.5rem !important;
+  padding-left: 2rem !important;
+  padding-right: 2rem !important;
+}
+
+.large-dialog-content p {
+  margin-top: 0;
+  margin-bottom: 1rem;
+  margin-left: 0;
+  margin-right: 0;
+  font-size: 1.25rem !important;
+  line-height: 1.8 !important;
+  font-weight: 600;
+  color: #111827;
+  text-align: center;
+}
+
+.large-dialog-content p:last-child {
+  margin-bottom: 0;
+}
+
+.large-dialog-content .dialog-subtitle {
+  font-size: 1.125rem !important;
+  color: #6b7280 !important;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding-top: 1rem !important;
+  padding-bottom: 1.5rem !important;
+  padding-left: 1.5rem !important;
+  padding-right: 1.5rem !important;
+  display: flex;
+}
+
+:deep(.large-dialog .van-dialog__footer > *) {
+  margin-right: 1rem;
+}
+
+:deep(.large-dialog .van-dialog__footer > *:last-child) {
+  margin-right: 0;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1;
+  height: 56px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel:active) {
+  background-color: #e5e7eb !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #10b981 !important;
+  color: white !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:active) {
+  background-color: #059669 !important;
+}
+
+/* 简单表格样式 */
+.simple-table {
+  width: 100%;
+  overflow-x: auto;
+}
+
+.table-header {
+  background-color: #f9fafb;
+  border-bottom: 2px solid #e5e7eb;
+}
+
+.table-header .table-row {
+  font-weight: 600;
+  color: #374151;
+}
+
+.table-row {
+  display: flex;
+  min-width: 800px;
+}
+
+.table-header .table-row {
+  padding: 1rem;
+}
+
+.table-body .table-row {
+  padding: 1rem;
+  border-bottom: 1px solid #e5e7eb;
+  transition: background-color 0.2s;
+}
+
+.table-body .table-row:hover {
+  background-color: #f9fafb;
+}
+
+.table-cell {
+  flex: 1;
+  padding: 0 0.5rem;
+  display: flex;
+  align-items: center;
+}
+
+.table-cell:first-child {
+  min-width: 150px;
+}
+
+.table-cell:nth-child(2) {
+  min-width: 100px;
+}
+
+.table-cell:nth-child(3) {
+  min-width: 120px;
+}
+
+.table-cell:nth-child(4) {
+  min-width: 200px;
+}
+
+.table-cell:nth-child(5) {
+  min-width: 80px;
+}
+
+.table-cell:last-child {
+  min-width: 100px;
+}
 </style>

+ 57 - 21
src/login/UserLogin.vue

@@ -71,12 +71,12 @@
             </div>
 
             <!-- 登录按钮 -->
-            <a-button type="primary" block size="large" class="login-button" :loading="loading" @click="handleLogin">
+            <van-button type="primary" block size="large" class="login-button" :loading="loading" @click="handleLogin">
               登录
-            </a-button>
-            <a-button type="primary" block size="large" class="login-button" @click="handleFingerprintLogin">
+            </van-button>
+            <van-button type="primary" block size="large" class="login-button" @click="handleFingerprintLogin">
               指纹登录
-            </a-button>
+            </van-button>
 
             <!-- 忘记密码链接 -->
             <!-- <div class="form-footer">
@@ -99,10 +99,9 @@
 <script setup>
 import { ref, onMounted } from 'vue';
 import { useRouter, useRoute } from 'vue-router';
-import { message } from 'ant-design-vue';
 import { loginApi } from '../api/login.js';
 import { getFormattedDateTime } from '../common/Common.js';
-import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
+import { showNotify } from 'vant';
 
 const router = useRouter();
 const route = useRoute();
@@ -131,7 +130,8 @@ onMounted(() => {
 // 登录处理函数
 const handleLogin = async () => {
     if (!username.value || !password.value) {
-        message.error('请输入完整的登录信息');
+        // message.error('请输入完整的登录信息');
+        showNotify({ type: 'warning', message: '请输入完整的登录信息' });
         return;
     }
 
@@ -155,7 +155,8 @@ const handleLogin = async () => {
                 localStorage.setItem('#accountId', res.data.accountId);
                 localStorage.setItem('#LoginInfo', JSON.stringify(res.data));
             }
-            message.success('欢迎登录,' + res.data.userName);
+            // message.success('欢迎登录,' + res.data.userName);
+            showNotify({ type: 'success', message: '欢迎登录,' + res.data.userName });
             const redirectUrl = window.location.href.split('redirectUrl=')[1];
 
             if (redirectUrl) {
@@ -164,10 +165,10 @@ const handleLogin = async () => {
                 router.push('/home');
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
     } catch (error) {
-        message.error('登录失败');
+        showNotify({ type: 'danger', message: '登录失败' });
     } finally {
         loading.value = false;
     }
@@ -184,7 +185,7 @@ const handleFingerprintLogin = () => {
 
 // 忘记密码处理函数
 const handleForgotPassword = () => {
-    message.info('请联系管理员重置密码');
+    showNotify({ type: 'warning', message: '请联系管理员重置密码' });
 };
 </script>
 
@@ -220,7 +221,10 @@ const handleForgotPassword = () => {
   display: flex;
   align-items: center;
   justify-content: center;
-  padding: 40px 20px;
+  padding-top: 40px;
+  padding-bottom: 40px;
+  padding-left: 20px;
+  padding-right: 20px;
   position: relative;
   z-index: 1;
 }
@@ -229,7 +233,10 @@ const handleForgotPassword = () => {
 .login-left {
   flex: 1;
   max-width: 500px;
-  padding: 0 40px;
+  padding-top: 0;
+  padding-bottom: 0;
+  padding-left: 40px;
+  padding-right: 40px;
   color: white;
 }
 
@@ -255,17 +262,31 @@ const handleForgotPassword = () => {
 .feature-list {
   display: flex;
   flex-direction: column;
-  gap: 20px;
+}
+
+.feature-list > * {
+  margin-bottom: 20px;
+}
+
+.feature-list > *:last-child {
+  margin-bottom: 0;
 }
 
 .feature-item {
   display: flex;
   align-items: center;
-  gap: 12px;
   font-size: 16px;
   opacity: 0.95;
 }
 
+.feature-item > * {
+  margin-right: 12px;
+}
+
+.feature-item > *:last-child {
+  margin-right: 0;
+}
+
 .feature-item i {
   font-size: 20px;
   color: #4ade80;
@@ -274,13 +295,19 @@ const handleForgotPassword = () => {
 /* 右侧登录表单 */
 .login-right {
   flex: 0 0 450px;
-  padding: 0 20px;
+  padding-top: 0;
+  padding-bottom: 0;
+  padding-left: 20px;
+  padding-right: 20px;
 }
 
 .login-form-wrapper {
   background: white;
   border-radius: 16px;
-  padding: 48px 40px;
+  padding-top: 48px;
+  padding-bottom: 48px;
+  padding-left: 40px;
+  padding-right: 40px;
   box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
   animation: fadeInRight 0.8s ease-out;
 }
@@ -305,11 +332,18 @@ const handleForgotPassword = () => {
 .form-content {
   display: flex;
   flex-direction: column;
-  gap: 20px;
+}
+
+.form-content > * {
+  margin-bottom: 20px;
+}
+
+.form-content > *:last-child {
+  margin-bottom: 0;
 }
 
 .form-item {
-  margin-bottom: 4px;
+  margin-bottom: 1rem;
 }
 
 .input-icon {
@@ -324,7 +358,6 @@ const handleForgotPassword = () => {
   background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
   border: none;
   border-radius: 8px;
-  margin-top: 8px;
   transition: all 0.3s ease;
   color: white;
 }
@@ -420,7 +453,10 @@ const handleForgotPassword = () => {
   align-items: center;
   border: 1px solid #e5e7eb;
   border-radius: 8px;
-  padding: 12px 16px;
+  padding-top: 12px;
+  padding-bottom: 12px;
+  padding-left: 16px;
+  padding-right: 16px;
   background: white;
   transition: all 0.3s ease;
 }

+ 3 - 3
src/main.js

@@ -1,13 +1,13 @@
 import './public-path.js';
 
 import { createApp } from 'vue/dist/vue.esm-bundler.js';
-import Antd from 'ant-design-vue';
+// import Antd from 'ant-design-vue';
 import App from './App.vue';
 import { createRouter, createWebHashHistory } from 'vue-router';
 
 import * as PcComponentV3 from 'pc-component-v3';
 import 'pc-component-v3/dist/pc-component-v3.css';
-import 'ant-design-vue/dist/reset.css';
+// import 'ant-design-vue/dist/reset.css';
 import './assets/main.css';
 import routes from './router/routes.js';
 import $ from 'jquery';
@@ -43,7 +43,7 @@ function render(props = {}) {
     const { container } = props;
   
     instance = createApp(App);
-    instance.use(Antd);
+    // instance.use(Antd);
     instance.use(Vant);
     instance.use(PcComponentV3);
     instance.use(router);

+ 5 - 0
src/router/routes.js

@@ -12,6 +12,8 @@ const StockPickingCar = () => import('../stock/StockPickingCar.vue');
 const OrderPicking = () => import('../stock-out/OrderPicking.vue');
 // 出库确认
 const OutboundConfirm = () => import('../stock-out/OutboundConfirm.vue');
+// 还料离开
+const ReturnedLeave = () => import('../stock-in/ReturnedLeave.vue');
 // AGV RFID识别
 const AgvRfidRecognition = () => import('../stock-out/AgvRfidRecognition.vue');
 // 入库确认
@@ -29,8 +31,10 @@ const FinishProductIn = () => import('../finishProduct/FinishProductIn.vue');
 // 成品出库
 const FinishProductOut = () => import('../finishProduct/FinishProductOut.vue');
 
+// 指纹录入
 const FingerprintEnroll = () => import('../Fingerprint/FingerprintEnroll.vue');
 
+// 指纹登录
 const FingerprintLogin = () => import('../login/FingerprintLogin.vue');
 
 const routes = [
@@ -39,6 +43,7 @@ const routes = [
     { path: '/home', component: UserHome, meta: { title: '首页' } },
     { path: '/order-picking', component: OrderPicking, meta: { title: '拣货管理' } },
     { path: '/outbound-confirm', component: OutboundConfirm, meta: { title: '出库确认' } },
+    { path: '/returned-leave', component: ReturnedLeave, meta: { title: '还料离开' } },    
     { path: '/stock-picking-car', component: StockPickingCar, meta: { title: '领料车' } },
     { path: '/stock-requisition', component: StockRequisition, meta: { title: '领料管理' } },
     { path: '/regular-requisition', component: RegularRequisition, meta: { title: '常用领料' } },

+ 133 - 157
src/stock-in/InConfirm.vue

@@ -2,56 +2,50 @@
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="还料入库" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
       <!-- 页面标题 -->
       <div class="page-header-row">
-        <div class="page-title">
-          <h2>{{ isLeave == 'true' ? '还料离开' : '还料入库' }}</h2>
-        </div>
-        <a-button type="primary" @click="verify">
+        <van-button type="primary" @click="verify">
           <i class="fas fa-sync-alt mr-1" /> 重新校验
-        </a-button>
+        </van-button>
       </div>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 卡片列表区域 -->
         <div class="card-list-wrapper">
-          <a-empty v-if="materialList.length === 0" description="暂无数据" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="materialList.length === 0" description="暂无数据" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
+            <div
               v-for="(item, index) in materialList" :key="item.id || index" :class="getCardClass(item.remarks)"
               class="inventory-card"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }">
-                      <template #icon>
-                        <i :class="getInventoryIcon(item.inventoryType)" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ item.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <div class="custom-avatar">
+                    <!-- :style="{ backgroundColor: getStatusColor(item.remarks) }" -->
+                    <i :class="getInventoryIcon(item.inventoryType)" />
+                  </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ item.positionName || '-' }}</span>
-                      <span class="mx-2">/</span>
-                      <span>{{ item.remarks || '-' }}</span>
-                    </div>
+                </div>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.positionName || '-' }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ item.remarks || '-' }}</span>
                   </div>
                 </div>
-              </template>
-            </a-card>
+              </div>
+            </div>
           </div>
         </div>
 
@@ -60,27 +54,24 @@
           <span class="text-gray-600">共 {{ materialList.length }} 条数据</span>
         </div>
       </div>
-
-      <!-- 底部完成按钮 -->
-      <div v-if="!isLeave" class="bottom-actions">
-        <a-button
-          type="primary" size="large" :disabled="notInStockCount === 0" class="shadow-sm"
-          @click="handleComplete"
-        >
-          <template #icon>
-            <i class="fas fa-check-circle mr-2" />
-          </template>
-          还料入库 ({{ notInStockCount }})
-        </a-button>
-      </div>
     </main>
+    <!-- 底部完成按钮 -->
+    <div class="bottom-actions">
+      <van-button
+        type="primary" size="large" :disabled="notInStockCount === 0" class="submit-btn"
+        @click="handleComplete"
+      >
+        <i class="fas fa-check-circle mr-2" />
+        还料入库
+      </van-button>
+    </div>
   </div>
   <Loading v-if="loading" />
 </template>
 
 <script setup>
 import { useRouter } from 'vue-router';
-import { message, Empty } from 'ant-design-vue';
+import { showNotify } from 'vant';
 import PageHeader from '../common/PageHeader.vue';
 import { ref, reactive, onMounted, computed } from 'vue';
 import { cfStockIn, createStockIn, cfStockInLeave } from '../api/stockIn.js';
@@ -93,8 +84,6 @@ const loading = ref(false);
 // 物料列表数据
 const materialList = ref([]);
 
-const isLeave = ref(false);
-
 // 分页配置
 const pagination = reactive({
     start: 1,
@@ -127,13 +116,23 @@ const getInventoryIcon = type => {
     return iconMap[type] || 'fas fa-cube';
 };
 
+// 获取状态颜色
+// const getStatusColor = remarks => {
+//     if (remarks === '不在库') {
+//         return '#3b82f6'; // 蓝色 - 需要入库
+//     } else if (remarks === '在库') {
+//         return '#10b981'; // 绿色 - 已在库
+//     }
+//     return '#6b7280'; // 灰色 - 默认
+// };
+
 // 还料入库
 const handleComplete = async () => {
     // 只处理不在库的数据
     const notInStockItems = materialList.value.filter(item => item.remarks === '不在库');
 
     if (notInStockItems.length === 0) {
-        message.warning('没有需要入库的物料!');
+        showNotify({ type: 'warning', message: '没有需要入库的物料!' });
         return;
     }
 
@@ -152,11 +151,7 @@ const handleComplete = async () => {
 
 // 校验
 const verify = () => {
-    if (isLeave.value == 'true') {
-        getInnerList();
-    } else {
-        getList();
-    }
+    getList();
 };
 
 // 获取物料列表(外侧校验)
@@ -174,11 +169,11 @@ const getList = async () => {
                 materialList.value = [];
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取物料列表API调用失败:', error);
-        message.error('获取物料列表API调用失败');
+        showNotify({ type: 'danger', message: '获取物料列表API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -193,16 +188,16 @@ const getInnerList = async () => {
             if (res.datas && res.datas.length > 0) {
                 materialList.value = res.datas;
                 pagination.total = materialList.value.length;
-                message.warning('检测到您已携带工装设备,请将工装设备全部还料后,再次点击【重新校验】按钮,待校验通过后,请在闸机开门后离开', 8);
+                showNotify({ type: 'warning', message: '检测到您已携带工装设备,请将工装设备全部还料后,再次点击【重新校验】按钮,待校验通过后,请在闸机开门后离开', duration: 8000 });
             } else {
-                message.success('校验成功,请在闸机开门后离开');
+                showNotify({ type: 'success', message: '校验成功,请在闸机开门后离开' });
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取物料列表API调用失败:', error);
-        message.error('获取物料列表API调用失败');
+        showNotify({ type: 'danger', message: '获取物料列表API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -214,27 +209,22 @@ const generateCFStockIn = async params => {
         const res = await createStockIn(params);
 
         if (res.errorCode === 0) {
-            message.success('入库申请已完成,还料任务已创建!');
+            showNotify({ type: 'success', message: '入库申请已完成,还料任务已创建!' });
             router.push('/home');
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('生成CF入库单API调用失败:', error);
-        message.error('生成CF入库单API调用失败');
+        showNotify({ type: 'danger', message: '生成CF入库单API调用失败' });
     } finally {
         loading.value = false;
     }
 };
 
 onMounted(() => {
-    isLeave.value = router.currentRoute.value.query.isLeave;
-    if (isLeave.value == 'true') {
-        getInnerList();
-    } else {
-        getList();
-    }
+    getList();
 });
 </script>
 
@@ -252,30 +242,17 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
 
-/* 页面标题行 */
+/* 操作按钮行 */
 .page-header-row {
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-end;
   align-items: center;
-  /* margin-bottom: 1.5rem; */
-}
-
-/* 页面标题 */
-.page-title {
-  margin-bottom: 1.5rem;
-}
-
-/* 页面标题 */
-.page-title h2 {
-  font-size: 1.5rem;
-  font-weight: 700;
-  color: #111827;
-  margin: 0;
+  margin-bottom: 1rem;
 }
 
 /* 卡片容器 */
@@ -302,7 +279,14 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+}
+
+.card-list>* {
+  margin-bottom: 1rem;
+}
+
+.card-list>*:last-child {
+  margin-bottom: 0;
 }
 
 /* 分页包装器(固定底部) */
@@ -320,35 +304,26 @@ onMounted(() => {
 .bottom-actions {
   position: sticky;
   bottom: 0;
-  padding: 1rem 1rem 0 1rem;
+  padding: 0 1rem 1rem 1rem;
   background-color: #f9fafb;
   display: flex;
   justify-content: flex-end;
   z-index: 10;
 }
 
-/* 筛选表单 */
-.filter-form {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 0.5rem;
-}
-
-:deep(.ant-form-item) {
-  margin-bottom: 0 !important;
-}
-
-:deep(.ant-form-item-label > label) {
-  font-size: 14px !important;
-  font-weight: 600 !important;
-}
-
 /* 库存卡片样式 */
 .inventory-card {
+  padding: 1rem 1.5rem;
   transition: all 0.25s ease;
   border: 2px solid #e5e7eb;
   background-color: #ffffff;
   border-radius: 0.5rem;
+  cursor: pointer;
+}
+
+.inventory-card:hover {
+  border-color: #93c5fd;
+  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
 }
 
 /* 不在库卡片 */
@@ -369,7 +344,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header>* {
+  margin-right: 1rem;
+}
+
+.card-header>*:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -382,17 +364,31 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right>* {
+  margin-left: 0.75rem;
+}
+
+.card-header-right>*:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info>* {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info>*:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
   font-size: 1.125rem;
   font-weight: 600;
@@ -425,70 +421,50 @@ onMounted(() => {
   color: #3b82f6;
 }
 
-/* 卡片标题样式 */
-:deep(.ant-card-head) {
-  border-bottom: none;
-  padding: 1rem 1.5rem;
-  min-height: auto;
-}
-
-:deep(.ant-card-head-title) {
-  padding: 0;
-}
-
-/* 卡片内容区域 - 选中时显示 */
-:deep(.ant-card-body) {
-  padding: 0;
-  display: block;
+/* 按钮样式 */
+:deep(.van-button--primary) {
+  background-color: #3b82f6;
+  border-color: #3b82f6;
 }
 
-/* 卡片配送方式选择区域 */
-.card-delivery-section {
-  padding: 1rem 1.5rem;
-  background-color: #f8fafc;
-  border-top: 1px solid #e5e7eb;
-  margin-top: 0;
+:deep(.van-button--primary:active) {
+  background-color: #2563eb;
+  border-color: #2563eb;
 }
 
-.option-label {
-  font-size: 0.875rem;
-  font-weight: 600;
-  color: #374151;
-  min-width: 80px;
-  margin: 0;
+:deep(.van-button--disabled) {
+  opacity: 0.5;
+  cursor: not-allowed;
 }
 
-/* Radio Group 样式优化 */
-:deep(.ant-radio-button-wrapper) {
-  display: inline-flex;
-  align-items: center;
-  height: 32px;
-  padding: 0 15px;
-  font-size: 14px;
+.submit-btn {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
 }
 
-:deep(.ant-radio-button-wrapper i) {
-  font-size: 14px;
+:deep(.submit-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
 }
 
-/* Select 样式优化 */
-:deep(.ant-select) {
-  font-size: 14px;
+:deep(.submit-btn.van-button--primary:active) {
+  background-color: #059669 !important;
+  border-color: #059669 !important;
 }
 
-/* 按钮样式 */
-:deep(.ant-btn-primary) {
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
   background-color: #3b82f6;
-  border-color: #3b82f6;
-}
-
-:deep(.ant-btn-primary:hover) {
-  background-color: #2563eb;
-  border-color: #2563eb;
-}
-
-:deep(.ant-btn[disabled]) {
-  opacity: 0.5;
-  cursor: not-allowed;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
 }
 </style>

+ 446 - 0
src/stock-in/ReturnedLeave.vue

@@ -0,0 +1,446 @@
+<!-- 还料离开 -->
+<template>
+  <div class="page-container">
+    <!-- 顶部信息栏 -->
+    <PageHeader title="还料离开" />
+
+    <!-- 主内容区域 -->
+    <main class="main-content">
+      <!-- 页面标题 -->
+      <div class="page-header-row">
+        <van-button type="primary" @click="verify">
+          <i class="fas fa-sync-alt mr-1" /> 重新校验
+        </van-button>
+      </div>
+
+      <!-- 卡片容器 -->
+      <div class="card-container">
+        <!-- 卡片列表区域 -->
+        <div class="card-list-wrapper">
+          <van-empty v-if="materialList.length === 0" description="暂无数据" />
+
+          <!-- 卡片列表 -->
+          <div v-else class="card-list">
+            <div
+              v-for="(item, index) in materialList" :key="item.id || index" :class="getCardClass(item.remarks)"
+              class="inventory-card"
+            >
+              <div class="card-header">
+                <div class="card-header-left">
+                  <div class="custom-avatar">
+                    <!-- :style="{ backgroundColor: getStatusColor(item.remarks) }" -->
+                    <i :class="getInventoryIcon(item.inventoryType)" />
+                  </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
+                  </div>
+                </div>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.positionName || '-' }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ item.remarks || '-' }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 分页区域(固定底部) -->
+        <div v-if="materialList.length > 0" class="pagination-wrapper">
+          <span class="text-gray-600">共 {{ materialList.length }} 条数据</span>
+        </div>
+      </div>
+    </main>
+    <!-- 底部完成按钮 -->
+    <div class="bottom-actions">
+      <van-button
+        type="primary" size="large" :disabled="notInStockCount === 0" class="submit-btn"
+        @click="handleComplete"
+      >
+        <i class="fas fa-check-circle mr-2" />
+        还料离开
+      </van-button>
+    </div>
+  </div>
+  <Loading v-if="loading" />
+</template>
+
+<script setup>
+import { useRouter } from 'vue-router';
+import { showNotify } from 'vant';
+import PageHeader from '../common/PageHeader.vue';
+import { ref, reactive, onMounted, computed } from 'vue';
+import { cfStockIn, createStockIn, cfStockInLeave } from '../api/stockIn.js';
+
+const router = useRouter();
+
+// 表格加载状态
+const loading = ref(false);
+
+// 物料列表数据
+const materialList = ref([]);
+
+// 分页配置
+const pagination = reactive({
+    start: 1,
+    lang: 10,
+    total: 0,
+});
+
+// 计算不在库的数量
+const notInStockCount = computed(() => {
+    return materialList.value.filter(item => item.remarks === '不在库').length;
+});
+
+// 根据 remarks 返回卡片样式类
+const getCardClass = remarks => {
+    if (remarks === '不在库') {
+        return 'not-in-stock-card';
+    } else if (remarks === '在库') {
+        return 'in-stock-card';
+    }
+    return '';
+};
+
+// 获取设备类型图标
+const getInventoryIcon = type => {
+    const iconMap = {
+        '工装': 'fas fa-cube',
+        '设备': 'fas fa-cogs',
+        '成品': 'fas fa-box',
+    };
+    return iconMap[type] || 'fas fa-cube';
+};
+
+// 获取状态颜色
+// const getStatusColor = remarks => {
+//     if (remarks === '不在库') {
+//         return '#3b82f6'; // 蓝色 - 需要入库
+//     } else if (remarks === '在库') {
+//         return '#10b981'; // 绿色 - 已在库
+//     }
+//     return '#6b7280'; // 灰色 - 默认
+// };
+
+// 还料入库
+const handleComplete = async () => {
+    // 只处理不在库的数据
+    const notInStockItems = materialList.value.filter(item => item.remarks === '不在库');
+
+    if (notInStockItems.length === 0) {
+        showNotify({ type: 'warning', message: '没有需要入库的物料!' });
+        return;
+    }
+
+    const params = [];
+    notInStockItems.forEach(item => {
+        if (item.remarks === '不在库') {
+            params.push({
+                inventoryId: item.inventoryId,
+            });
+        }
+    });
+
+    console.log('提交参数:', params);
+    await generateCFStockIn(params);
+};
+
+// 校验
+const verify = () => {
+    getInnerList();
+};
+
+// 获取物料列表(内侧校验)
+const getInnerList = async () => {
+    loading.value = true;
+    try {
+        const res = await cfStockInLeave();
+
+        if (res.errorCode === 0) {
+            if (res.datas && res.datas.length > 0) {
+                materialList.value = res.datas;
+                pagination.total = materialList.value.length;
+                showNotify({ type: 'warning', message: '检测到您已携带工装设备,请将工装设备全部还料后,再次点击【重新校验】按钮,待校验通过后,请在闸机开门后离开', duration: 8000 });
+            } else {
+                showNotify({ type: 'success', message: '校验成功,请在闸机开门后离开' });
+            }
+        } else {
+            showNotify({ type: 'warning', message: res.errorMessage });
+        }
+    } catch (error) {
+        console.error('获取物料列表API调用失败:', error);
+        showNotify({ type: 'danger', message: '获取物料列表API调用失败' });
+    } finally {
+        loading.value = false;
+    }
+};
+// 生成CF入库单
+const generateCFStockIn = async params => {
+    loading.value = true;
+    try {
+        const res = await createStockIn(params);
+
+        if (res.errorCode === 0) {
+            showNotify({ type: 'success', message: '入库申请已完成,还料任务已创建!' });
+            router.push('/home');
+        } else {
+            showNotify({ type: 'warning', message: res.errorMessage });
+        }
+
+    } catch (error) {
+        console.error('生成CF入库单API调用失败:', error);
+        showNotify({ type: 'danger', message: '生成CF入库单API调用失败' });
+    } finally {
+        loading.value = false;
+    }
+};
+
+onMounted(() => {
+    getInnerList();
+});
+</script>
+
+<style scoped>
+/* 页面容器 - 固定高度布局 */
+.page-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #f9fafb;
+  overflow: hidden;
+}
+
+/* 主内容区 - 可滚动 */
+.main-content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 1rem;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 操作按钮行 */
+.page-header-row {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  margin-bottom: 1rem;
+}
+
+/* 卡片容器 */
+.card-container {
+  flex: 1;
+  background-color: white;
+  border-radius: 0.5rem;
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 0;
+}
+
+/* 卡片列表包装器(可滚动区域) */
+.card-list-wrapper {
+  flex: 1;
+  overflow-y: auto;
+  padding: 1.5rem;
+  min-height: 0;
+}
+
+/* 卡片列表(单列布局) */
+.card-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.card-list>* {
+  margin-bottom: 1rem;
+}
+
+.card-list>*:last-child {
+  margin-bottom: 0;
+}
+
+/* 分页包装器(固定底部) */
+.pagination-wrapper {
+  padding: 0.5rem 1.5rem;
+  border-top: 1px solid #e5e7eb;
+  border-bottom: 1px solid #e5e7eb;
+  background-color: #fafafa;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+
+/* 底部操作按钮 */
+.bottom-actions {
+  position: sticky;
+  bottom: 0;
+  padding: 0 1rem 1rem 1rem;
+  background-color: #f9fafb;
+  display: flex;
+  justify-content: flex-end;
+  z-index: 10;
+}
+
+/* 库存卡片样式 */
+.inventory-card {
+  padding: 1rem 1.5rem;
+  transition: all 0.25s ease;
+  border: 2px solid #e5e7eb;
+  background-color: #ffffff;
+  border-radius: 0.5rem;
+  cursor: pointer;
+}
+
+.inventory-card:hover {
+  border-color: #93c5fd;
+  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
+}
+
+/* 不在库卡片 */
+.not-in-stock-card {
+  background-color: #eff6ff !important;
+  border-color: #93c5fd !important;
+}
+
+/* 在库卡片 */
+.in-stock-card {
+  background-color: #f5f5f5 !important;
+  border-color: #d1d5db !important;
+}
+
+/* 卡片标题区域 */
+.card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.card-header>* {
+  margin-right: 1rem;
+}
+
+.card-header>*:last-child {
+  margin-right: 0;
+}
+
+.card-header-left {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  min-width: 0;
+}
+
+.card-header-right {
+  display: flex;
+  align-items: center;
+}
+
+.card-header-right>* {
+  margin-left: 0.75rem;
+}
+
+.card-header-right>*:first-child {
+  margin-left: 0;
+}
+
+/* 卡片标题信息 */
+.card-title-info {
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+}
+
+.card-title-info>* {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info>*:last-child {
+  margin-bottom: 0;
+}
+
+.card-name {
+  font-size: 1.125rem;
+  font-weight: 600;
+  color: #111827;
+  line-height: 1.5;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.card-subtitle {
+  font-size: 0.875rem;
+  color: #6b7280;
+  line-height: 1.4;
+}
+
+/* 卡片位置信息 */
+.card-location {
+  display: flex;
+  align-items: center;
+  padding: 0.5rem 1rem;
+  background-color: #f3f4f6;
+  border-radius: 0.375rem;
+  font-size: 0.875rem;
+  color: #374151;
+  white-space: nowrap;
+}
+
+.card-location i {
+  color: #3b82f6;
+}
+
+/* 按钮样式 */
+:deep(.van-button--primary) {
+  background-color: #3b82f6;
+  border-color: #3b82f6;
+}
+
+:deep(.van-button--primary:active) {
+  background-color: #2563eb;
+  border-color: #2563eb;
+}
+
+:deep(.van-button--disabled) {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.submit-btn {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
+}
+
+:deep(.submit-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+}
+
+:deep(.submit-btn.van-button--primary:active) {
+  background-color: #059669 !important;
+  border-color: #059669 !important;
+}
+
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
+}
+</style>

+ 26 - 6
src/stock-out/AgvRfidRecognition.vue

@@ -168,10 +168,10 @@
 
 <script setup>
 import { useRouter } from 'vue-router';
-import { message } from 'ant-design-vue';
 import { ref, computed, onMounted } from 'vue';
 import PageHeader from '../common/PageHeader.vue';
 import { getAgvRfidLogByTaskId } from '../api/stockOut.js';
+import { showNotify } from 'vant';
 
 // 路由
 const router = useRouter();
@@ -253,11 +253,19 @@ const getAgvRfidLog = async () => {
 
             // message.success('获取识别结果成功');
         } else {
-            message.warning('暂无识别记录');
+            showNotify({
+                type: 'warning',
+                message: '暂无识别记录',
+                duration: 3000,
+            });
         }
     } catch (error) {
         console.error('获取RFID识别记录失败:', error);
-        message.error('获取识别结果失败');
+        showNotify({
+            type: 'danger',
+            message: '获取识别结果失败',
+            duration: 3000,
+        });
     } finally {
         loading.value = false;
     }
@@ -294,7 +302,11 @@ const handleBack = () => {
 
 // 重新识别
 const handleReIdentify = () => {
-    message.info('正在重新识别 RFID 标签...');
+    showNotify({
+        type: 'warning',
+        message: '正在重新识别 RFID 标签...',
+        duration: 3000,
+    });
     // 示例:重置识别结果
     // identifiedDevices.value = [];
     // taskStatus.value = { success: false, text: '识别中...' };
@@ -302,13 +314,21 @@ const handleReIdentify = () => {
 
 // 手动确认
 const handleManualConfirm = () => {
-    message.success('手动确认成功,任务继续执行');
+    showNotify({
+        type: 'success',
+        message: '手动确认成功,任务继续执行',
+        duration: 3000,
+    });
     router.back();
 };
 
 // 取消任务
 const handleCancelTask = () => {
-    message.warning('任务已取消');
+    showNotify({
+        type: 'warning',
+        message: '任务已取消',
+        duration: 3000,
+    });
     router.back();
 };
 </script>

+ 581 - 214
src/stock-out/OrderPicking.vue

@@ -2,144 +2,163 @@
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="拣货管理" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
       <!-- 页面标题 -->
-      <div class="page-title">
+      <!-- <div class="page-title">
         <h2>拣货管理</h2>
-      </div>
+      </div> -->
 
       <!-- 筛选区域 -->
-      <FilterPanel :default-collapsed="false" :active-count="getActiveFilterCount()">
-        <a-form layout="inline" class="filter-form">
-          <a-form-item label="名称">
-            <a-input
-              v-model:value="filterForm.inventoryName" placeholder="输入名称" style="width: 150px"
+      <FilterPanel :default-collapsed="false" :enable-collapse="false" :active-count="getActiveFilterCount()">
+        <div class="filter-form">
+          <div class="filter-item">
+            <label class="filter-label">名称:</label>
+            <van-field
+              v-model="filterForm.inventoryName" placeholder="输入名称" class="filter-input"
               @keyup.enter="getList"
             />
-          </a-form-item>
-          <a-form-item label="编号">
-            <a-input
-              v-model:value="filterForm.inventoryNo" placeholder="输入编号" style="width: 150px"
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">编号:</label>
+            <van-field
+              v-model="filterForm.inventoryNo" placeholder="输入编号" class="filter-input"
               @keyup.enter="getList"
             />
-          </a-form-item>
-          <a-form-item label="类型">
-            <a-select
-              v-model:value="filterForm.inventoryType" placeholder="选择类型" option-filter-prop="label" show-search
-              allow-clear style="width: 150px" :options="inventoryTypeList" @change="getList"
-            />
-          </a-form-item>
-          <a-form-item label="仓库">
-            <a-select
-              v-model:value="filterForm.warehouseId" :options="warehouseList" placeholder="选择仓库"
-              option-filter-prop="name" show-search allow-clear :field-names="{ label: 'name', value: 'id' }"
-              style="width: 150px" @change="getList"
-            />
-          </a-form-item>
-          <a-form-item label="货位">
-            <a-input
-              v-model:value="filterForm.positionName" placeholder="输入货位名称" style="width: 150px"
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">类型:</label>
+            <v-select
+              v-model="filterForm.inventoryType" :options="inventoryTypeList" :reduce="item => item.value"
+              label="label" placeholder="选择类型" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getList"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">仓库:</label>
+            <v-select
+              v-model="filterForm.warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
+              placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getList"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">货位:</label>
+            <van-field
+              v-model="filterForm.positionName" placeholder="输入货位名称" class="filter-input"
               @keyup.enter="getList"
             />
-          </a-form-item>
-          <a-form-item>
-            <a-button type="primary" @click="getList">
+          </div>
+          <div class="filter-item filter-buttons">
+            <van-button type="primary" @click="getList">
               <i class="fas fa-search mr-1" /> 搜索
-            </a-button>
-            <a-button class="ml-2" @click="handleReset">
+            </van-button>
+            <van-button class="ml-2" @click="handleReset">
               <i class="fas fa-redo mr-1" /> 重置
-            </a-button>
-          </a-form-item>
-        </a-form>
+            </van-button>
+          </div>
+        </div>
       </FilterPanel>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 全选控制栏 -->
+        <div v-if="materialList.length > 0" class="select-all-bar">
+          <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
+            <span class="select-all-text">全选当前页(已选 {{ selectedIds.length }} 项)</span>
+          </van-checkbox>
+        </div>
+
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
-          <a-empty v-if="materialList.length === 0" description="暂无数据" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <!-- 空状态 -->
+          <van-empty v-if="materialList.length === 0" description="暂无数据" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
-              v-for="(item, index) in materialList" :key="item.id || index" :hoverable="true"
+            <div
+              v-for="(item, index) in materialList" :key="item.id || index"
               :class="{ 'selected-card': selectedIds.includes(item.id) }" class="inventory-card"
               @click="toggleSelect(item.id)"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-checkbox
-                      :checked="selectedIds.includes(item.id)" @click.stop
-                      @change="e => handleCheckboxChange(e, item.id)"
-                    />
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }" class="ml-3">
-                      <template #icon>
-                        <i :class="getInventoryIcon(item.inventoryType)" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ item.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <van-checkbox
+                    :model-value="selectedIds.includes(item.id)" @click.stop
+                    @update:model-value="checked => handleCheckboxChange(checked, item.id)"
+                  />
+                  <div class="custom-avatar ml-3">
+                    <i :class="getInventoryIcon(item.inventoryType)" />
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ item.positionName || '-' }}</span>
-                      <span class="mx-2">/</span>
-                      <span>{{ item.warehouseName || '-' }}</span>
-                    </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
                   </div>
                 </div>
-              </template>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.positionName || '-' }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ item.warehouseName || '-' }}</span>
+                  </div>
+                </div>
+              </div>
 
               <!-- 卡片展开内容:配送方式选择(只有选中时显示) -->
-              <template v-if="selectedIds.includes(item.id)">
-                <div class="card-delivery-section" @click.stop>
-                  <!-- <div class="delivery-section-title">
-                    <i class="fas fa-truck mr-2" />
-                    配送方式设置
-                  </div> -->
-
-                  <div class="delivery-options">
-                    <!-- 配送方式选择 -->
-                    <div class="delivery-option-item">
-                      <label class="option-label">配送方式:</label>
-                      <a-radio-group
-                        v-model:value="item.deliveryMethod" button-style="solid"
-                        @change="handleDeliveryMethodChange(item)"
+              <div v-if="selectedIds.includes(item.id)" class="card-delivery-section" @click.stop>
+                <div class="delivery-options">
+                  <!-- 配送方式选择 -->
+                  <div class="delivery-option-item">
+                    <label class="option-label">配送方式:</label>
+                    <div class="delivery-method-buttons">
+                      <button
+                        :class="['delivery-btn', 'agv-btn', { 'active': item.deliveryMethod === 'AGV_Delivery', 'disabled': item.deliveryType === '人工配送' }]"
+                        :disabled="item.deliveryType === '人工配送'" @click="changeDeliveryMethod(item, 'AGV_Delivery')"
                       >
-                        <a-radio-button value="AGV_Delivery" :disabled="item.deliveryType === '人工配送'">
-                          <i class="fas fa-robot mr-1" /> AGV 配送
-                        </a-radio-button>
-                        <a-radio-button value="Manual_Delivery" :disabled="item.deliveryType === '强制AGV配送'">
-                          <i class="fas fa-user mr-1" /> 人工配送
-                        </a-radio-button>
-                      </a-radio-group>
-                      <span
-                        v-if="item.deliveryType" class="delivery-type-badge"
-                        :class="getDeliveryTypeBadgeClass(item.deliveryType)"
+                        <i class="fas fa-robot" /> AGV 配送
+                      </button>
+                      <button
+                        :class="['delivery-btn', 'manual-btn', { 'active': item.deliveryMethod === 'Manual_Delivery', 'disabled': item.deliveryType === '强制AGV配送' }]"
+                        :disabled="item.deliveryType === '强制AGV配送'"
+                        @click="changeDeliveryMethod(item, 'Manual_Delivery')"
                       >
-                        {{ item.deliveryType }}
-                      </span>
+                        <i class="fas fa-user" /> 人工配送
+                      </button>
                     </div>
+                    <span
+                      v-if="item.deliveryType" class="delivery-type-badge"
+                      :class="getDeliveryTypeBadgeClass(item.deliveryType)"
+                    >
+                      {{ item.deliveryType }}
+                    </span>
+                  </div>
 
-                    <!-- 配送位置选择(仅 AGV 配送时显示) -->
-                    <div v-if="item.deliveryMethod === 'AGV_Delivery'" class="delivery-location-item">
-                      <label class="option-label">配送位置:</label>
-                      <a-select
-                        v-model:value="item.selectedLocation" placeholder="请选择" style="width: 200px"
-                        :options="locator"
-                      />
-                    </div>
+                  <!-- 配送位置选择(仅 AGV 配送时显示) -->
+                  <div v-if="item.deliveryMethod === 'AGV_Delivery'" class="delivery-location-item">
+                    <label class="option-label">配送位置:</label>
+                    <v-select
+                      v-model="item.selectedLocation" :options="locator" placeholder="请选择" :clearable="false"
+                      class="location-select"
+                    >
+                      <template #no-options>
+                        <span>无该选项数据</span>
+                      </template>
+                    </v-select>
                   </div>
                 </div>
-              </template>
-            </a-card>
+              </div>
+            </div>
           </div>
         </div>
 
@@ -148,28 +167,38 @@
           <span class="text-gray-600">共 {{ materialList.length }} 条数据</span>
         </div>
       </div>
-
-      <!-- 底部完成按钮 -->
-      <div class="bottom-actions">
-        <a-button
-          type="primary" size="large" :disabled="selectedIds.length === 0" class="shadow-sm"
-          @click="handleComplete"
-        >
-          <template #icon>
-            <i class="fas fa-check-circle mr-2" />
-          </template>
-          拣货 ({{ selectedIds.length }})
-        </a-button>
-      </div>
     </main>
+    <!-- 底部完成按钮 -->
+    <div class="bottom-actions">
+      <van-button
+        type="primary" size="large" :disabled="selectedIds.length === 0" class="complete-btn"
+        @click="handleComplete"
+      >
+        <i class="fas fa-check-circle mr-2" />
+        拣货 ({{ selectedIds.length }})
+      </van-button>
+    </div>
+
+    <!-- 拣货完成确认弹窗 -->
+    <van-dialog
+      v-model:show="completeModalVisible" title="拣货完成" show-cancel-button class-name="large-dialog"
+      confirm-button-text="确认" cancel-button-text="取消" @confirm="handleCompleteConfirm" @cancel="handleCompleteCancel"
+    >
+      <div class="large-dialog-content">
+        <p>领料申请已完成,配送任务已创建!请确认是否返回主页?</p>
+        <!-- <p class="dialog-subtitle">如果是请点击【确认】按钮</p> -->
+      </div>
+    </van-dialog>
   </div>
   <Loading v-if="loading" />
 </template>
 
 <script setup>
 import { useRouter } from 'vue-router';
-import { ref, reactive, onMounted } from 'vue';
-import { message, Empty } from 'ant-design-vue';
+import { ref, reactive, computed, onMounted } from 'vue';
+import { showNotify } from 'vant';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
 import PageHeader from '../common/PageHeader.vue';
 import FilterPanel from '../common/FilterPanel.vue';
 import { list, createStockOut } from '../api/stockOut.js';
@@ -195,6 +224,9 @@ const inventoryTypeList = ref([
 // 表格加载状态
 const loading = ref(false);
 
+// 完成弹窗控制
+const completeModalVisible = ref(false);
+
 // 物料列表数据
 const materialList = ref([]);
 
@@ -209,6 +241,12 @@ const pagination = reactive({
 const selectedIds = ref([]);
 const selectedRows = ref([]);
 
+// 计算是否全选
+const isAllSelected = computed(() => {
+    if (materialList.value.length === 0) return false;
+    return materialList.value.every(item => selectedIds.value.includes(item.id));
+});
+
 // 可配送位置
 const locator = ref([]);
 
@@ -262,8 +300,8 @@ const toggleSelect = id => {
 };
 
 // 处理复选框变化
-const handleCheckboxChange = (e, id) => {
-    if (e.target.checked) {
+const handleCheckboxChange = (checked, id) => {
+    if (checked) {
         if (!selectedIds.value.includes(id)) {
             selectedIds.value.push(id);
             const item = materialList.value.find(item => item.id === id);
@@ -282,6 +320,25 @@ const handleCheckboxChange = (e, id) => {
     }
 };
 
+// 全选/取消全选
+const toggleSelectAll = checked => {
+    if (checked) {
+    // 全选当前页所有项(累加模式)
+        materialList.value.forEach(item => {
+            if (!selectedIds.value.includes(item.id)) {
+                selectedIds.value.push(item.id);
+                initDeliveryMethod(item);
+                selectedRows.value.push(item);
+            }
+        });
+    } else {
+    // 取消全选当前页
+        const currentPageIds = materialList.value.map(item => item.id);
+        selectedIds.value = selectedIds.value.filter(id => !currentPageIds.includes(id));
+        selectedRows.value = selectedRows.value.filter(row => !currentPageIds.includes(row.id));
+    }
+};
+
 // 获取设备类型图标
 const getInventoryIcon = type => {
     const iconMap = {
@@ -325,7 +382,7 @@ const addToRequisition = record => {
 // 领料申请,直接验证并提交
 const handleComplete = async () => {
     if (selectedIds.value.length === 0) {
-        message.warning('请选择物料加入领料申请!');
+        showNotify({ type: 'warning', message: '请至少选择一个物料' });
         return;
     }
 
@@ -334,7 +391,7 @@ const handleComplete = async () => {
     const hasEmptyLocation = agvItems.some(item => !item.selectedLocation);
 
     if (hasEmptyLocation) {
-        message.warning('请为所有 AGV 配送的物料选择配送位置');
+        showNotify({ type: 'warning', message: '请为所有 AGV 配送的物料选择配送位置' });
         return;
     }
 
@@ -343,7 +400,7 @@ const handleComplete = async () => {
         params.push({
             stockOutPrepareLineId: item.id,
             deliveryMethod: item.deliveryMethod,
-            positionEndNo: item.selectedLocation,
+            positionEndNo: item.selectedLocation.value,
         });
     });
 
@@ -353,12 +410,13 @@ const handleComplete = async () => {
 
 
 // 配送方式变更处理
-const handleDeliveryMethodChange = record => {
+const changeDeliveryMethod = (item, method) => {
+    item.deliveryMethod = method;
     // 当选择人工配送时,清空配送位置
-    if (record.deliveryMethod === 'Manual_Delivery') {
-        record.selectedLocation = '';
+    if (method === 'Manual_Delivery') {
+        item.selectedLocation = '';
     }
-    console.log('配送方式变更:', record.inventoryName, record.deliveryMethod);
+    console.log('配送方式变更:', item.inventoryName, item.deliveryMethod);
 };
 
 
@@ -421,11 +479,12 @@ const getList = async () => {
                 materialList.value = [];
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         loading.value = false;
         console.error('获取物料列表API调用失败:', error);
+        showNotify({ type: 'danger', message: '获取物料列表API调用失败' });
         return;
     }
 };
@@ -436,23 +495,37 @@ const generateCFStockOut = async params => {
         const res = await createStockOut(params);
 
         if (res.errorCode === 0) {
-            message.success('领料申请已完成,配送任务已创建!');
+            showNotify({ type: 'success', message: '领料完成' });
             selectedIds.value = [];
             selectedRows.value = [];
-            // getList();
-            router.push('/home');
+            // 显示完成确认弹窗
+            completeModalVisible.value = true;
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('生成CF出库单API调用失败:', error);
-        message.error('生成CF出库单API调用失败');
+        showNotify({ type: 'danger', message: '生成CF出库单API调用失败' });
     } finally {
         loading.value = false;
     }
 };
 
+// 处理完成确认
+const handleCompleteConfirm = () => {
+    completeModalVisible.value = false;
+    router.push('/home');
+    // showToast('领料申请已完成,配送任务已创建!');
+};
+
+// 处理完成取消
+const handleCompleteCancel = () => {
+    completeModalVisible.value = false;
+    // 留在当前页面,刷新列表
+    getList();
+};
+
 // 获取仓库列表
 const getWarehouse = async () => {
     try {
@@ -465,11 +538,11 @@ const getWarehouse = async () => {
                 warehouseList.value = [];
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取仓库列表API调用失败:', error);
-        message.error('获取仓库列表API调用失败');
+        showNotify({ type: 'danger', message: '获取仓库列表API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -491,11 +564,11 @@ const getIdleLocator = async () => {
                 locator.value = [];
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取空闲货位API调用失败:', error);
-        message.error('获取空闲货位API调用失败');
+        showNotify({ type: 'danger', message: '获取空闲货位API调用失败' });
     }
 };
 
@@ -504,19 +577,6 @@ onMounted(() => {
     getList();
     getIdleLocator();
 });
-
-
-//  获取分页参数
-// const getPageParams = (current, pageSize) => {
-//     pagination.start = current;
-//     pagination.lang = pageSize;
-//     getList();
-// };
-
-// 查询回到第一页
-// const getDatas = () => {
-//     commonTableRef.value.backFirstPage();
-// };
 </script>
 
 <style scoped>
@@ -533,7 +593,7 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
@@ -574,14 +634,21 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+  font-weight: 600;
+}
+
+.card-list > * {
+  margin-bottom: 1rem;
+}
+
+.card-list > *:last-child {
+  margin-bottom: 0;
 }
 
 /* 分页包装器(固定底部) */
 .pagination-wrapper {
   padding: 0.5rem 1.5rem;
   border-top: 1px solid #e5e7eb;
-  border-bottom: 1px solid #e5e7eb;
   background-color: #fafafa;
   display: flex;
   justify-content: flex-end;
@@ -592,7 +659,7 @@ onMounted(() => {
 .bottom-actions {
   position: sticky;
   bottom: 0;
-  padding: 1rem 1rem 0 1rem;
+  padding: 0 1rem 1rem 1rem;
   background-color: #f9fafb;
   display: flex;
   justify-content: flex-end;
@@ -603,16 +670,108 @@ onMounted(() => {
 .filter-form {
   display: flex;
   flex-wrap: wrap;
-  gap: 0.5rem;
+  align-items: center;
 }
 
-:deep(.ant-form-item) {
-  margin-bottom: 0 !important;
+.filter-form > * {
+  margin-right: 1rem;
+  margin-bottom: 0.4rem;
 }
 
-:deep(.ant-form-item-label > label) {
-  font-size: 14px !important;
-  font-weight: 600 !important;
+.filter-item {
+  display: flex;
+  align-items: center;
+}
+
+.filter-item > * {
+  margin-right: 0.5rem;
+}
+
+.filter-item > *:last-child {
+  margin-right: 0;
+}
+
+.filter-label {
+  font-size: 14px;
+  font-weight: 600;
+  white-space: nowrap;
+  color: #374151;
+}
+
+.filter-select {
+  width: 200px;
+  position: relative;
+  z-index: 100;
+}
+
+.filter-input {
+  width: 200px;
+}
+
+:deep(.filter-input .van-cell) {
+  padding: 8px 12px !important;
+  border: 1px solid #ccc !important;
+  border-radius: 4px !important;
+  background-color: white !important;
+  min-height: 32px !important;
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.filter-input .van-cell::after) {
+  display: none !important;
+}
+
+:deep(.filter-input .van-field__body) {
+  border: none !important;
+}
+
+:deep(.filter-input .van-field__control) {
+  font-size: 14px;
+}
+
+/* Vue Select 样式 */
+:deep(.v-select) {
+  font-size: 14px;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  border: 1px solid #d1d5db;
+  border-radius: 4px;
+  padding: 4px 8px;
+  min-height: 32px;
+  background: white;
+}
+
+:deep(.v-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+  border: 1px solid #d1d5db;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.v-select .vs__selected) {
+  margin: 2px;
+  padding: 0 4px;
+}
+
+:deep(.v-select .vs__search) {
+  padding: 0;
+  margin: 0;
+}
+
+:deep(.v-select .vs__actions) {
+  padding: 0 4px;
+}
+
+.filter-buttons {
+  display: flex;
+}
+
+.filter-buttons > * {
+  margin-right: 0.5rem;
+}
+
+.filter-buttons > *:last-child {
+  margin-right: 0;
 }
 
 /* 库存卡片样式 */
@@ -641,7 +800,15 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+  padding: 1rem 1.5rem;
+}
+
+.card-header > * {
+  margin-right: 1.5rem;
+}
+
+.card-header > *:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -654,19 +821,33 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right > * {
+  margin-left: 1rem;
+}
+
+.card-header-right > *:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info > * {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info > *:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
-  font-size: 1.125rem;
+  font-size: 1.2rem;
   font-weight: 600;
   color: #111827;
   line-height: 1.5;
@@ -697,21 +878,18 @@ onMounted(() => {
   color: #3b82f6;
 }
 
-/* 卡片标题样式 */
-:deep(.ant-card-head) {
-  border-bottom: none;
-  padding: 1rem 1.5rem;
-  min-height: auto;
-}
-
-:deep(.ant-card-head-title) {
-  padding: 0;
-}
-
-/* 卡片内容区域 - 选中时显示 */
-:deep(.ant-card-body) {
-  padding: 0;
-  display: block;
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
 }
 
 /* 卡片配送方式选择区域 */
@@ -719,41 +897,44 @@ onMounted(() => {
   padding: 1rem 1.5rem;
   background-color: #f8fafc;
   border-top: 1px solid #e5e7eb;
-  margin-top: 0;
-}
-
-.delivery-section-title {
-  font-size: 0.9375rem;
-  font-weight: 600;
-  color: #1f2937;
-  margin-bottom: 1rem;
-  display: flex;
-  align-items: center;
-}
-
-.delivery-section-title i {
-  color: #3b82f6;
-  font-size: 1rem;
 }
 
 .delivery-options {
   display: flex;
   flex-direction: row;
   align-items: center;
-  gap: 2rem;
   flex-wrap: wrap;
 }
 
+.delivery-options > * {
+  margin-right: 2rem;
+  margin-bottom: 1rem;
+}
+
 .delivery-option-item {
   display: flex;
   align-items: center;
-  gap: 0.75rem;
+}
+
+.delivery-option-item > * {
+  margin-right: 0.75rem;
+}
+
+.delivery-option-item > *:last-child {
+  margin-right: 0;
 }
 
 .delivery-location-item {
   display: flex;
   align-items: center;
-  gap: 0.75rem;
+}
+
+.delivery-location-item > * {
+  margin-right: 0.75rem;
+}
+
+.delivery-location-item > *:last-child {
+  margin-right: 0;
 }
 
 .option-label {
@@ -764,6 +945,82 @@ onMounted(() => {
   margin: 0;
 }
 
+/* 配送方式按钮容器 */
+.delivery-method-buttons {
+  display: flex;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+/* 配送方式按钮样式 */
+.delivery-btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 6px 16px;
+  font-size: 14px;
+  font-weight: 500;
+  border: 1px solid #d1d5db;
+  background-color: white;
+  color: #374151;
+  cursor: pointer;
+  transition: all 0.2s;
+  height: 32px;
+  min-width: 110px;
+}
+
+.delivery-btn > * {
+  margin-right: 6px;
+}
+
+.delivery-btn > *:last-child {
+  margin-right: 0;
+}
+
+.delivery-btn:first-child {
+  border-radius: 4px 0 0 4px;
+  border-right: none;
+}
+
+.delivery-btn:last-child {
+  border-radius: 0 4px 4px 0;
+}
+
+.delivery-btn i {
+  font-size: 14px;
+}
+
+/* AGV配送按钮 - 蓝色 */
+.delivery-btn.agv-btn.active {
+  background-color: #3b82f6;
+  color: white;
+  border-color: #3b82f6;
+}
+
+.delivery-btn.agv-btn:hover:not(.disabled):not(.active) {
+  background-color: #eff6ff;
+  border-color: #3b82f6;
+  color: #3b82f6;
+}
+
+/* 人工配送按钮 - 蓝色 */
+.delivery-btn.manual-btn.active {
+  background-color: #3b82f6;
+  color: white;
+  border-color: #3b82f6;
+}
+
+.delivery-btn.manual-btn:hover:not(.disabled):not(.active) {
+  background-color: #f3f4f6;
+  border-color: #9ca3af;
+}
+
+/* 禁用状态 */
+.delivery-btn.disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
 .delivery-type-badge {
   margin-left: 0.75rem;
   padding: 0.25rem 0.625rem;
@@ -791,37 +1048,147 @@ onMounted(() => {
   color: #065f46;
 }
 
-/* Radio Group 样式优化 */
-:deep(.ant-radio-button-wrapper) {
-  display: inline-flex;
-  align-items: center;
-  height: 32px;
-  padding: 0 15px;
-  font-size: 14px;
-}
-
-:deep(.ant-radio-button-wrapper i) {
-  font-size: 14px;
-}
-
-/* Select 样式优化 */
-:deep(.ant-select) {
-  font-size: 14px;
+/* 配送位置选择器样式 */
+.location-select {
+  width: 200px;
 }
 
 /* 按钮样式 */
-:deep(.ant-btn-primary) {
+:deep(.van-button--primary) {
   background-color: #3b82f6;
   border-color: #3b82f6;
 }
 
-:deep(.ant-btn-primary:hover) {
+:deep(.van-button--primary:active) {
   background-color: #2563eb;
   border-color: #2563eb;
 }
 
-:deep(.ant-btn[disabled]) {
+.complete-btn {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
+}
+
+:deep(.complete-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+}
+
+:deep(.complete-btn.van-button--primary:active) {
+  background-color: #059669 !important;
+  border-color: #059669 !important;
+}
+
+:deep(.van-button--disabled) {
   opacity: 0.5;
   cursor: not-allowed;
 }
+
+:deep(.van-cell) {
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  padding: 6px 8px !important;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  padding: 6px 8px !important;
+}
+
+/* 全选控制栏 */
+.select-all-bar {
+  background-color: white;
+  padding: 1rem 1.5rem;
+  border-bottom: 1px solid #e5e7eb;
+  display: flex;
+  align-items: center;
+}
+
+.select-all-text {
+  font-size: 0.95rem;
+  color: #374151;
+  font-weight: 500;
+}
+
+/* 大弹窗样式,适合触摸屏 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  padding: 2rem 1.5rem 1rem !important;
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  color: #111827 !important;
+}
+
+.large-dialog-content {
+  padding: 1.5rem 2rem 2.5rem !important;
+}
+
+.large-dialog-content p {
+  margin: 0 0 1rem 0;
+  font-size: 1.25rem !important;
+  line-height: 1.8 !important;
+  color: #374151 !important;
+  text-align: center;
+}
+
+.large-dialog-content p:last-child {
+  margin-bottom: 0;
+}
+
+.large-dialog-content .dialog-subtitle {
+  font-size: 1.125rem !important;
+  color: #6b7280 !important;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding: 1rem 1.5rem 1.5rem !important;
+  display: flex;
+}
+
+:deep(.large-dialog .van-dialog__footer > *) {
+  margin-right: 1rem;
+}
+
+:deep(.large-dialog .van-dialog__footer > *:last-child) {
+  margin-right: 0;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1;
+  height: 56px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel:active) {
+  background-color: #e5e7eb !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #10b981 !important;
+  color: white !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:active) {
+  background-color: #059669 !important;
+}
+
+/* 大弹窗蒙层 */
+:deep(.van-overlay) {
+  background-color: rgba(0, 0, 0, 0.6) !important;
+}
 </style>

+ 111 - 125
src/stock-out/OutboundConfirm.vue

@@ -2,69 +2,58 @@
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader />
+    <PageHeader title="出库确认" />
 
     <!-- 主内容区域 -->
     <main class="main-content">
-      <!-- 页面标题和操作按钮 -->
+      <!-- 操作按钮 -->
       <div class="page-header-row">
-        <div class="page-title">
-          <h2>出库确认</h2>
-        </div>
-        <a-button type="primary" @click="getStockOutList(false)">
+        <van-button type="primary" @click="getStockOutList(false)">
           <i class="fas fa-sync-alt mr-1" /> 重新校验
-        </a-button>
+        </van-button>
       </div>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
-          <a-empty v-if="materialList.length === 0" description="暂无数据" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="materialList.length === 0" description="暂无数据" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
+            <div
               v-for="(item, index) in materialList" :key="item.inventoryId || index"
-              :hoverable="item.positionName !== '不在库' && item.inventoryName !== '未识别epc'"
               :class="{ 'status-card': true, 'completed-card': item.status === 1 }" class="inventory-card"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-avatar :size="42" :style="{ backgroundColor: getStatusColor(item.status) }" class="ml-0">
-                      <template #icon>
-                        <i :class="getInventoryIcon(item.inventoryType)" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ item.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <div class="custom-avatar" :style="{ backgroundColor: getStatusColor(item.status) }">
+                    <i :class="getInventoryIcon(item.inventoryType)" />
+                  </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ item.positionName || '-' }}</span>
-                    </div>
-                    <!-- <div class="status-badge" :class="getStatusClass(item.status)">
-                      {{ getStatusText(item.status) }}
-                    </div> -->
-                    <template v-if="item.positionName !== '不在库' && item.inventoryName !== '未识别epc'">
-                      <a-button v-if="item.status === 1" type="primary" size="small" disabled class="btn-complete">
-                        领料完成
-                      </a-button>
-                      <a-button v-if="item.status === 2" type="primary" size="small" @click="handleStart(item)">
-                        开始领料
-                      </a-button>
-                      <a-button v-if="item.status === 3" type="primary" size="small" @click="handleApply(item)">
-                        申请领料
-                      </a-button>
-                    </template>
+                </div>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.positionName || '-' }}</span>
                   </div>
+                  <template v-if="item.positionName !== '不在库' && item.inventoryName !== '未识别epc'">
+                    <van-button v-if="item.status === 1" type="primary" size="small" disabled class="btn-complete">
+                      领料完成
+                    </van-button>
+                    <van-button v-if="item.status === 2" type="primary" size="small" @click="handleStart(item)">
+                      开始领料
+                    </van-button>
+                    <van-button v-if="item.status === 3" type="primary" size="small" @click="handleApply(item)">
+                      申请领料
+                    </van-button>
+                  </template>
                 </div>
-              </template>
-            </a-card>
+              </div>
+            </div>
           </div>
         </div>
 
@@ -73,20 +62,14 @@
           <span class="text-gray-600">共 {{ materialList.length }} 条数据,已完成 {{ completedCount }} 条</span>
         </div>
       </div>
-
-      <!-- 底部操作按钮 -->
-      <div class="bottom-actions">
-        <a-button
-          type="primary" size="large" class="shadow-sm btn-complete" :disabled="!isCanLeave"
-          @click="handleLeave"
-        >
-          <template #icon>
-            <i class="fas fa-check-circle mr-2" />
-          </template>
-          领料离开
-        </a-button>
-      </div>
     </main>
+    <!-- 底部操作按钮 -->
+    <div class="bottom-actions">
+      <van-button type="primary" size="large" :disabled="!isCanLeave" class="btn-complete" @click="handleLeave">
+        <i class="fas fa-check-circle mr-2" />
+        领料离开
+      </van-button>
+    </div>
   </div>
   <Loading v-if="loading" />
 </template>
@@ -94,7 +77,7 @@
 <script setup>
 import { useRouter } from 'vue-router';
 import { ref, computed, onMounted } from 'vue';
-import { message, Empty } from 'ant-design-vue';
+import { showNotify } from 'vant';
 import PageHeader from '../common/PageHeader.vue';
 import { cfStockOut, createStockOut, cfStockOutLeave } from '../api/stockOut.js';
 
@@ -158,7 +141,7 @@ const handleStart = record => {
 
 // 申请领料
 const handleApply = record => {
-    message.success('申请领料成功');
+    showNotify({ type: 'success', message: '申请领料成功' });
 };
 
 // 领料完成后离开
@@ -175,14 +158,14 @@ const handleLeave = async () => {
         const res = await cfStockOutLeave(params);
 
         if (res.errorCode === 0) {
-            message.success('领料离开成功');
+            showNotify({ type: 'success', message: '领料离开成功' });
             router.push('/home');
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('领料离开API调用失败:', error);
-        message.error('领料离开API调用失败');
+        showNotify({ type: 'danger', message: '领料离开API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -215,11 +198,11 @@ const getStockOutList = async (isOne = false) => {
                 materialList.value = [];
             }
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取扫描到的领料数据API调用失败:', error);
-        message.error('获取扫描到的领料数据API调用失败');
+        showNotify({ type: 'danger', message: '获取扫描到的领料数据API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -240,12 +223,12 @@ const generateCFStockOut = async id => {
         if (res.errorCode === 0) {
             getStockOutList(true);
         } else {
-            message.error(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('生成出库单API调用失败:', error);
-        message.error('生成出库单API调用失败');
+        showNotify({ type: 'danger', message: '生成出库单API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -270,25 +253,17 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
 
-/* 页面标题行 */
+/* 操作按钮行 */
 .page-header-row {
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-end;
   align-items: center;
-  margin-bottom: 1.5rem;
-}
-
-/* 页面标题 */
-.page-title h2 {
-  font-size: 1.5rem;
-  font-weight: 700;
-  color: #111827;
-  margin: 0;
+  margin-bottom: 1rem;
 }
 
 /* 卡片容器 */
@@ -315,7 +290,14 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+}
+
+.card-list>* {
+  margin-bottom: 1rem;
+}
+
+.card-list>*:last-child {
+  margin-bottom: 0;
 }
 
 /* 分页包装器(固定底部) */
@@ -325,7 +307,7 @@ onMounted(() => {
   border-bottom: 1px solid #e5e7eb;
   background-color: #fafafa;
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-end;
   align-items: center;
 }
 
@@ -333,7 +315,7 @@ onMounted(() => {
 .bottom-actions {
   position: sticky;
   bottom: 0;
-  padding: 1rem 1rem 0 1rem;
+  padding: 0 1rem 1rem 1rem;
   background-color: #f9fafb;
   display: flex;
   justify-content: flex-end;
@@ -342,10 +324,12 @@ onMounted(() => {
 
 /* 库存卡片样式 */
 .inventory-card {
+  padding: 1rem 1.5rem;
   transition: all 0.25s ease;
   border: 2px solid #e5e7eb;
   background-color: #ffffff;
   border-radius: 0.5rem;
+  cursor: pointer;
 }
 
 .inventory-card:hover {
@@ -364,7 +348,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header>* {
+  margin-right: 1rem;
+}
+
+.card-header>*:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -377,17 +368,31 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right>* {
+  margin-left: 0.75rem;
+}
+
+.card-header-right>*:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info>* {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info>*:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
   font-size: 1.125rem;
   font-weight: 600;
@@ -420,52 +425,26 @@ onMounted(() => {
   color: #3b82f6;
 }
 
-/* 状态徽章 */
-.status-badge {
-  padding: 0.375rem 0.875rem;
-  border-radius: 0.375rem;
-  font-size: 0.875rem;
-  font-weight: 600;
-  white-space: nowrap;
-}
-
-.status-completed {
-  background-color: #d1fae5;
-  color: #065f46;
-}
-
-.status-processing {
-  background-color: #fed7aa;
-  color: #92400e;
-}
-
-.status-pending {
-  background-color: #dbeafe;
-  color: #1e40af;
-}
-
-/* 卡片标题样式 */
-:deep(.ant-card-head) {
-  border-bottom: none;
-  padding: 1rem 1.5rem;
-  min-height: auto;
-}
-
-:deep(.ant-card-head-title) {
-  padding: 0;
-}
-
-:deep(.ant-card-body) {
-  display: none;
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
 }
 
 /* 按钮样式 */
-:deep(.ant-btn-primary) {
+:deep(.van-button--primary) {
   background-color: #3b82f6;
   border-color: #3b82f6;
 }
 
-:deep(.ant-btn-primary:hover) {
+:deep(.van-button--primary:active) {
   background-color: #2563eb;
   border-color: #2563eb;
 }
@@ -473,14 +452,21 @@ onMounted(() => {
 .btn-complete {
   background-color: #10b981 !important;
   border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
+}
+
+:deep(.btn-complete.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
 }
 
-.btn-complete:hover {
+:deep(.btn-complete.van-button--primary:active) {
   background-color: #059669 !important;
   border-color: #059669 !important;
 }
 
-:deep(.ant-btn[disabled]) {
+:deep(.van-button--disabled) {
   opacity: 0.5;
   cursor: not-allowed;
 }

+ 521 - 133
src/stock/RegularRequisition.vue

@@ -1,12 +1,13 @@
+<!-- 常用领料 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader :is-go-home="false">
+    <PageHeader :is-go-home="false" title="常用领料">
       <template #actions>
         <button class="action-btn cart-btn" @click="openStockOutCar">
-          <a-badge :count="count" show-zero :number-style="{ backgroundColor: '#10b981' }">
+          <van-badge :content="count" :show-zero="true">
             <i class="fas fa-shopping-cart text-xl" />
-          </a-badge>
+          </van-badge>
           <span class="ml-2">领料车</span>
         </button>
       </template>
@@ -15,120 +16,175 @@
     <!-- 主内容区域 -->
     <main class="main-content">
       <!-- 页面标题 -->
-      <div class="page-title">
+      <!-- <div class="page-title">
         <h2>常用领料</h2>
-      </div>
+      </div> -->
 
       <!-- 筛选区域 -->
-      <FilterPanel :default-collapsed="true" :active-count="getActiveFilterCount()">
-        <a-form layout="inline" class="filter-form">
-          <a-form-item label="仓库">
-            <a-select
-              v-model:value="warehouseId" :options="warehouseList" placeholder="选择仓库" option-filter-prop="name" show-search
-              allow-clear :field-names="{ label: 'name', value: 'id' }" style="width: 150px" @change="getDatas"
-            />
-          </a-form-item>
-          <a-form-item label="名称">
-            <a-input v-model:value="inventoryName" placeholder="输入名称" style="width: 150px" @keyup.enter="getDatas" />
-          </a-form-item>
-          <a-form-item label="编号">
-            <a-input v-model:value="inventoryNo" placeholder="输入编号" style="width: 150px" @keyup.enter="getDatas" />
-          </a-form-item>
-          <a-form-item label="类型">
-            <a-select
-              v-model:value="inventoryType" placeholder="选择类型" option-filter-prop="label" show-search allow-clear
-              :options="inventoryTypeList" style="width: 150px" @change="getDatas"
-            />
-          </a-form-item>
-          <a-form-item>
-            <a-button type="primary" @click="getDatas">
+      <FilterPanel :default-collapsed="false" :enable-collapse="false" :active-count="getActiveFilterCount()">
+        <div class="filter-form">
+          <div class="filter-item">
+            <label class="filter-label">仓库</label>
+            <v-select
+              v-model="warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
+              placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getDatas"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">名称</label>
+            <van-field v-model="inventoryName" placeholder="输入名称" class="filter-input" @keyup.enter="getDatas" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">编号</label>
+            <van-field v-model="inventoryNo" placeholder="输入编号" class="filter-input" @keyup.enter="getDatas" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">类型</label>
+            <v-select
+              v-model="inventoryType" :options="inventoryTypeList" :reduce="item => item.value" label="label"
+              placeholder="选择类型" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getDatas"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item filter-buttons">
+            <van-button type="primary" @click="getDatas">
               <i class="fas fa-search mr-1" /> 搜索
-            </a-button>
-            <a-button class="ml-2" @click="handleReset">
+            </van-button>
+            <van-button class="ml-2" @click="handleReset">
               <i class="fas fa-redo mr-1" /> 重置
-            </a-button>
-          </a-form-item>
-        </a-form>
+            </van-button>
+          </div>
+        </div>
       </FilterPanel>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 全选控制栏 -->
+        <div v-if="stockRequisitionList.length > 0" class="select-all-bar">
+          <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
+            <span class="select-all-text">全选当前页(已选 {{ selectedIds.length }} 项)</span>
+          </van-checkbox>
+        </div>
+
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
           <!-- 空状态 -->
-          <a-empty v-if="stockRequisitionList.length === 0" description="暂无数据" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="stockRequisitionList.length === 0" description="暂无数据" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
-              v-for="(item, index) in stockRequisitionList" :key="item.id || index" :hoverable="true"
+            <div
+              v-for="(item, index) in stockRequisitionList" :key="item.id || index"
               :class="{ 'selected-card': selectedIds.includes(item.id) }" class="inventory-card"
               @click="toggleSelect(item.id)"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-checkbox
-                      :checked="selectedIds.includes(item.id)" @click.stop
-                      @change="e => handleCheckboxChange(e, item.id)"
-                    />
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }" class="ml-3">
-                      <template #icon>
-                        <i :class="getInventoryIcon(item.inventoryType)" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ item.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <van-checkbox
+                    :model-value="selectedIds.includes(item.id)" @click.stop
+                    @update:model-value="checked => handleCheckboxChange(checked, item.id)"
+                  />
+                  <div class="custom-avatar ml-3">
+                    <i :class="getInventoryIcon(item.inventoryType)" />
                   </div>
-                  <div class="card-header-center">
-                    <div class="card-stats">
-                      <div class="stat-item">
-                        <i class="fas fa-history" />
-                        <span class="stat-label">借用次数</span>
-                        <span class="stat-value">{{ item.borrowTotal || 0 }}次</span>
-                      </div>
-                      <div class="stat-item">
-                        <i class="fas fa-clock" />
-                        <span class="stat-label">上次借用</span>
-                        <span class="stat-value">{{ item.lastBorrowTime || '暂无记录' }}</span>
-                      </div>
-                    </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ item.inventoryActulPosition || item.inventoryPosition || '-' }}</span>
-                      <span class="mx-2">/</span>
-                      <span>{{ item.inventoryWarehouse || '-' }}</span>
+                </div>
+                <div class="card-header-center">
+                  <div class="card-stats">
+                    <div class="stat-item">
+                      <i class="fas fa-history" />
+                      <span class="stat-label">借用次数</span>
+                      <span class="stat-value">{{ item.borrowTotal || 0 }}次</span>
+                    </div>
+                    <div class="stat-item">
+                      <i class="fas fa-clock" />
+                      <span class="stat-label">上次借用</span>
+                      <span class="stat-value">{{ item.lastBorrowTime || '暂无记录' }}</span>
                     </div>
                   </div>
                 </div>
-              </template>
-            </a-card>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.inventoryActulPosition || item.inventoryPosition || '-' }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ item.inventoryWarehouse || '-' }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
 
         <!-- 分页区域(固定底部) -->
         <div v-if="stockRequisitionList.length > 0" class="pagination-wrapper">
-          <AntdPagination
-            ref="paginationRef" :pagination="pagination" :options="options"
-            @get-page-params="getPageParams"
-          />
+          <div class="pagination-info">
+            第{{ (pagination.current_page - 1) * pagination.per_page + 1 }}-{{ Math.min(pagination.current_page *
+              pagination.per_page, pagination.total) }}条,共{{ pagination.total }}条
+          </div>
+          <div class="pagination-controls">
+            <button
+              class="pagination-btn" :disabled="pagination.current_page === 1"
+              @click="handlePageChange(pagination.current_page - 1)"
+            >
+              &lt;
+            </button>
+            <template v-for="page in getPageNumbers()" :key="page">
+              <button
+                v-if="page !== '...'" class="pagination-btn" :class="{ active: page === pagination.current_page }"
+                @click="handlePageChange(page)"
+              >
+                {{ page }}
+              </button>
+              <span v-else class="pagination-ellipsis">...</span>
+            </template>
+            <button
+              class="pagination-btn"
+              :disabled="pagination.current_page === Math.ceil(pagination.total / pagination.per_page)"
+              @click="handlePageChange(pagination.current_page + 1)"
+            >
+              &gt;
+            </button>
+          </div>
+          <div class="pagination-size">
+            <v-select
+              v-model="pagination.per_page" :options="pageSizeOptions" :clearable="false" :searchable="false"
+              :append-to-body="true" :calculate-position="withPopper" class="size-select"
+              @update:model-value="handlePageSizeChange"
+            >
+              <template #selected-option="{ label }">
+                {{ label }}条/页
+              </template>
+              <template #option="{ label }">
+                {{ label }}条/页
+              </template>
+            </v-select>
+          </div>
         </div>
       </div>
     </main>
 
     <!-- 底部操作按钮 -->
     <div class="bottom-actions">
-      <a-button
+      <van-button
         type="primary" size="large" :disabled="selectedIds.length === 0" class="add-to-cart-btn"
         @click="submitStock"
       >
         <i class="fas fa-cart-plus mr-2" />
         加入领料车
-      </a-button>
+      </van-button>
     </div>
 
     <Loading v-if="loading" />
@@ -136,9 +192,36 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
+import { ref, computed, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
-import { message, Empty } from 'ant-design-vue';
+import Common from '../common/Common.js';
+import { showNotify } from 'vant';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
+import { createPopper } from '@popperjs/core';
+
+// 为 vue-select 配置 popper
+const withPopper = (dropdownList, component, { width }) => {
+    dropdownList.style.width = width;
+    const popper = createPopper(component.$refs.toggle, dropdownList, {
+        placement: 'bottom',
+        modifiers: [
+            {
+                name: 'offset',
+                options: { offset: [0, -1] },
+            },
+            {
+                name: 'toggleClass',
+                enabled: true,
+                phase: 'write',
+                fn({ state }) {
+                    component.$el.classList.toggle('drop-up', state.placement === 'top');
+                },
+            },
+        ],
+    });
+    return () => popper.destroy();
+};
 import PageHeader from '../common/PageHeader.vue';
 import FilterPanel from '../common/FilterPanel.vue';
 import { getWarehouseList } from '../api/stock.js';
@@ -155,6 +238,12 @@ const count = ref(0);
 const warehouseList = ref([]);
 const stockRequisitionList = ref([]);
 const selectedIds = ref([]);
+
+// 计算是否全选
+const isAllSelected = computed(() => {
+    if (stockRequisitionList.value.length === 0) return false;
+    return stockRequisitionList.value.every(item => selectedIds.value.includes(item.id));
+});
 const loading = ref(false);
 const inventoryTypeList = ref([
     { value: 'Clamp', label: '工装' },
@@ -166,12 +255,7 @@ const pagination = ref({
     current_page: 1,
     per_page: 20,
 });
-const options = {
-    showTotal: true,
-    showSizeChanger: true,
-    showQuickJumper: false,
-    pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
-};
+const pageSizeOptions = [10, 20, 50, 100, 200, 500];
 
 // 计算激活的筛选条件数量
 const getActiveFilterCount = () => {
@@ -199,8 +283,8 @@ const toggleSelect = id => {
 };
 
 // 处理复选框变化
-const handleCheckboxChange = (e, id) => {
-    if (e.target.checked) {
+const handleCheckboxChange = (checked, id) => {
+    if (checked) {
         if (!selectedIds.value.includes(id)) {
             selectedIds.value.push(id);
         }
@@ -212,6 +296,22 @@ const handleCheckboxChange = (e, id) => {
     }
 };
 
+// 全选/取消全选
+const toggleSelectAll = checked => {
+    if (checked) {
+    // 全选当前页所有项(累加模式)
+        stockRequisitionList.value.forEach(item => {
+            if (!selectedIds.value.includes(item.id)) {
+                selectedIds.value.push(item.id);
+            }
+        });
+    } else {
+    // 取消全选当前页
+        const currentPageIds = stockRequisitionList.value.map(item => item.id);
+        selectedIds.value = selectedIds.value.filter(id => !currentPageIds.includes(id));
+    }
+};
+
 // 获取设备类型图标
 const getInventoryIcon = type => {
     const iconMap = {
@@ -223,12 +323,49 @@ const getInventoryIcon = type => {
 };
 
 // 分页改变
-const getPageParams = (page, pageSize) => {
+const handlePageChange = page => {
     pagination.value.current_page = page;
+    getStockRequisitionList();
+};
+
+// 每页大小改变
+const handlePageSizeChange = pageSize => {
     pagination.value.per_page = pageSize;
+    pagination.value.current_page = 1;
     getStockRequisitionList();
 };
 
+// 获取分页页码数组
+const getPageNumbers = () => {
+    const totalPages = Math.ceil(pagination.value.total / pagination.value.per_page);
+    const current = pagination.value.current_page;
+    const pages = [];
+
+    if (totalPages <= 7) {
+        for (let i = 1; i <= totalPages; i++) {
+            pages.push(i);
+        }
+    } else {
+        if (current <= 3) {
+            for (let i = 1; i <= 5; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        } else if (current >= totalPages - 2) {
+            pages.push(1);
+            pages.push('...');
+            for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
+        } else {
+            pages.push(1);
+            pages.push('...');
+            for (let i = current - 1; i <= current + 1; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        }
+    }
+
+    return pages;
+};
+
 // 查询物料数据
 const getStockRequisitionList = async () => {
     loading.value = true;
@@ -255,7 +392,7 @@ const getStockRequisitionList = async () => {
             }
         }
     } catch (error) {
-        message.error('查询常用物料API失败');
+        showNotify({ type: 'danger', message: '查询常用物料API失败' });
         console.error('查询常用物料API失败', error);
     } finally {
         loading.value = false;
@@ -273,11 +410,11 @@ const getWarehouses = async () => {
                 warehouseList.value = [];
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取仓库数据失败:', error);
-        message.error('获取仓库数据失败');
+        showNotify({ type: 'danger', message: '获取库存数据失败' });
     }
 };
 
@@ -300,7 +437,7 @@ const getDatas = () => {
 // 加入领料车
 const submitStock = async () => {
     if (selectedIds.value.length == 0) {
-        message.warning('请至少选择一个工装设备');
+        showNotify({ type: 'warning', message: '请至少选择一个物料' });
         return;
     }
     loading.value = true;
@@ -314,11 +451,11 @@ const submitStock = async () => {
             selectedIds.value = [];
             getDatas();
             queryPickingCarCount();
-            message.success('添加领料车成功');
+            showNotify({ type: 'success', message: '已添加到领料车' });
         }
     } catch (error) {
         console.error('添加领料车失败:', error);
-        message.error('添加领料车失败');
+        showNotify({ type: 'danger', message: '添加到领料车失败' });
     } finally {
         loading.value = false;
     }
@@ -337,12 +474,12 @@ const queryPickingCarCount = async () => {
                 count.value = 0;
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('查询领料车数量失败:', error);
-        message.error('查询领料车数量失败');
+        showNotify({ type: 'danger', message: '查询领料车数量失败' });
     }
 };
 onMounted(() => {
@@ -366,7 +503,7 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
@@ -407,7 +544,15 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+  font-weight: 600;
+}
+
+.card-list > * {
+  margin-bottom: 1rem;
+}
+
+.card-list > *:last-child {
+  margin-bottom: 0;
 }
 
 /* 分页包装器(固定底部) */
@@ -420,6 +565,90 @@ onMounted(() => {
   align-items: center;
 }
 
+.pagination-wrapper > * {
+  margin-left: 1rem;
+}
+
+.pagination-wrapper > *:first-child {
+  margin-left: 0;
+}
+
+.pagination-info {
+  font-size: 14px;
+  color: #6b7280;
+  white-space: nowrap;
+}
+
+.pagination-controls {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-controls > * {
+  margin-right: 0.25rem;
+}
+
+.pagination-controls > *:last-child {
+  margin-right: 0;
+}
+
+.pagination-btn {
+  min-width: 32px;
+  height: 32px;
+  padding: 0 8px;
+  border: 1px solid #d1d5db;
+  background: white;
+  color: #374151;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.2s;
+}
+
+.pagination-btn:hover:not(:disabled) {
+  border-color: #3b82f6;
+  color: #3b82f6;
+}
+
+.pagination-btn.active {
+  background: #3b82f6;
+  border-color: #3b82f6;
+  color: white;
+}
+
+.pagination-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.pagination-ellipsis {
+  padding: 0 4px;
+  color: #6b7280;
+}
+
+.pagination-size {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-size > * {
+  margin-right: 0.5rem;
+}
+
+.pagination-size > *:last-child {
+  margin-right: 0;
+}
+
+.size-select {
+  width: 150px;
+  position: relative;
+  z-index: 50;
+}
+
+:deep(.size-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+}
+
 /* 底部操作按钮 */
 .bottom-actions {
   position: sticky;
@@ -454,16 +683,108 @@ onMounted(() => {
 .filter-form {
   display: flex;
   flex-wrap: wrap;
-  gap: 0.5rem;
+  align-items: center;
+}
+
+.filter-form > * {
+  margin-right: 1rem;
+  margin-bottom: 0.4rem;
+}
+
+.filter-item {
+  display: flex;
+  align-items: center;
+}
+
+.filter-item > * {
+  margin-right: 0.5rem;
 }
 
-:deep(.ant-form-item) {
-  margin-bottom: 0 !important;
+.filter-item > *:last-child {
+  margin-right: 0;
 }
 
-:deep(.ant-form-item-label > label) {
-  font-size: 14px !important;
-  font-weight: 600 !important;
+.filter-label {
+  font-size: 14px;
+  font-weight: 600;
+  white-space: nowrap;
+  color: #374151;
+}
+
+.filter-select {
+  width: 200px;
+  position: relative;
+  z-index: 100;
+}
+
+.filter-input {
+  width: 200px;
+}
+
+:deep(.filter-input .van-cell) {
+  padding: 8px 12px !important;
+  border: 1px solid #d1d5db !important;
+  border-radius: 4px !important;
+  background-color: white !important;
+  min-height: 32px !important;
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.filter-input .van-cell::after) {
+  display: none !important;
+}
+
+:deep(.filter-input .van-field__body) {
+  border: none !important;
+}
+
+:deep(.filter-input .van-field__control) {
+  font-size: 14px;
+}
+
+/* Vue Select 样式 */
+:deep(.v-select) {
+  font-size: 14px;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  border: 1px solid #d1d5db;
+  border-radius: 4px;
+  padding: 4px 8px;
+  min-height: 32px;
+  background: white;
+}
+
+:deep(.v-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+  border: 1px solid #d1d5db;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.v-select .vs__selected) {
+  margin: 2px;
+  padding: 0 4px;
+}
+
+:deep(.v-select .vs__search) {
+  padding: 0;
+  margin: 0;
+}
+
+:deep(.v-select .vs__actions) {
+  padding: 0 4px;
+}
+
+.filter-buttons {
+  display: flex;
+}
+
+.filter-buttons > * {
+  margin-right: 0.5rem;
+}
+
+.filter-buttons > *:last-child {
+  margin-right: 0;
 }
 
 /* 库存卡片样式 */
@@ -492,7 +813,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header > * {
+  margin-right: 1.5rem;
+}
+
+.card-header > *:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -511,20 +839,34 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
   flex-shrink: 0;
 }
 
+.card-header-right > * {
+  margin-left: 1rem;
+}
+
+.card-header-right > *:first-child {
+  margin-left: 0;
+}
+
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info > * {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info > *:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
-  font-size: 1.125rem;
+  font-size: 1.2rem;
   font-weight: 600;
   color: #111827;
   line-height: 1.5;
@@ -563,20 +905,34 @@ onMounted(() => {
 .card-stats {
   display: flex;
   align-items: center;
-  gap: 1.5rem;
   padding: 0.4rem 1.25rem;
   background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
   border-radius: 0.5rem;
-  border: 1px solid #bae6fd;
+  margin-top: 0.75rem;
+}
+
+.card-stats > * {
+  margin-right: 1.5rem;
+}
+
+.card-stats > *:last-child {
+  margin-right: 0;
 }
 
 .stat-item {
   display: flex;
   align-items: center;
-  gap: 0.5rem;
   color: #0369a1;
 }
 
+.stat-item > * {
+  margin-right: 0.5rem;
+}
+
+.stat-item > *:last-child {
+  margin-right: 0;
+}
+
 .stat-item i {
   font-size: 0.875rem;
   color: #0284c7;
@@ -594,32 +950,32 @@ onMounted(() => {
   color: #0c4a6e;
 }
 
-/* 卡片标题样式 */
-:deep(.ant-card-head) {
-  border-bottom: none;
-  padding: 1rem 1.5rem;
-  min-height: auto;
-}
-
-:deep(.ant-card-head-title) {
-  padding: 0;
-}
-
-:deep(.ant-card-body) {
-  display: none;
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
 }
 
-/* 自定义样式 */
-:deep(.ant-table-cell) {
-  padding: 12px 16px;
+/* 卡片内部样式 */
+.inventory-card .card-header {
+  padding: 1rem 1.5rem;
 }
 
-:deep(.ant-btn-primary) {
+/* 按钮样式 */
+:deep(.van-button--primary) {
   background-color: #3b82f6;
   border-color: #3b82f6;
 }
 
-:deep(.ant-btn-primary:hover) {
+:deep(.van-button--primary:active) {
   background-color: #2563eb;
   border-color: #2563eb;
 }
@@ -627,14 +983,21 @@ onMounted(() => {
 .add-to-cart-btn {
   background-color: #10b981 !important;
   border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
 }
 
-.add-to-cart-btn:hover {
+:deep(.add-to-cart-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
+}
+
+:deep(.add-to-cart-btn.van-button--primary:active) {
   background-color: #059669 !important;
   border-color: #059669 !important;
 }
 
-:deep(.ant-btn[disabled]) {
+:deep(.van-button--disabled) {
   opacity: 0.5;
   cursor: not-allowed;
 }
@@ -649,4 +1012,29 @@ onMounted(() => {
   border: none;
   cursor: pointer;
 }
+
+:deep(.van-cell) {
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  padding: 6px 8px !important;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  padding: 6px 8px !important;
+}
+
+/* 全选控制栏 */
+.select-all-bar {
+  background-color: white;
+  padding: 1rem 1.5rem;
+  border-bottom: 1px solid #e5e7eb;
+  display: flex;
+  align-items: center;
+}
+
+.select-all-text {
+  font-size: 0.95rem;
+  color: #374151;
+  font-weight: 500;
+}
 </style>

+ 597 - 135
src/stock/StockPickingCar.vue

@@ -1,7 +1,8 @@
+<!-- 领料车管理 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader :is-go-home="false">
+    <PageHeader :is-go-home="false" title="领料车管理">
       <template #actions>
         <button class="action-btn home-btn" @click="goToHome">
           <i class="fas fa-home mr-1" /> {{ isRegular == 'true' ? '返回常用领料' : '返回领料' }}
@@ -12,145 +13,224 @@
     <!-- 主内容区域 -->
     <main class="main-content">
       <!-- 页面标题 -->
-      <div class="page-title">
+      <!-- <div class="page-title">
         <h2>领料车管理</h2>
-      </div>
+      </div> -->
 
       <!-- 筛选区域 -->
-      <FilterPanel :default-collapsed="true" :active-count="getActiveFilterCount()">
-        <a-form layout="inline" class="filter-form">
-          <a-form-item label="仓库">
-            <a-select
-              v-model:value="warehouseId" :options="warehouseList" placeholder="选择仓库" option-filter-prop="name"
-              show-search allow-clear :field-names="{ label: 'name', value: 'id' }" style="width: 150px"
-              @change="getDatas"
-            />
-          </a-form-item>
-          <a-form-item label="名称">
-            <a-input v-model:value="inventoryName" placeholder="输入名称" style="width: 150px" @keyup.enter="getDatas" />
-          </a-form-item>
-          <a-form-item label="编号">
-            <a-input v-model:value="inventoryNo" placeholder="输入编号" style="width: 150px" @keyup.enter="getDatas" />
-          </a-form-item>
-          <a-form-item label="类型">
-            <a-select
-              v-model:value="inventoryType" placeholder="选择类型" option-filter-prop="label" show-search
-              allow-clear :options="inventoryTypeList" style="width: 150px" @change="getDatas"
-            />
-          </a-form-item>
-          <a-form-item>
-            <a-button type="primary" @click="getDatas">
+      <FilterPanel :enable-collapse="false" :active-count="getActiveFilterCount()">
+        <div class="filter-form">
+          <div class="filter-item">
+            <label class="filter-label">仓库</label>
+            <v-select
+              v-model="warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
+              placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getDatas"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">名称</label>
+            <van-field v-model="inventoryName" placeholder="输入名称" class="filter-input" @keyup.enter="getDatas" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">编号</label>
+            <van-field v-model="inventoryNo" placeholder="输入编号" class="filter-input" @keyup.enter="getDatas" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">类型</label>
+            <v-select
+              v-model="inventoryType" :options="inventoryTypeList" :reduce="item => item.value" label="label"
+              placeholder="选择类型" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getDatas"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item filter-buttons">
+            <van-button type="primary" @click="getDatas">
               <i class="fas fa-search mr-1" /> 搜索
-            </a-button>
-            <a-button class="ml-2" @click="handleReset">
+            </van-button>
+            <van-button class="ml-2" @click="handleReset">
               <i class="fas fa-redo mr-1" /> 重置
-            </a-button>
-          </a-form-item>
-        </a-form>
+            </van-button>
+          </div>
+        </div>
       </FilterPanel>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 全选控制栏 -->
+        <div v-if="carList.length > 0" class="select-all-bar">
+          <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
+            <span class="select-all-text">全选当前页(已选 {{ selectedIds.length }} 项)</span>
+          </van-checkbox>
+        </div>
+
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
-          <a-empty v-if="carList.length === 0" description="领料车为空" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="carList.length === 0" description="领料车为空" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
-              v-for="(item, index) in carList" :key="item.id || index" :hoverable="true"
+            <div
+              v-for="(item, index) in carList" :key="item.id || index"
               :class="{ 'selected-card': selectedIds.includes(item.id) }" class="inventory-card"
               @click="toggleSelect(item.id)"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-checkbox
-                      :checked="selectedIds.includes(item.id)" @click.stop
-                      @change="e => handleCheckboxChange(e, item.id)"
-                    />
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }" class="ml-3">
-                      <template #icon>
-                        <i :class="getInventoryIcon(item.inventoryType)" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ item.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <van-checkbox
+                    :model-value="selectedIds.includes(item.id)" @click.stop
+                    @update:model-value="checked => handleCheckboxChange(checked, item.id)"
+                  />
+                  <div class="custom-avatar ml-3">
+                    <i :class="getInventoryIcon(item.inventoryType)" />
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ item.positionName || '-' }}</span>
-                      <span class="mx-2">/</span>
-                      <span>{{ item.warehouseName || '-' }}</span>
-                    </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
                   </div>
                 </div>
-              </template>
-            </a-card>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.positionName || '-' }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ item.warehouseName || '-' }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
 
         <!-- 分页区域(固定底部) -->
         <div v-if="carList.length > 0" class="pagination-wrapper">
-          <AntdPagination
-            ref="paginationRef" :pagination="pagination" :options="options"
-            @get-page-params="getPageParams"
-          />
+          <div class="pagination-info">
+            第{{ (pagination.current_page - 1) * pagination.per_page + 1 }}-{{ Math.min(pagination.current_page *
+              pagination.per_page, pagination.total) }}条,共{{ pagination.total }}条
+          </div>
+          <div class="pagination-controls">
+            <button
+              class="pagination-btn" :disabled="pagination.current_page === 1"
+              @click="handlePageChange(pagination.current_page - 1)"
+            >
+              &lt;
+            </button>
+            <template v-for="page in getPageNumbers()" :key="page">
+              <button
+                v-if="page !== '...'" class="pagination-btn" :class="{ active: page === pagination.current_page }"
+                @click="handlePageChange(page)"
+              >
+                {{ page }}
+              </button>
+              <span v-else class="pagination-ellipsis">...</span>
+            </template>
+            <button
+              class="pagination-btn"
+              :disabled="pagination.current_page === Math.ceil(pagination.total / pagination.per_page)"
+              @click="handlePageChange(pagination.current_page + 1)"
+            >
+              &gt;
+            </button>
+          </div>
+          <div class="pagination-size">
+            <v-select
+              v-model="pagination.per_page" :options="pageSizeOptions" :clearable="false" :searchable="false"
+              :append-to-body="true" :calculate-position="withPopper" class="size-select"
+              @update:model-value="handlePageSizeChange"
+            >
+              <template #selected-option="{ label }">
+                {{ label }}条/页
+              </template>
+              <template #option="{ label }">
+                {{ label }}条/页
+              </template>
+            </v-select>
+          </div>
         </div>
       </div>
 
       <!-- 底部操作按钮 -->
       <div class="bottom-actions">
-        <a-button danger size="large" :disabled="selectedIds.length === 0" class="mr-3" @click="deleteStock">
+        <van-button
+          type="danger" style="margin-right: 16px;" size="large" :disabled="selectedIds.length === 0"
+          class="mr-3" @click="deleteStock"
+        >
           <i class="fas fa-trash mr-2" />
           删除选中
-        </a-button>
-        <a-button
+        </van-button>
+        <van-button
           type="primary" size="large" :disabled="selectedIds.length === 0" class="submit-btn"
           @click="submitStock"
         >
           <i class="fas fa-check-circle mr-2" />
           提交领料
-        </a-button>
+        </van-button>
       </div>
     </main>
 
     <!-- 拣货确认弹窗 -->
-    <a-modal
-      v-model:open="confirmModalVisible" title="请您确认要进行的操作" :closable="true" :mask-closable="false"
-      @ok="handleConfirmOk" @cancel="confirmModalVisible = false"
+    <van-dialog
+      v-model:show="confirmModalVisible" title="请您确认要进行的操作" show-cancel-button class-name="large-dialog"
+      confirm-button-text="确认" cancel-button-text="取消" @confirm="handleConfirmOk" @cancel="handleConfirmCancel"
     >
-      <template #footer>
-        <a-button @click="handleConfirmCancel">取消</a-button>
-        <a-button type="primary" danger @click="handleConfirmOk">确认</a-button>
-      </template>
-      <p>您是否要进入仓库,并开始拣货。</p>
-    </a-modal>
+      <div class="large-dialog-content">
+        <p>您是否要进入仓库,并开始拣货。</p>
+      </div>
+    </van-dialog>
 
     <!-- 删除确认弹窗 -->
-    <a-modal
-      v-model:open="deleteModalVisible" title="确认将工装设备从领料车中删除吗?" :closable="true" :mask-closable="false"
-      @ok="handleDeleteOk" @cancel="handleDeleteCancel"
+    <van-dialog
+      v-model:show="deleteModalVisible" title="确认将工装设备从领料车中删除吗?" show-cancel-button class-name="large-dialog"
+      confirm-button-text="确认" cancel-button-text="取消" @confirm="handleDeleteOk" @cancel="handleDeleteCancel"
     >
-      <template #footer>
-        <a-button @click="handleDeleteCancel">取消</a-button>
-        <a-button type="primary" danger @click="handleDeleteOk">确认</a-button>
-      </template>
-      <p>确认的话请点击【确认】按钮,否则请点击【取消】按钮</p>
-    </a-modal>
+      <div class="large-dialog-content">
+        <p>确认的话请点击【确认】按钮,否则请点击【取消】按钮</p>
+      </div>
+    </van-dialog>
 
     <Loading v-if="loading" />
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
+import { ref, computed, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import Common from '../common/Common.js';
-import { message, Empty } from 'ant-design-vue';
+import { showNotify } from 'vant';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
+import { createPopper } from '@popperjs/core';
+
+// 为 vue-select 配置 popper
+const withPopper = (dropdownList, component, { width }) => {
+    dropdownList.style.width = width;
+    const popper = createPopper(component.$refs.toggle, dropdownList, {
+        placement: 'bottom',
+        modifiers: [
+            {
+                name: 'offset',
+                options: { offset: [0, -1] },
+            },
+            {
+                name: 'toggleClass',
+                enabled: true,
+                phase: 'write',
+                fn({ state }) {
+                    component.$el.classList.toggle('drop-up', state.placement === 'top');
+                },
+            },
+        ],
+    });
+    return () => popper.destroy();
+};
 import PageHeader from '../common/PageHeader.vue';
 import FilterPanel from '../common/FilterPanel.vue';
 import { getWarehouseList } from '../api/stock.js';
@@ -170,6 +250,12 @@ const inventoryType = ref(undefined);
 const warehouseList = ref([]);
 const carList = ref([]);
 const selectedIds = ref([]);
+
+// 计算是否全选
+const isAllSelected = computed(() => {
+    if (carList.value.length === 0) return false;
+    return carList.value.every(item => selectedIds.value.includes(item.id));
+});
 const loading = ref(false);
 const isRegular = ref(false);
 const inventoryTypeList = ref([
@@ -182,12 +268,7 @@ const pagination = ref({
     current_page: 1,
     per_page: 20,
 });
-const options = {
-    showTotal: true,
-    showSizeChanger: true,
-    showQuickJumper: false,
-    pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
-};
+const pageSizeOptions = [10, 20, 50, 100, 200, 500];
 
 // 计算激活的筛选条件数量
 const getActiveFilterCount = () => {
@@ -210,8 +291,8 @@ const toggleSelect = id => {
 };
 
 // 处理复选框变化
-const handleCheckboxChange = (e, id) => {
-    if (e.target.checked) {
+const handleCheckboxChange = (checked, id) => {
+    if (checked) {
         if (!selectedIds.value.includes(id)) {
             selectedIds.value.push(id);
         }
@@ -223,6 +304,22 @@ const handleCheckboxChange = (e, id) => {
     }
 };
 
+// 全选/取消全选
+const toggleSelectAll = checked => {
+    if (checked) {
+    // 全选当前页所有项(累加模式)
+        carList.value.forEach(item => {
+            if (!selectedIds.value.includes(item.id)) {
+                selectedIds.value.push(item.id);
+            }
+        });
+    } else {
+    // 取消全选当前页
+        const currentPageIds = carList.value.map(item => item.id);
+        selectedIds.value = selectedIds.value.filter(id => !currentPageIds.includes(id));
+    }
+};
+
 // 获取设备类型图标
 const getInventoryIcon = type => {
     const iconMap = {
@@ -251,17 +348,54 @@ const handleReset = () => {
     getDatas();
 };
 
-// 获取分页参数
-const getPageParams = (page, pageSize) => {
+// 分页改变
+const handlePageChange = page => {
     pagination.value.current_page = page;
+    getPickerCarList();
+};
+
+// 每页大小改变
+const handlePageSizeChange = pageSize => {
     pagination.value.per_page = pageSize;
+    pagination.value.current_page = 1;
     getPickerCarList();
 };
 
+// 获取分页页码数组
+const getPageNumbers = () => {
+    const totalPages = Math.ceil(pagination.value.total / pagination.value.per_page);
+    const current = pagination.value.current_page;
+    const pages = [];
+
+    if (totalPages <= 7) {
+        for (let i = 1; i <= totalPages; i++) {
+            pages.push(i);
+        }
+    } else {
+        if (current <= 3) {
+            for (let i = 1; i <= 5; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        } else if (current >= totalPages - 2) {
+            pages.push(1);
+            pages.push('...');
+            for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
+        } else {
+            pages.push(1);
+            pages.push('...');
+            for (let i = current - 1; i <= current + 1; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        }
+    }
+
+    return pages;
+};
+
 // 从领料车里删除工装设备
 const deleteStock = () => {
     if (selectedIds.value.length == 0) {
-        message.warning('请至少选择一个工装设备');
+        showNotify({ type: 'warning', message: '请至少选择一个工装设备' });
         return;
     }
     // 显示删除确认弹窗
@@ -297,11 +431,11 @@ const getPickerCarList = async () => {
                 pagination.value.total = 0;
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.log('获取领料车列表API调用失败', error);
-        message.error('获取领料车列表API调用失败');
+        showNotify({ type: 'danger', message: '获取领料车列表API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -316,15 +450,15 @@ const deleteStockCarApi = async () => {
     try {
         const res = await deleteCFPickCar(params);
         if (res.errorCode == 0) {
-            message.success('工装设备从领料车中删除成功');
+            showNotify({ type: 'success', message: '删除成功' });
             selectedIds.value = [];
             getDatas();
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.log('删除领料车API调用失败', error);
-        message.error('删除领料车API调用失败');
+        showNotify({ type: 'danger', message: '删除领料车API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -340,11 +474,11 @@ const getWarehouses = async () => {
                 warehouseList.value = [];
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
     } catch (error) {
         console.error('获取仓库数据失败:', error);
-        message.error('获取仓库数据失败');
+        showNotify({ type: 'danger', message: '获取仓库数据失败' });
     }
 };
 
@@ -378,7 +512,7 @@ const getDatas = () => {
 // 领料
 const submitStock = async () => {
     if (selectedIds.value.length == 0) {
-        message.warning('请至少选择一个工装设备');
+        showNotify({ type: 'warning', message: '请至少选择一个工装设备' });
         return;
     }
     loading.value = true;
@@ -391,11 +525,11 @@ const submitStock = async () => {
         if (res.errorCode == 0) {
             confirmModalVisible.value = true;
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
     } catch (error) {
         console.log('生成领料单API调用失败', error);
-        message.error('生成领料单API调用失败');
+        showNotify({ type: 'danger', message: '生成领料单API调用失败' });
     } finally {
         loading.value = false;
     }
@@ -421,7 +555,7 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
@@ -462,25 +596,116 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+  font-weight: 600;
+}
+
+.card-list > * {
+  margin-bottom: 1rem;
+}
+
+.card-list > *:last-child {
+  margin-bottom: 0;
 }
 
 /* 分页包装器(固定底部) */
 .pagination-wrapper {
   padding: 0.5rem 1.5rem;
   border-top: 1px solid #e5e7eb;
-  border-bottom: 1px solid #e5e7eb;
   background-color: #fafafa;
   display: flex;
   justify-content: flex-end;
   align-items: center;
 }
 
+.pagination-wrapper > * {
+  margin-left: 1rem;
+}
+
+.pagination-wrapper > *:first-child {
+  margin-left: 0;
+}
+
+.pagination-info {
+  font-size: 14px;
+  color: #6b7280;
+  white-space: nowrap;
+}
+
+.pagination-controls {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-controls > * {
+  margin-right: 0.25rem;
+}
+
+.pagination-controls > *:last-child {
+  margin-right: 0;
+}
+
+.pagination-btn {
+  min-width: 32px;
+  height: 32px;
+  padding: 0 8px;
+  border: 1px solid #d1d5db;
+  background: white;
+  color: #374151;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.2s;
+}
+
+.pagination-btn:hover:not(:disabled) {
+  border-color: #3b82f6;
+  color: #3b82f6;
+}
+
+.pagination-btn.active {
+  background: #3b82f6;
+  border-color: #3b82f6;
+  color: white;
+}
+
+.pagination-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.pagination-ellipsis {
+  padding: 0 4px;
+  color: #6b7280;
+}
+
+.pagination-size {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-size > * {
+  margin-right: 0.5rem;
+}
+
+.pagination-size > *:last-child {
+  margin-right: 0;
+}
+
+.size-select {
+  width: 150px;
+  position: relative;
+  z-index: 50;
+}
+
+:deep(.size-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+}
+
 /* 底部操作按钮 */
 .bottom-actions {
   position: sticky;
   bottom: 0;
-  padding: 1rem 0;
+  padding: 1rem 0 0;
   background-color: #f9fafb;
   display: flex;
   justify-content: flex-end;
@@ -510,16 +735,108 @@ onMounted(() => {
 .filter-form {
   display: flex;
   flex-wrap: wrap;
-  gap: 0.5rem;
+  align-items: center;
 }
 
-:deep(.ant-form-item) {
-  margin-bottom: 0 !important;
+.filter-form > * {
+  margin-right: 1rem;
+  margin-bottom: 0.4rem;
 }
 
-:deep(.ant-form-item-label > label) {
-  font-size: 14px !important;
-  font-weight: 600 !important;
+.filter-item {
+  display: flex;
+  align-items: center;
+}
+
+.filter-item > * {
+  margin-right: 0.5rem;
+}
+
+.filter-item > *:last-child {
+  margin-right: 0;
+}
+
+.filter-label {
+  font-size: 14px;
+  font-weight: 600;
+  white-space: nowrap;
+  color: #374151;
+}
+
+.filter-select {
+  width: 200px;
+  position: relative;
+  z-index: 100;
+}
+
+.filter-input {
+  width: 200px;
+}
+
+:deep(.filter-input .van-cell) {
+  padding: 8px 12px !important;
+  border: 1px solid #d1d5db !important;
+  border-radius: 4px !important;
+  background-color: white !important;
+  min-height: 32px !important;
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.filter-input .van-cell::after) {
+  display: none !important;
+}
+
+:deep(.filter-input .van-field__body) {
+  border: none !important;
+}
+
+:deep(.filter-input .van-field__control) {
+  font-size: 14px;
+}
+
+/* Vue Select 样式 */
+:deep(.v-select) {
+  font-size: 14px;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  border: 1px solid #d1d5db;
+  border-radius: 4px;
+  padding: 4px 8px;
+  min-height: 32px;
+  background: white;
+}
+
+:deep(.v-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+  border: 1px solid #d1d5db;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.v-select .vs__selected) {
+  margin: 2px;
+  padding: 0 4px;
+}
+
+:deep(.v-select .vs__search) {
+  padding: 0;
+  margin: 0;
+}
+
+:deep(.v-select .vs__actions) {
+  padding: 0 4px;
+}
+
+.filter-buttons {
+  display: flex;
+}
+
+.filter-buttons > * {
+  margin-right: 0.5rem;
+}
+
+.filter-buttons > *:last-child {
+  margin-right: 0;
 }
 
 /* 库存卡片样式 */
@@ -548,7 +865,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header > * {
+  margin-right: 1.5rem;
+}
+
+.card-header > *:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -561,19 +885,33 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right > * {
+  margin-left: 1rem;
+}
+
+.card-header-right > *:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info > * {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info > *:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
-  font-size: 1.125rem;
+  font-size: 1.2rem;
   font-weight: 600;
   color: #111827;
   line-height: 1.5;
@@ -604,28 +942,110 @@ onMounted(() => {
   color: #3b82f6;
 }
 
-/* 卡片标题样式 */
-:deep(.ant-card-head) {
-  border-bottom: none;
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
+}
+
+/* 卡片内部样式 */
+.inventory-card .card-header {
   padding: 1rem 1.5rem;
-  min-height: auto;
 }
 
-:deep(.ant-card-head-title) {
-  padding: 0;
+/* 弹窗内容 */
+.dialog-content {
+  padding: 1rem;
+}
+
+.dialog-content p {
+  margin: 0;
+  line-height: 1.6;
+  color: #374151;
+}
+
+/* 大弹窗样式,适合触摸屏 */
+:deep(.large-dialog) {
+  width: 85vw !important;
+  max-width: 600px !important;
+  border-radius: 16px !important;
+}
+
+:deep(.large-dialog .van-dialog__header) {
+  padding: 2rem 1.5rem 1rem !important;
+  font-size: 1.5rem !important;
+  font-weight: 700 !important;
+  color: #111827 !important;
+}
+
+.large-dialog-content {
+  padding: 1.5rem 2rem 2.5rem !important;
+}
+
+.large-dialog-content p {
+  margin: 0;
+  font-size: 1.25rem !important;
+  line-height: 1.8 !important;
+  color: #374151 !important;
+  text-align: center;
+}
+
+:deep(.large-dialog .van-dialog__footer) {
+  padding: 1rem 1.5rem 1.5rem !important;
+  display: flex;
+}
+
+:deep(.large-dialog .van-dialog__footer > *) {
+  margin-right: 1rem;
+}
+
+:deep(.large-dialog .van-dialog__footer > *:last-child) {
+  margin-right: 0;
+}
+
+:deep(.large-dialog .van-dialog__cancel),
+:deep(.large-dialog .van-dialog__confirm) {
+  flex: 1;
+  height: 56px !important;
+  font-size: 1.125rem !important;
+  font-weight: 600 !important;
+  border-radius: 8px !important;
+  border: none !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel) {
+  background-color: #f3f4f6 !important;
+  color: #374151 !important;
+}
+
+:deep(.large-dialog .van-dialog__cancel:active) {
+  background-color: #e5e7eb !important;
 }
 
-:deep(.ant-card-body) {
-  display: none;
+:deep(.large-dialog .van-dialog__confirm) {
+  background-color: #3b82f6 !important;
+  color: white !important;
+}
+
+:deep(.large-dialog .van-dialog__confirm:active) {
+  background-color: #2563eb !important;
 }
 
 /* 按钮样式 */
-:deep(.ant-btn-primary) {
+:deep(.van-button--primary) {
   background-color: #3b82f6;
   border-color: #3b82f6;
 }
 
-:deep(.ant-btn-primary:hover) {
+:deep(.van-button--primary:active) {
   background-color: #2563eb;
   border-color: #2563eb;
 }
@@ -633,15 +1053,57 @@ onMounted(() => {
 .submit-btn {
   background-color: #10b981 !important;
   border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
+}
+
+:deep(.submit-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
 }
 
-.submit-btn:hover {
+:deep(.submit-btn.van-button--primary:active) {
   background-color: #059669 !important;
   border-color: #059669 !important;
 }
 
-:deep(.ant-btn[disabled]) {
+:deep(.van-button--disabled) {
   opacity: 0.5;
   cursor: not-allowed;
 }
+
+:deep(.van-button--danger) {
+  width: auto;
+  padding: 0 24px;
+}
+
+:deep(.van-cell) {
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  padding: 6px 8px !important;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  padding: 6px 8px !important;
+}
+
+/* 全选控制栏 */
+.select-all-bar {
+  background-color: white;
+  padding: 1rem 1.5rem;
+  border-bottom: 1px solid #e5e7eb;
+  display: flex;
+  align-items: center;
+}
+
+.select-all-text {
+  font-size: 0.95rem;
+  color: #374151;
+  font-weight: 500;
+}
+
+/* 大弹窗蒙层 */
+:deep(.van-overlay) {
+  background-color: rgba(0, 0, 0, 0.6) !important;
+}
 </style>

+ 493 - 119
src/stock/StockRequisition.vue

@@ -1,16 +1,17 @@
+<!-- 领料管理 -->
 <template>
   <div class="page-container">
     <!-- 顶部信息栏 -->
-    <PageHeader>
+    <PageHeader title="领料管理">
       <template #actions>
         <button class="action-btn regular-btn" @click="openRegularRequisition">
           <i class="fas fa-star text-xl" />
           <span class="ml-2">常用领料</span>
         </button>
         <button class="action-btn cart-btn" @click="openStockOutCar">
-          <a-badge :count="count" show-zero :number-style="{ backgroundColor: '#10b981' }">
+          <van-badge :content="count" :show-zero="true">
             <i class="fas fa-shopping-cart text-xl" />
-          </a-badge>
+          </van-badge>
           <span class="ml-2">领料车</span>
         </button>
       </template>
@@ -19,107 +20,161 @@
     <!-- 主内容区域 -->
     <main class="main-content">
       <!-- 页面标题 -->
-      <div class="page-title">
+      <!-- <div class="page-title">
         <h2>领料管理</h2>
-      </div>
+      </div> -->
 
       <!-- 筛选区域 -->
-      <FilterPanel :default-collapsed="false" :active-count="getActiveFilterCount()">
-        <a-form layout="inline" class="filter-form">
-          <a-form-item label="仓库">
-            <a-select
-              v-model:value="warehouseId" :options="warehouseList" placeholder="选择仓库" option-filter-prop="name"
-              show-search allow-clear :field-names="{ label: 'name', value: 'id' }" style="width: 150px"
-              @change="getDatas"
-            />
-          </a-form-item>
-          <a-form-item label="名称">
-            <a-input v-model:value="inventoryName" placeholder="输入名称" style="width: 150px" @keyup.enter="getDatas" />
-          </a-form-item>
-          <a-form-item label="编号">
-            <a-input v-model:value="inventoryNo" placeholder="输入编号" style="width: 150px" @keyup.enter="getDatas" />
-          </a-form-item>
-          <a-form-item label="类型">
-            <a-select
-              v-model:value="inventoryType" placeholder="选择类型" option-filter-prop="label" show-search
-              allow-clear :options="inventoryTypeList" style="width: 150px" @change="getDatas"
-            />
-          </a-form-item>
-          <a-form-item>
-            <a-button type="primary" @click="getDatas">
+      <FilterPanel :default-collapsed="false" :enable-collapse="false" :active-count="getActiveFilterCount()">
+        <div class="filter-form">
+          <div class="filter-item">
+            <label class="filter-label">仓库:</label>
+            <v-select
+              v-model="warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
+              placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getDatas"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">名称:</label>
+            <van-field v-model="inventoryName" placeholder="输入名称" class="filter-input" @keyup.enter="getDatas" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">编号:</label>
+            <van-field v-model="inventoryNo" placeholder="输入编号" class="filter-input" @keyup.enter="getDatas" />
+          </div>
+          <div class="filter-item">
+            <label class="filter-label">类型:</label>
+            <v-select
+              v-model="inventoryType" :options="inventoryTypeList" :reduce="item => item.value" label="label"
+              placeholder="选择类型" :clearable="true" :filterable="true" class="filter-select"
+              @update:model-value="getDatas"
+            >
+              <template #no-options>
+                <span>无该选项数据</span>
+              </template>
+            </v-select>
+          </div>
+          <div class="filter-item filter-buttons">
+            <van-button type="primary" @click="getDatas">
               <i class="fas fa-search mr-1" /> 搜索
-            </a-button>
-            <a-button class="ml-2" @click="handleReset">
+            </van-button>
+            <van-button class="ml-2" @click="handleReset">
               <i class="fas fa-redo mr-1" /> 重置
-            </a-button>
-          </a-form-item>
-        </a-form>
+            </van-button>
+          </div>
+        </div>
       </FilterPanel>
 
       <!-- 卡片容器 -->
       <div class="card-container">
-        <!-- 卡片列表区域(可滚动) -->
+        <!-- 全选控制栏 -->
+        <div v-if="stockRequisitionList.length > 0" class="select-all-bar">
+          <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
+            <span class="select-all-text">全选当前页(已选 {{ selectedIds.length }} 项)</span>
+          </van-checkbox>
+        </div>
+
+        <!-- 卡片列表区域  -->
         <div class="card-list-wrapper">
           <!-- 空状态 -->
-          <a-empty v-if="stockRequisitionList.length === 0" description="暂无数据" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
+          <van-empty v-if="stockRequisitionList.length === 0" description="暂无数据" />
 
           <!-- 卡片列表 -->
           <div v-else class="card-list">
-            <a-card
-              v-for="(item, index) in stockRequisitionList" :key="item.id || index" :hoverable="true"
+            <div
+              v-for="(item, index) in stockRequisitionList" :key="item.id || index"
               :class="{ 'selected-card': selectedIds.includes(item.id) }" class="inventory-card"
               @click="toggleSelect(item.id)"
             >
-              <template #title>
-                <div class="card-header">
-                  <div class="card-header-left">
-                    <a-checkbox
-                      :checked="selectedIds.includes(item.id)" @click.stop
-                      @change="e => handleCheckboxChange(e, item.id)"
-                    />
-                    <a-avatar :size="42" :style="{ backgroundColor: '#3b82f6' }" class="ml-3">
-                      <template #icon>
-                        <i :class="getInventoryIcon(item.inventoryType)" style="font-size: 20px;" />
-                      </template>
-                    </a-avatar>
-                    <div class="ml-4 card-title-info">
-                      <div class="card-name">{{ item.inventoryName }}</div>
-                      <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
-                    </div>
+              <div class="card-header">
+                <div class="card-header-left">
+                  <van-checkbox
+                    :model-value="selectedIds.includes(item.id)" @click.stop
+                    @update:model-value="checked => handleCheckboxChange(checked, item.id)"
+                  />
+                  <div class="custom-avatar ml-3">
+                    <i :class="getInventoryIcon(item.inventoryType)" />
                   </div>
-                  <div class="card-header-right">
-                    <div class="card-location">
-                      <i class="fas fa-map-marker-alt mr-2" />
-                      <span>{{ item.inventoryActulPosition || item.inventoryPosition || '-' }}</span>
-                      <span class="mx-2">/</span>
-                      <span>{{ item.inventoryWarehouse || '-' }}</span>
-                    </div>
+                  <div class="ml-4 card-title-info">
+                    <div class="card-name">{{ item.inventoryName }}</div>
+                    <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
                   </div>
                 </div>
-              </template>
-            </a-card>
+                <div class="card-header-right">
+                  <div class="card-location">
+                    <i class="fas fa-map-marker-alt mr-2" />
+                    <span>{{ item.inventoryActulPosition || item.inventoryPosition || '-' }}</span>
+                    <span class="mx-2">/</span>
+                    <span>{{ item.inventoryWarehouse || '-' }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
 
         <!-- 分页区域(固定底部) -->
         <div v-if="stockRequisitionList.length > 0" class="pagination-wrapper">
-          <AntdPagination
-            ref="paginationRef" :pagination="pagination" :options="options"
-            @get-page-params="getPageParams"
-          />
+          <div class="pagination-info">
+            第{{ (pagination.current_page - 1) * pagination.per_page + 1 }}-{{ Math.min(pagination.current_page *
+              pagination.per_page, pagination.total) }}条,共{{ pagination.total }}条
+          </div>
+          <div class="pagination-controls">
+            <button
+              class="pagination-btn" :disabled="pagination.current_page === 1"
+              @click="handlePageChange(pagination.current_page - 1)"
+            >
+              &lt;
+            </button>
+            <template v-for="page in getPageNumbers()" :key="page">
+              <button
+                v-if="page !== '...'" class="pagination-btn" :class="{ active: page === pagination.current_page }"
+                @click="handlePageChange(page)"
+              >
+                {{ page }}
+              </button>
+              <span v-else class="pagination-ellipsis">...</span>
+            </template>
+            <button
+              class="pagination-btn"
+              :disabled="pagination.current_page === Math.ceil(pagination.total / pagination.per_page)"
+              @click="handlePageChange(pagination.current_page + 1)"
+            >
+              &gt;
+            </button>
+          </div>
+          <div class="pagination-size">
+            <v-select
+              v-model="pagination.per_page" :options="pageSizeOptions" :clearable="false" :searchable="false"
+              :append-to-body="true" :calculate-position="withPopper" class="size-select"
+              @update:model-value="handlePageSizeChange"
+            >
+              <template #selected-option="{ label }">
+                {{ label }}条/页
+              </template>
+              <template #option="{ label }">
+                {{ label }}条/页
+              </template>
+            </v-select>
+          </div>
         </div>
       </div>
     </main>
 
     <!-- 底部操作按钮 -->
     <div class="bottom-actions">
-      <a-button
+      <van-button
         type="primary" size="large" :disabled="selectedIds.length === 0" class="add-to-cart-btn"
         @click="submitStock"
       >
         <i class="fas fa-cart-plus mr-2" />
         加入领料车
-      </a-button>
+      </van-button>
     </div>
 
     <Loading v-if="loading" />
@@ -127,10 +182,36 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue';
+import { ref, computed, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import Common from '../common/Common.js';
-import { message, Empty } from 'ant-design-vue';
+import { showNotify } from 'vant';
+import vSelect from 'vue-select';
+import 'vue-select/dist/vue-select.css';
+import { createPopper } from '@popperjs/core';
+
+// 为 vue-select 配置 popper
+const withPopper = (dropdownList, component, { width }) => {
+    dropdownList.style.width = width;
+    const popper = createPopper(component.$refs.toggle, dropdownList, {
+        placement: 'bottom',
+        modifiers: [
+            {
+                name: 'offset',
+                options: { offset: [0, -1] },
+            },
+            {
+                name: 'toggleClass',
+                enabled: true,
+                phase: 'write',
+                fn({ state }) {
+                    component.$el.classList.toggle('drop-up', state.placement === 'top');
+                },
+            },
+        ],
+    });
+    return () => popper.destroy();
+};
 import PageHeader from '../common/PageHeader.vue';
 import FilterPanel from '../common/FilterPanel.vue';
 import { getWarehouseList } from '../api/stock.js';
@@ -146,6 +227,12 @@ const count = ref(0);
 const warehouseList = ref([]);
 const stockRequisitionList = ref([]);
 const selectedIds = ref([]);
+
+// 计算是否全选
+const isAllSelected = computed(() => {
+    if (stockRequisitionList.value.length === 0) return false;
+    return stockRequisitionList.value.every(item => selectedIds.value.includes(item.id));
+});
 const loading = ref(false);
 
 const inventoryTypeList = ref([
@@ -158,12 +245,7 @@ const pagination = ref({
     current_page: 1,
     per_page: 20,
 });
-const options = {
-    showTotal: true,
-    showSizeChanger: true,
-    showQuickJumper: false,
-    pageSizeOptions: ['2', '4', '50', '100', '200', '500'],
-};
+const pageSizeOptions = [10, 20, 50, 100, 200, 500];
 
 // 计算激活的筛选条件数量
 const getActiveFilterCount = () => {
@@ -197,8 +279,8 @@ const toggleSelect = id => {
 };
 
 // 处理复选框变化
-const handleCheckboxChange = (e, id) => {
-    if (e.target.checked) {
+const handleCheckboxChange = (checked, id) => {
+    if (checked) {
         if (!selectedIds.value.includes(id)) {
             selectedIds.value.push(id);
         }
@@ -210,6 +292,22 @@ const handleCheckboxChange = (e, id) => {
     }
 };
 
+// 全选/取消全选
+const toggleSelectAll = checked => {
+    if (checked) {
+    // 全选当前页所有项(累加模式)
+        stockRequisitionList.value.forEach(item => {
+            if (!selectedIds.value.includes(item.id)) {
+                selectedIds.value.push(item.id);
+            }
+        });
+    } else {
+    // 取消全选当前页
+        const currentPageIds = stockRequisitionList.value.map(item => item.id);
+        selectedIds.value = selectedIds.value.filter(id => !currentPageIds.includes(id));
+    }
+};
+
 // 获取设备类型图标
 const getInventoryIcon = type => {
     const iconMap = {
@@ -221,12 +319,49 @@ const getInventoryIcon = type => {
 };
 
 // 分页改变
-const getPageParams = (page, pageSize) => {
+const handlePageChange = page => {
     pagination.value.current_page = page;
+    getStockRequisitionList();
+};
+
+// 每页大小改变
+const handlePageSizeChange = pageSize => {
     pagination.value.per_page = pageSize;
+    pagination.value.current_page = 1;
     getStockRequisitionList();
 };
 
+// 获取分页页码数组
+const getPageNumbers = () => {
+    const totalPages = Math.ceil(pagination.value.total / pagination.value.per_page);
+    const current = pagination.value.current_page;
+    const pages = [];
+
+    if (totalPages <= 7) {
+        for (let i = 1; i <= totalPages; i++) {
+            pages.push(i);
+        }
+    } else {
+        if (current <= 3) {
+            for (let i = 1; i <= 5; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        } else if (current >= totalPages - 2) {
+            pages.push(1);
+            pages.push('...');
+            for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
+        } else {
+            pages.push(1);
+            pages.push('...');
+            for (let i = current - 1; i <= current + 1; i++) pages.push(i);
+            pages.push('...');
+            pages.push(totalPages);
+        }
+    }
+
+    return pages;
+};
+
 // 查询物料数据
 const getStockRequisitionList = async () => {
     loading.value = true;
@@ -255,7 +390,7 @@ const getStockRequisitionList = async () => {
 
     } catch (error) {
         console.error('获取物料数据失败:', error);
-        message.error('获取物料数据失败');
+        showNotify({ type: 'danger', message: '获取库存数据失败' });
     } finally {
         loading.value = false;
     }
@@ -272,12 +407,12 @@ const getWarehouses = async () => {
                 warehouseList.value = [];
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'danger', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('获取仓库数据失败:', error);
-        message.error('获取仓库数据失败');
+        showNotify({ type: 'danger', message: '获取仓库数据失败' });
     }
 };
 
@@ -305,7 +440,7 @@ const getDatas = () => {
 // 加入领料车
 const submitStock = async () => {
     if (selectedIds.value.length == 0) {
-        message.warning('请至少选择一个工装设备');
+        showNotify({ type: 'warning', message: '请至少选择一个物料' });
         return;
     }
     loading.value = true;
@@ -319,11 +454,11 @@ const submitStock = async () => {
             selectedIds.value = [];
             getDatas();
             queryPickingCarCount();
-            message.success('添加领料车成功');
+            showNotify({ type: 'success', message: '已添加到领料车' });
         }
     } catch (error) {
         console.error('添加领料车失败:', error);
-        message.error('添加领料车失败');
+        showNotify({ type: 'danger', message: '添加到领料车失败' });
     } finally {
         loading.value = false;
     }
@@ -342,12 +477,12 @@ const queryPickingCarCount = async () => {
                 count.value = 0;
             }
         } else {
-            message.warning(res.errorMessage);
+            showNotify({ type: 'warning', message: res.errorMessage });
         }
 
     } catch (error) {
         console.error('查询领料车数量失败:', error);
-        message.error('查询领料车数量失败');
+        showNotify({ type: 'danger', message: '查询领料车数量失败' });
     }
 };
 onMounted(() => {
@@ -371,7 +506,7 @@ onMounted(() => {
 .main-content {
   flex: 1;
   overflow-y: auto;
-  padding: 1.5rem;
+  padding: 1rem;
   display: flex;
   flex-direction: column;
 }
@@ -412,7 +547,15 @@ onMounted(() => {
 .card-list {
   display: flex;
   flex-direction: column;
-  gap: 1rem;
+  font-weight: 600;
+}
+
+.card-list > * {
+  margin-bottom: 1rem;
+}
+
+.card-list > *:last-child {
+  margin-bottom: 0;
 }
 
 /* 分页包装器(固定底部) */
@@ -423,6 +566,92 @@ onMounted(() => {
   display: flex;
   justify-content: flex-end;
   align-items: center;
+  position: relative;
+  z-index: 100;
+}
+
+.pagination-wrapper > * {
+  margin-left: 1rem;
+}
+
+.pagination-wrapper > *:first-child {
+  margin-left: 0;
+}
+
+.pagination-info {
+  font-size: 14px;
+  color: #6b7280;
+  white-space: nowrap;
+}
+
+.pagination-controls {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-controls > * {
+  margin-right: 0.25rem;
+}
+
+.pagination-controls > *:last-child {
+  margin-right: 0;
+}
+
+.pagination-btn {
+  min-width: 32px;
+  height: 32px;
+  padding: 0 8px;
+  border: 1px solid #d1d5db;
+  background: white;
+  color: #374151;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.2s;
+}
+
+.pagination-btn:hover:not(:disabled) {
+  border-color: #3b82f6;
+  color: #3b82f6;
+}
+
+.pagination-btn.active {
+  background: #3b82f6;
+  border-color: #3b82f6;
+  color: white;
+}
+
+.pagination-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.pagination-ellipsis {
+  padding: 0 4px;
+  color: #6b7280;
+}
+
+.pagination-size {
+  display: flex;
+  align-items: center;
+}
+
+.pagination-size > * {
+  margin-right: 0.5rem;
+}
+
+.pagination-size > *:last-child {
+  margin-right: 0;
+}
+
+.size-select {
+  width: 120px;
+  position: relative;
+  z-index: 50;
+}
+
+:deep(.size-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
 }
 
 /* 底部操作按钮 */
@@ -466,16 +695,108 @@ onMounted(() => {
 .filter-form {
   display: flex;
   flex-wrap: wrap;
-  gap: 0.5rem;
+  align-items: center;
+}
+
+.filter-form > * {
+  margin-right: 1rem;
+  margin-bottom: 0.4rem;
+}
+
+.filter-item {
+  display: flex;
+  align-items: center;
+}
+
+.filter-item > * {
+  margin-right: 0.5rem;
+}
+
+.filter-item > *:last-child {
+  margin-right: 0;
+}
+
+.filter-label {
+  font-size: 14px;
+  font-weight: 600;
+  white-space: nowrap;
+  color: #374151;
 }
 
-:deep(.ant-form-item) {
-  margin-bottom: 0 !important;
+.filter-select {
+  width: 200px;
+  position: relative;
+  z-index: 100;
+}
+
+.filter-input {
+  width: 200px;
+}
+
+:deep(.filter-input .van-cell) {
+  padding: 8px 12px !important;
+  border: 1px solid #ccc !important;
+  border-radius: 4px !important;
+  background-color: white !important;
+  min-height: 32px !important;
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+:deep(.filter-input .van-cell::after) {
+  display: none !important;
+}
+
+:deep(.filter-input .van-field__body) {
+  border: none !important;
+}
+
+:deep(.filter-input .van-field__control) {
+  font-size: 14px;
+}
+
+/* Vue Select 样式 */
+:deep(.v-select) {
+  font-size: 14px;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  border: 1px solid #d1d5db;
+  border-radius: 4px;
+  padding: 4px 8px;
+  min-height: 32px;
+  background: white;
+}
+
+:deep(.v-select .vs__dropdown-menu) {
+  z-index: 9999 !important;
+  border: 1px solid #d1d5db;
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.v-select .vs__selected) {
+  margin: 2px;
+  padding: 0 4px;
+}
+
+:deep(.v-select .vs__search) {
+  padding: 0;
+  margin: 0;
 }
 
-:deep(.ant-form-item-label > label) {
-  font-size: 14px !important;
-  font-weight: 600 !important;
+:deep(.v-select .vs__actions) {
+  padding: 0 4px;
+}
+
+.filter-buttons {
+  display: flex;
+}
+
+.filter-buttons > * {
+  margin-right: 0.5rem;
+}
+
+.filter-buttons > *:last-child {
+  margin-right: 0;
 }
 
 /* 库存卡片样式 */
@@ -504,7 +825,14 @@ onMounted(() => {
   align-items: center;
   justify-content: space-between;
   width: 100%;
-  gap: 1.5rem;
+}
+
+.card-header > * {
+  margin-right: 1.5rem;
+}
+
+.card-header > *:last-child {
+  margin-right: 0;
 }
 
 .card-header-left {
@@ -517,19 +845,33 @@ onMounted(() => {
 .card-header-right {
   display: flex;
   align-items: center;
-  gap: 1rem;
+}
+
+.card-header-right > * {
+  margin-left: 1rem;
+}
+
+.card-header-right > *:first-child {
+  margin-left: 0;
 }
 
 /* 卡片标题信息 */
 .card-title-info {
   display: flex;
   flex-direction: column;
-  gap: 0.25rem;
   min-width: 0;
 }
 
+.card-title-info > * {
+  margin-bottom: 0.25rem;
+}
+
+.card-title-info > *:last-child {
+  margin-bottom: 0;
+}
+
 .card-name {
-  font-size: 1.125rem;
+  font-size: 1.2rem;
   font-weight: 600;
   color: #111827;
   line-height: 1.5;
@@ -560,32 +902,32 @@ onMounted(() => {
   color: #3b82f6;
 }
 
-/* 卡片标题样式 */
-:deep(.ant-card-head) {
-  border-bottom: none;
-  padding: 1rem 1.5rem;
-  min-height: auto;
-}
-
-:deep(.ant-card-head-title) {
-  padding: 0;
-}
-
-:deep(.ant-card-body) {
-  display: none;
+/* 自定义头像 */
+.custom-avatar {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  background-color: #3b82f6;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 20px;
+  flex-shrink: 0;
 }
 
-/* 自定义样式 */
-:deep(.ant-table-cell) {
-  padding: 12px 16px;
+/* 卡片内部样式 */
+.inventory-card .card-header {
+  padding: 1rem 1.5rem;
 }
 
-:deep(.ant-btn-primary) {
+/* 按钮样式 */
+:deep(.van-button--primary) {
   background-color: #3b82f6;
   border-color: #3b82f6;
 }
 
-:deep(.ant-btn-primary:hover) {
+:deep(.van-button--primary:active) {
   background-color: #2563eb;
   border-color: #2563eb;
 }
@@ -593,14 +935,21 @@ onMounted(() => {
 .add-to-cart-btn {
   background-color: #10b981 !important;
   border-color: #10b981 !important;
+  width: auto;
+  padding: 0 24px;
+}
+
+:deep(.add-to-cart-btn.van-button--primary) {
+  background-color: #10b981 !important;
+  border-color: #10b981 !important;
 }
 
-.add-to-cart-btn:hover {
+:deep(.add-to-cart-btn.van-button--primary:active) {
   background-color: #059669 !important;
   border-color: #059669 !important;
 }
 
-:deep(.ant-btn[disabled]) {
+:deep(.van-button--disabled) {
   opacity: 0.5;
   cursor: not-allowed;
 }
@@ -615,4 +964,29 @@ onMounted(() => {
   border: none;
   cursor: pointer;
 }
+
+:deep(.van-cell) {
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  padding: 6px 8px !important;
+}
+
+:deep(.v-select .vs__dropdown-toggle) {
+  padding: 6px 8px !important;
+}
+
+/* 全选控制栏 */
+.select-all-bar {
+  background-color: white;
+  padding: 1rem 1.5rem;
+  border-bottom: 1px solid #e5e7eb;
+  display: flex;
+  align-items: center;
+}
+
+.select-all-text {
+  font-size: 0.95rem;
+  color: #374151;
+  font-weight: 500;
+}
 </style>

+ 19 - 18
src/util/common.js

@@ -1,4 +1,4 @@
-import { notification } from 'ant-design-vue';
+import { showNotify } from 'vant';
 
 /**
  * 请求错误
@@ -6,10 +6,11 @@ import { notification } from 'ant-design-vue';
  */
 export function requestFailed(err) {
     console.error(err);
-    notification['error']({
-        message: '错误',
-        description: ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试',
-        duration: 8,
+    const errorMessage = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试';
+    showNotify({
+        type: 'danger',
+        message: `错误: ${errorMessage}`,
+        duration: 3000,
     });
 }
 
@@ -19,10 +20,10 @@ export function requestFailed(err) {
  */
 export function requestSuccess(response) {
     if (response.errorCode !== 0) {
-        notification['error']({
-            message: '错误',
-            description: response.errorMessage,
-            duration: 8,
+        showNotify({
+            type: 'danger',
+            message: `错误: ${response.errorMessage}`,
+            duration: 3000,
         });
     }
 }
@@ -32,22 +33,22 @@ export function requestSuccess(response) {
  * @param {} response 
  */
 export function notificationError(message, title) {
-    notification['error']({
-        message: title || '操作失败',
-        description: message,
-        duration: 8,
+    showNotify({
+        type: 'danger',
+        message: title ? `${title}: ${message}` : message,
+        duration: 3000,
     });
 }
 
 
 /**
- * 错误提示
+ * 成功提示
  * @param {} response 
  */
 export function notificationSuccess(message, title) {
-    notification['success']({
-        message: title || '操作成功',
-        description: message,
-        duration: 8,
+    showNotify({
+        type: 'success',
+        message: title ? `${title}: ${message}` : message,
+        duration: 3000,
     });
 }

+ 19 - 13
src/util/request.js

@@ -1,5 +1,5 @@
 import axios from 'axios';
-import { notification } from 'ant-design-vue';
+import { showNotify } from 'vant';
 import Common from '../common/Common.js';
 
 // 创建 axios 实例
@@ -17,16 +17,18 @@ const errorHandler = error => {
         // 从 localstorage 获取 token
         const token = localStorage.getItem('#token');
         if (error.response.status === 403) {
-            notification.error({
-                message: 'Forbidden',
-                description: data.message,
+            showNotify({
+                type: 'danger',
+                message: `Forbidden: ${data.message}`,
+                duration: 3000,
             });
         }
         if (error.response.status === 401 && !(data.result && data.result.isLogin)) {
             console.log('401');
-            notification.error({
-                message: 'Unauthorized',
-                description: 'Authorization verification failed',
+            showNotify({
+                type: 'danger',
+                message: 'Unauthorized: Authorization verification failed',
+                duration: 3000,
             });
             if (token) {
                 // store.dispatch('Logout').then(() => {
@@ -62,18 +64,22 @@ const responseErrorHandler = error => {
             // window.location = Common.getRedirectUrl('#/login?redirectUrl=' + encodeURIComponent(currentUrl));
 
             const isOut = localStorage.getItem('isOut');
-            console.log(isOut, '9999999999999');
+            // 根据环境判断使用不同的 URL
+            const isProduction = process.env.APP_ENV === 'production';
+            const loginPath = isProduction ? 'board.html#/login' : '#/login';
+            
             if (isOut == 'true') {
-                window.location = Common.getRedirectUrl('#/login?isOut=true&redirectUrl=' + encodeURIComponent(currentUrl));
+                window.location = Common.getRedirectUrl(loginPath + '?isOut=true&redirectUrl=' + encodeURIComponent(currentUrl));
             } else {
-                window.location = Common.getRedirectUrl('#/login?isOut=false&redirectUrl=' + encodeURIComponent(currentUrl));
+                window.location = Common.getRedirectUrl(loginPath + '?isOut=false&redirectUrl=' + encodeURIComponent(currentUrl));
             }
         }
     }
     if (error.response.status === 504) {
-        notification.error({
-            message: '504',
-            description: error.response.data,
+        showNotify({
+            type: 'danger',
+            message: `504: ${error.response.data}`,
+            duration: 3000,
         });
     }
     return Promise.reject(error);

+ 11 - 3
webpack.dev.js

@@ -18,7 +18,7 @@ module.exports = WebpackMerge.merge(baseConfig, {
     cache: true,
     // 代码入口
     entry: {
-    // 注册界面
+        // 注册界面
         main: './src/main.js',
     },
 
@@ -43,12 +43,17 @@ module.exports = WebpackMerge.merge(baseConfig, {
         // },
         proxy: {
             '/api': {
-                target: 'http://192.168.1.107:10026/',
+                target: 'http://192.168.1.7:10026/',
                 ws: false,
                 changeOrigin: true,
             },
             '/authApi': {
-                target: 'http://192.168.1.107:10026/',
+                target: 'http://192.168.1.7:10026/',
+                ws: false,
+                changeOrigin: true,
+            },
+            '/android-device-sdk': {
+                target: 'http://192.168.1.7:10026/',
                 ws: false,
                 changeOrigin: true,
             },
@@ -63,6 +68,9 @@ module.exports = WebpackMerge.merge(baseConfig, {
 
 
     plugins: (module.exports.plugins || []).concat([
+        new webpack.DefinePlugin({
+            'process.env.APP_ENV': JSON.stringify('development'),
+        }),
         new HtmlWebpackPlugin({
             title: 'client-div-v3',
             template: './public/index.html',  // 源模板文件

+ 3 - 0
webpack.prod.js

@@ -31,6 +31,9 @@ module.exports =  WebpackMerge.merge(baseConfig, {
 
     plugins: (module.exports.plugins || []).concat([
 
+        new webpack.DefinePlugin({
+            'process.env.APP_ENV': JSON.stringify('production'),
+        }),
 
         new HtmlWebpackPlugin({
             title: 'Board',