From 95fce9ecb77e9615df925eee143ce34647c694ce Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期四, 16 四月 2026 17:20:22 +0800
Subject: [PATCH] 扫码出库和扫码入库功能开发

---
 src/pages/inventoryManagement/receiptManagement/index.vue |  593 +++++++++++++------------
 src/pages.json                                            |   16 
 src/pages/inventoryManagement/scanOut/index.vue           |  339 ++++++++++++++
 src/pages/works.vue                                       |   86 ++
 src/pages/inventoryManagement/scanIn/index.vue            |  307 +++++++++++++
 5 files changed, 1,032 insertions(+), 309 deletions(-)

diff --git a/src/pages.json b/src/pages.json
index 1699610..8ba3857 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -747,6 +747,20 @@
       }
     },
     {
+      "path": "pages/inventoryManagement/scanIn/index",
+      "style": {
+        "navigationBarTitleText": "鎵爜鍏ュ簱",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/inventoryManagement/scanOut/index",
+      "style": {
+        "navigationBarTitleText": "鎵爜鍑哄簱",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/safeProduction/safeQualifications/index",
       "style": {
         "navigationBarTitleText": "瑙勭▼涓庤祫璐�",
@@ -1337,4 +1351,4 @@
     "navigationBarTitleText": "RuoYi",
     "navigationBarBackgroundColor": "#FFFFFF"
   }
-}
+}
\ No newline at end of file
diff --git a/src/pages/inventoryManagement/receiptManagement/index.vue b/src/pages/inventoryManagement/receiptManagement/index.vue
index 257ea3e..da08367 100644
--- a/src/pages/inventoryManagement/receiptManagement/index.vue
+++ b/src/pages/inventoryManagement/receiptManagement/index.vue
@@ -1,34 +1,42 @@
 <template>
   <view class="stock-in-page">
-    <PageHeader title="鑷畾涔夊叆搴�" @back="goBack" />
-    
+    <PageHeader title="鑷畾涔夊叆搴�"
+                @back="goBack" />
     <!-- 鎼滅储鍖哄煙 -->
     <view class="search-section">
       <view class="search-bar">
         <view class="search-input">
-          <up-input
-            v-model="searchForm.supplierName"
-            placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О"
-            clearable
-          />
+          <up-input v-model="searchForm.supplierName"
+                    placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О"
+                    clearable />
         </view>
-        <view class="search-button" @click="handleQuery">
-          <up-icon name="search" size="24" color="#999"></up-icon>
+        <view class="search-button"
+              @click="handleQuery">
+          <up-icon name="search"
+                   size="24"
+                   color="#999"></up-icon>
         </view>
       </view>
-      <view class="date-filter" @click="openDatePickerHandler">
+      <view class="date-filter"
+            @click="openDatePickerHandler">
         <text class="date-text">{{ searchForm.timeStr || '閫夋嫨鏃ユ湡' }}</text>
-        <up-icon name="calendar" size="18" color="#999"></up-icon>
+        <up-icon name="calendar"
+                 size="18"
+                 color="#999"></up-icon>
       </view>
     </view>
-    
     <!-- 鍒楄〃 -->
-    <view class="stock-list" v-if="tableData.length > 0">
-      <view v-for="(item, index) in tableData" :key="index" class="stock-item">
+    <view class="stock-list"
+          v-if="tableData.length > 0">
+      <view v-for="(item, index) in tableData"
+            :key="index"
+            class="stock-item">
         <view class="item-header">
           <view class="item-left">
             <view class="batch-icon">
-              <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+              <up-icon name="file-text"
+                       size="16"
+                       color="#ffffff"></up-icon>
             </view>
             <text class="batch-text">{{ item.inboundBatches }}</text>
           </view>
@@ -37,7 +45,6 @@
           </view>
         </view>
         <up-divider></up-divider>
-        
         <view class="item-details">
           <view class="detail-row">
             <text class="detail-label">渚涘簲鍟嗗悕绉�</text>
@@ -76,331 +83,341 @@
             <text class="detail-value">{{ item.createBy }}</text>
           </view>
         </view>
-        
         <view class="item-actions">
-          <u-button type="primary" size="small" @click="handleEdit(item)">缂栬緫</u-button>
-          <u-button type="error" size="small" plain @click="handleDeleteSingle(item)">鍒犻櫎</u-button>
+          <u-button type="primary"
+                    size="small"
+                    @click="handleEdit(item)">缂栬緫</u-button>
+          <u-button type="error"
+                    size="small"
+                    plain
+                    @click="handleDeleteSingle(item)">鍒犻櫎</u-button>
         </view>
       </view>
     </view>
-    
-    <view v-else class="no-data">
+    <view v-else
+          class="no-data">
       <text>鏆傛棤鏁版嵁</text>
     </view>
-    
     <!-- 娴姩鎿嶄綔鎸夐挳 -->
-    <view class="fab-button" @click="handleAdd">
-      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
+    <view class="fab-button"
+          @click="handleAdd">
+      <up-icon name="plus"
+               size="24"
+               color="#ffffff"></up-icon>
     </view>
-    
     <!-- 鏃ユ湡閫夋嫨鍣� -->
-    <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
-      <up-datetime-picker
-        :show="true"
-        v-model="dateValue"
-        @confirm="onDateConfirm"
-        @cancel="showDatePicker = false"
-        mode="date"
-      />
+    <up-popup :show="showDatePicker"
+              mode="bottom"
+              @close="showDatePicker = false">
+      <up-datetime-picker :show="true"
+                          v-model="dateValue"
+                          @confirm="onDateConfirm"
+                          @cancel="showDatePicker = false"
+                          mode="date" />
     </up-popup>
-    
     <!-- 琛ㄥ崟寮圭獥 -->
-    <form-dia-manual ref="formDiaManual" @close="getList" @success="getList" />
+    <form-dia-manual ref="formDiaManual"
+                     @close="getList"
+                     @success="getList" />
   </view>
 </template>
 
 <script setup>
-import { ref, reactive, toRefs, onMounted } from 'vue'
-import { onShow } from '@dcloudio/uni-app'
-import dayjs from 'dayjs'
-import PageHeader from '@/components/PageHeader.vue'
-import FormDiaManual from './components/formDiaManual.vue'
-import useUserStore from '@/store/modules/user'
-import { formatDateToYMD } from '@/utils/ruoyi'
-import {
-  getInPageByCustom,
-  delStockInCustom
-} from "@/api/inventoryManagement/stockIn.js"
+  import { ref, reactive, toRefs, onMounted } from "vue";
+  import { onShow } from "@dcloudio/uni-app";
+  import dayjs from "dayjs";
+  import PageHeader from "@/components/PageHeader.vue";
+  import FormDiaManual from "./components/formDiaManual.vue";
+  import useUserStore from "@/store/modules/user";
+  import { formatDateToYMD } from "@/utils/ruoyi";
+  import {
+    getInPageByCustom,
+    delStockInCustom,
+  } from "@/api/inventoryManagement/stockIn.js";
 
-const userStore = useUserStore()
+  const userStore = useUserStore();
 
-const tableData = ref([])
-const showDatePicker = ref(false)
-const dateValue = ref(new Date().getTime())
-const formDiaManual = ref(null)
+  const tableData = ref([]);
+  const showDatePicker = ref(false);
+  const dateValue = ref(new Date().getTime());
+  const formDiaManual = ref(null);
 
-const page = reactive({
-  current: 1,
-  size: 20,
-})
-const total = ref(0)
+  const page = reactive({
+    current: 1,
+    size: 20,
+  });
+  const total = ref(0);
 
-const data = reactive({
-  searchForm: {
-    supplierName: '',
-    timeStr: '',
-  },
-})
-const { searchForm } = toRefs(data)
+  const data = reactive({
+    searchForm: {
+      supplierName: "",
+      timeStr: "",
+    },
+  });
+  const { searchForm } = toRefs(data);
 
-// 缁熶竴鐢� dayjs 杈撳嚭 YYYY-MM-DD
-const formatYMDLocal = (ts) => dayjs(Number(ts)).format('YYYY-MM-DD')
+  // 缁熶竴鐢� dayjs 杈撳嚭 YYYY-MM-DD
+  const formatYMDLocal = ts => dayjs(Number(ts)).format("YYYY-MM-DD");
 
-// 杩斿洖涓婁竴椤�
-const goBack = () => {
-  uni.navigateBack()
-}
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
 
-// 鏌ヨ鍒楄〃
-const handleQuery = () => {
-  page.current = 1
-  getList()
-}
+  // 鏌ヨ鍒楄〃
+  const handleQuery = () => {
+    page.current = 1;
+    getList();
+  };
 
-const getList = () => {
-  uni.showLoading({
-    title: '鍔犺浇涓�...',
-    mask: true
-  })
-  
-  const params = {
-    ...page,
-    supplierName: searchForm.value.supplierName,
-    timeStr: searchForm.value.timeStr
-  }
-  
-  getInPageByCustom(params).then(res => {
-    uni.hideLoading()
-    tableData.value = res.data.records || []
-    total.value = res.data.total || 0
-  }).catch(() => {
-    uni.hideLoading()
-    uni.showToast({
-      title: '鍔犺浇澶辫触',
-      icon: 'none'
-    })
-  })
-}
+  const getList = () => {
+    uni.showLoading({
+      title: "鍔犺浇涓�...",
+      mask: true,
+    });
 
-// 鎵撳紑鏃ユ湡閫夋嫨鍣紙绠�鍗曞彲闈狅級
-const openDatePickerHandler = () => {
-  // 鑻ュ凡鏈夐�変腑鏃ユ湡锛岀敤瀹冨垵濮嬪寲锛涘惁鍒欑敤浠婂ぉ
-  dateValue.value = searchForm.value.timeStr
-    ? dayjs(searchForm.value.timeStr, 'YYYY-MM-DD').valueOf()
-    : Date.now()
-  showDatePicker.value = true
-}
+    const params = {
+      ...page,
+      supplierName: searchForm.value.supplierName,
+      timeStr: searchForm.value.timeStr,
+    };
 
-// 鏃ユ湡閫夋嫨纭锛堜笌鍏朵粬椤典竴鑷达細鎷挎椂闂存埑 -> YYYY-MM-DD锛�
-const onDateConfirm = (e) => {
-  searchForm.value.timeStr = formatDateToYMD(e.value)
-  showDatePicker.value = false
-  handleQuery()
-}
+    getInPageByCustom(params)
+      .then(res => {
+        uni.hideLoading();
+        tableData.value = res.data.records || [];
+        total.value = res.data.total || 0;
+      })
+      .catch(() => {
+        uni.hideLoading();
+        uni.showToast({
+          title: "鍔犺浇澶辫触",
+          icon: "none",
+        });
+      });
+  };
 
-// 鏂板鍏ュ簱
-const handleAdd = () => {
-  formDiaManual.value?.openDialog('add')
-}
+  // 鎵撳紑鏃ユ湡閫夋嫨鍣紙绠�鍗曞彲闈狅級
+  const openDatePickerHandler = () => {
+    // 鑻ュ凡鏈夐�変腑鏃ユ湡锛岀敤瀹冨垵濮嬪寲锛涘惁鍒欑敤浠婂ぉ
+    dateValue.value = searchForm.value.timeStr
+      ? dayjs(searchForm.value.timeStr, "YYYY-MM-DD").valueOf()
+      : Date.now();
+    showDatePicker.value = true;
+  };
 
-// 缂栬緫
-const handleEdit = (item) => {
-  formDiaManual.value?.openDialog('edit', item)
-}
+  // 鏃ユ湡閫夋嫨纭锛堜笌鍏朵粬椤典竴鑷达細鎷挎椂闂存埑 -> YYYY-MM-DD锛�
+  const onDateConfirm = e => {
+    searchForm.value.timeStr = formatDateToYMD(e.value);
+    showDatePicker.value = false;
+    handleQuery();
+  };
 
-// 鍒犻櫎鍗曟潯
-const handleDeleteSingle = (item) => {
-  // 妫�鏌ユ槸鍚︽槸鏈汉鍒涘缓
-  if (item.createBy !== userStore.nickName) {
-    uni.showToast({
-      title: '涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�',
-      icon: 'none'
-    })
-    return
-  }
-  
-  uni.showModal({
-    title: '鍒犻櫎',
-    content: '纭鍒犻櫎璇ュ叆搴撹褰曞悧锛�',
-    success: (res) => {
-      if (res.confirm) {
-        delStockInCustom({ ids: [item.id] }).then(() => {
-          uni.showToast({
-            title: '鍒犻櫎鎴愬姛',
-            icon: 'success'
-          })
-          getList()
-        }).catch(() => {
-          uni.showToast({
-            title: '鍒犻櫎澶辫触',
-            icon: 'none'
-          })
-        })
-      }
+  // 鏂板鍏ュ簱
+  const handleAdd = () => {
+    formDiaManual.value?.openDialog("add");
+  };
+
+  // 缂栬緫
+  const handleEdit = item => {
+    formDiaManual.value?.openDialog("edit", item);
+  };
+
+  // 鍒犻櫎鍗曟潯
+  const handleDeleteSingle = item => {
+    // 妫�鏌ユ槸鍚︽槸鏈汉鍒涘缓
+    if (item.createBy !== userStore.nickName) {
+      uni.showToast({
+        title: "涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�",
+        icon: "none",
+      });
+      return;
     }
-  })
-}
 
-onShow(() => {
-  getList()
-})
+    uni.showModal({
+      title: "鍒犻櫎",
+      content: "纭鍒犻櫎璇ュ叆搴撹褰曞悧锛�",
+      success: res => {
+        if (res.confirm) {
+          delStockInCustom({ ids: [item.id] })
+            .then(() => {
+              uni.showToast({
+                title: "鍒犻櫎鎴愬姛",
+                icon: "success",
+              });
+              getList();
+            })
+            .catch(() => {
+              uni.showToast({
+                title: "鍒犻櫎澶辫触",
+                icon: "none",
+              });
+            });
+        }
+      },
+    });
+  };
+
+  onShow(() => {
+    getList();
+  });
 </script>
 
 <style scoped lang="scss">
-.stock-in-page {
-  min-height: 100vh;
-  background: #f5f5f5;
-  padding-bottom: 80px;
-}
+  .stock-in-page {
+    min-height: 100vh;
+    background: #f5f5f5;
+    padding-bottom: 80px;
+  }
 
-.search-section {
-  background: #fff;
-  padding: 16px;
-  margin-bottom: 12px;
-}
+  .search-section {
+    background: #fff;
+    padding: 16px;
+    margin-bottom: 12px;
+  }
 
-.search-bar {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  margin-bottom: 12px;
-}
+  .search-bar {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-bottom: 12px;
+  }
 
-.search-input {
-  flex: 1;
-}
+  .search-input {
+    flex: 1;
+  }
 
-.search-button {
-  width: 44px;
-  height: 44px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: #f5f5f5;
-  border-radius: 8px;
-}
+  .search-button {
+    width: 44px;
+    height: 44px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #f5f5f5;
+    border-radius: 8px;
+  }
 
-.date-filter {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 12px 16px;
-  background: #f5f5f5;
-  border-radius: 8px;
-}
+  .date-filter {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 16px;
+    background: #f5f5f5;
+    border-radius: 8px;
+  }
 
-.date-text {
-  font-size: 14px;
-  color: #666;
-}
+  .date-text {
+    font-size: 14px;
+    color: #666;
+  }
 
-.stock-list {
-  padding: 0 16px;
-}
+  .stock-list {
+    padding: 0 16px;
+  }
 
-.stock-item {
-  background: #fff;
-  border-radius: 12px;
-  padding: 16px;
-  margin-bottom: 12px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
-}
+  .stock-item {
+    background: #fff;
+    border-radius: 12px;
+    padding: 16px;
+    margin-bottom: 12px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+  }
 
-.item-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12px;
-}
+  .item-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 12px;
+  }
 
-.item-left {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
+  .item-left {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
 
-.batch-icon {
-  width: 32px;
-  height: 32px;
-  background: #2979ff;
-  border-radius: 8px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
+  .batch-icon {
+    width: 32px;
+    height: 32px;
+    background: #2979ff;
+    border-radius: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
 
-.batch-text {
-  font-size: 14px;
-  font-weight: 500;
-  color: #333;
-}
+  .batch-text {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+  }
 
-.time-text {
-  font-size: 12px;
-  color: #999;
-}
+  .time-text {
+    font-size: 12px;
+    color: #999;
+  }
 
-.item-details {
-  margin: 12px 0;
-}
+  .item-details {
+    margin: 12px 0;
+  }
 
-.detail-row {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 8px 0;
-}
+  .detail-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 8px 0;
+  }
 
-.detail-label {
-  font-size: 14px;
-  color: #666;
-}
+  .detail-label {
+    font-size: 14px;
+    color: #666;
+  }
 
-.detail-value {
-  font-size: 14px;
-  color: #333;
-  text-align: right;
-  flex: 1;
-  margin-left: 12px;
-}
+  .detail-value {
+    font-size: 14px;
+    color: #333;
+    text-align: right;
+    flex: 1;
+    margin-left: 12px;
+  }
 
-.detail-value.highlight {
-  color: #2979ff;
-  font-weight: 500;
-}
+  .detail-value.highlight {
+    color: #2979ff;
+    font-weight: 500;
+  }
 
-.detail-value.price {
-  color: #ff6b00;
-  font-weight: 500;
-}
+  .detail-value.price {
+    color: #ff6b00;
+    font-weight: 500;
+  }
 
-.item-actions {
-  display: flex;
-  gap: 12px;
-  margin-top: 12px;
-  padding-top: 12px;
-  border-top: 1px solid #f5f5f5;
-}
+  .item-actions {
+    display: flex;
+    gap: 12px;
+    margin-top: 12px;
+    padding-top: 12px;
+    border-top: 1px solid #f5f5f5;
+  }
 
-.no-data {
-  text-align: center;
-  padding: 60px 0;
-  color: #999;
-  font-size: 14px;
-}
+  .no-data {
+    text-align: center;
+    padding: 60px 0;
+    color: #999;
+    font-size: 14px;
+  }
 
-.fab-button {
-  position: fixed;
-  right: 20px;
-  bottom: 80px;
-  width: 56px;
-  height: 56px;
-  background: #2979ff;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 4px 12px rgba(41, 121, 255, 0.4);
-  z-index: 999;
-}
+  .fab-button {
+    position: fixed;
+    right: 20px;
+    bottom: 80px;
+    width: 56px;
+    height: 56px;
+    background: #2979ff;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 4px 12px rgba(41, 121, 255, 0.4);
+    z-index: 999;
+  }
 </style>
diff --git a/src/pages/inventoryManagement/scanIn/index.vue b/src/pages/inventoryManagement/scanIn/index.vue
new file mode 100644
index 0000000..3e17f31
--- /dev/null
+++ b/src/pages/inventoryManagement/scanIn/index.vue
@@ -0,0 +1,307 @@
+<template>
+  <view class="scan-container">
+    <PageHeader title="鎵爜鍏ュ簱"
+                @back="goBack" />
+    <view class="module-selector"
+          v-if="!showForm">
+      <view class="module-card"
+            @click="startScan('qualified')">
+        <view class="module-icon qualified">
+          <u-icon name="checkbox-mark"
+                  color="#fff"
+                  size="40"></u-icon>
+        </view>
+        <view class="module-info">
+          <text class="module-label">鍚堟牸鍏ュ簱</text>
+          <text class="module-desc">鎵弿鍚堟牸浜у搧淇℃伅</text>
+        </view>
+      </view>
+      <view class="module-card"
+            @click="startScan('unqualified')">
+        <view class="module-icon unqualified">
+          <u-icon name="close"
+                  color="#fff"
+                  size="40"></u-icon>
+        </view>
+        <view class="module-info">
+          <text class="module-label">涓嶅悎鏍煎叆搴�</text>
+          <text class="module-desc">褰曞叆涓嶅悎鏍煎搧璁板綍</text>
+        </view>
+      </view>
+    </view>
+    <view class="form-content"
+          v-if="showForm">
+      <u-form ref="formRef"
+              :model="form"
+              :rules="formRules"
+              label-width="100px">
+        <u-form-item label="鍏ュ簱绫诲瀷"
+                     border-bottom>
+          <u-tag :text="type === 'qualified' ? '鍚堟牸鍏ュ簱' : '涓嶅悎鏍煎叆搴�'"
+                 :type="type === 'qualified' ? 'success' : 'error'"></u-tag>
+        </u-form-item>
+        <u-form-item label="浜у搧鍚嶇О"
+                     border-bottom>
+          <u-input v-model="form.productName"
+                   readonly
+                   border="none"></u-input>
+        </u-form-item>
+        <u-form-item label="瑙勬牸鍨嬪彿"
+                     border-bottom>
+          <u-input v-model="form.productModelName"
+                   readonly
+                   border="none"></u-input>
+        </u-form-item>
+        <u-form-item label="鍗曚綅"
+                     border-bottom>
+          <u-input v-model="form.unit"
+                   readonly
+                   border="none"></u-input>
+        </u-form-item>
+        <u-form-item label="鍏ュ簱鏁伴噺"
+                     prop="qualitity"
+                     required
+                     border-bottom>
+          <u-number-box v-model="form.qualitity"
+                        :min="1"
+                        :step="1"></u-number-box>
+        </u-form-item>
+        <u-form-item label="棰勮鏁伴噺"
+                     prop="warnNum"
+                     v-if="type === 'qualified'"
+                     border-bottom>
+          <u-number-box v-model="form.warnNum"
+                        :min="0"
+                        :step="1"></u-number-box>
+        </u-form-item>
+        <u-form-item label="澶囨敞"
+                     prop="remark"
+                     border-bottom>
+          <u-textarea v-model="form.remark"
+                      placeholder="璇疯緭鍏ュ娉�"
+                      count></u-textarea>
+        </u-form-item>
+      </u-form>
+      <view class="footer-btns">
+        <u-button class="cancel-btn"
+                  @click="cancelForm">鍙栨秷</u-button>
+        <u-button class="save-btn"
+                  @click="handleSubmit"
+                  :loading="loading">纭鍏ュ簱</u-button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, reactive } from "vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import {
+    createStockInventory,
+    getStockInventoryListPage,
+  } from "@/api/inventoryManagement/stockInventory.js";
+  import {
+    createStockUnInventory,
+    getStockUninventoryListPage,
+  } from "@/api/inventoryManagement/stockUninventory.js";
+  import modal from "@/plugins/modal";
+
+  const showForm = ref(false);
+  const type = ref("qualified"); // qualified | unqualified
+  const loading = ref(false);
+  const formRef = ref(null);
+
+  const form = ref({
+    productId: undefined,
+    productModelId: undefined,
+    productName: "",
+    productModelName: "",
+    unit: "",
+    qualitity: 1,
+    warnNum: 0,
+    remark: "",
+  });
+
+  const formRules = {
+    qualitity: [
+      {
+        required: true,
+        type: "number",
+        message: "璇疯緭鍏ュ叆搴撴暟閲�",
+        trigger: ["blur", "change"],
+      },
+    ],
+  };
+
+  const goBack = () => {
+    if (showForm.value) {
+      showForm.value = false;
+    } else {
+      uni.navigateBack();
+    }
+  };
+
+  const cancelForm = () => {
+    showForm.value = false;
+  };
+
+  const startScan = scanType => {
+    type.value = scanType;
+    uni.scanCode({
+      success: res => {
+        handleScanResult(res.result);
+      },
+      fail: err => {
+        modal.msgError("鎵爜澶辫触");
+      },
+    });
+  };
+
+  const handleScanResult = async result => {
+    try {
+      // 瑙f瀽浜岀淮鐮佹暟鎹�
+      const scanData = JSON.parse(result);
+      if (!scanData.id) {
+        modal.msgError("鏃犳晥鐨勪簩缁寸爜鏁版嵁");
+        return;
+      }
+
+      // 鐩存帴浠庝簩缁寸爜淇℃伅涓幏鍙栦骇鍝佽鎯�
+      form.value.productId = scanData.productId; // 濡傛灉浜岀淮鐮佷腑鏈� productId
+      form.value.productName = scanData.productName;
+      form.value.productModelId = scanData.id; // 浜岀淮鐮佷腑鐨� id 鏄骇鍝佸瀷鍙� ID
+      form.value.productModelName = scanData.model;
+      form.value.unit = scanData.unit;
+      form.value.qualitity = 1;
+      form.value.warnNum = 0;
+      form.value.remark = "";
+
+      showForm.value = true;
+    } catch (error) {
+      console.error("瑙f瀽浜岀淮鐮佸け璐�", error);
+      modal.msgError("瑙f瀽浜岀淮鐮佸け璐ワ紝璇风‘淇濇壂鐮佸唴瀹规纭�");
+    }
+  };
+
+  const handleSubmit = async () => {
+    try {
+      const valid = await formRef.value.validate();
+      if (!valid) return;
+
+      loading.value = true;
+      const apiCall =
+        type.value === "qualified"
+          ? createStockInventory
+          : createStockUnInventory;
+
+      const res = await apiCall(form.value);
+      if (res.code === 200) {
+        modal.msgSuccess("鍏ュ簱鎴愬姛");
+        setTimeout(() => {
+          showForm.value = false;
+        }, 1500);
+      }
+    } catch (error) {
+      console.error("鎻愪氦澶辫触", error);
+    } finally {
+      loading.value = false;
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .scan-container {
+    min-height: 100vh;
+    background-color: #f5f7fa;
+  }
+
+  .module-selector {
+    display: flex;
+    flex-direction: column;
+    padding: 40rpx;
+    height: 80vh;
+    justify-content: center;
+  }
+
+  .module-card {
+    display: flex;
+    align-items: center;
+    background-color: #fff;
+    padding: 80rpx 50rpx;
+    border-radius: 32rpx;
+    box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
+    margin-bottom: 50rpx;
+    transition: all 0.3s ease;
+    border: 2rpx solid transparent;
+
+    &:active {
+      transform: scale(0.98);
+      background-color: #f9f9f9;
+    }
+  }
+
+  .module-icon {
+    width: 140rpx;
+    height: 140rpx;
+    border-radius: 32rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-right: 40rpx;
+
+    &.qualified {
+      background: linear-gradient(135deg, #52c41a, #73d13d);
+      box-shadow: 0 10rpx 20rpx rgba(82, 196, 26, 0.2);
+    }
+
+    &.unqualified {
+      background: linear-gradient(135deg, #ff4d4f, #ff7875);
+      box-shadow: 0 10rpx 20rpx rgba(255, 77, 79, 0.2);
+    }
+  }
+
+  .module-info {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .module-label {
+    font-size: 40rpx;
+    font-weight: 700;
+    color: #1a1a1a;
+    margin-bottom: 12rpx;
+  }
+
+  .module-desc {
+    font-size: 28rpx;
+    color: #999;
+  }
+
+  .form-content {
+    background-color: #fff;
+    margin: 20rpx;
+    padding: 30rpx;
+    border-radius: 16rpx;
+  }
+
+  .footer-btns {
+    margin-top: 60rpx;
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 40rpx;
+  }
+
+  .cancel-btn {
+    width: 30%;
+    background-color: #f5f5f5;
+    color: #666;
+    border: none;
+  }
+
+  .save-btn {
+    width: 65%;
+    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
+    color: #fff;
+    border: none;
+  }
+</style>
diff --git a/src/pages/inventoryManagement/scanOut/index.vue b/src/pages/inventoryManagement/scanOut/index.vue
new file mode 100644
index 0000000..fadc34a
--- /dev/null
+++ b/src/pages/inventoryManagement/scanOut/index.vue
@@ -0,0 +1,339 @@
+<template>
+  <view class="scan-container">
+    <PageHeader title="鎵爜鍑哄簱"
+                @back="goBack" />
+    <view class="module-selector"
+          v-if="!showForm">
+      <view class="module-card"
+            @click="startScan('qualified')">
+        <view class="module-icon qualified">
+          <u-icon name="checkbox-mark"
+                  color="#fff"
+                  size="40"></u-icon>
+        </view>
+        <view class="module-info">
+          <text class="module-label">鍚堟牸鍑哄簱</text>
+          <text class="module-desc">鎵弿鍚堟牸鍝佽繘琛岄鐢ㄥ嚭搴�</text>
+        </view>
+      </view>
+      <view class="module-card"
+            @click="startScan('unqualified')">
+        <view class="module-icon unqualified">
+          <u-icon name="close"
+                  color="#fff"
+                  size="40"></u-icon>
+        </view>
+        <view class="module-info">
+          <text class="module-label">涓嶅悎鏍煎嚭搴�</text>
+          <text class="module-desc">璁板綍涓嶅悎鏍煎搧鐨勫嚭搴撴祦鍚�</text>
+        </view>
+      </view>
+    </view>
+    <view class="form-content"
+          v-if="showForm">
+      <u-form ref="formRef"
+              :model="form"
+              :rules="formRules"
+              label-width="100px">
+        <u-form-item label="鍑哄簱绫诲瀷"
+                     border-bottom>
+          <u-tag :text="type === 'qualified' ? '鍚堟牸鍑哄簱' : '涓嶅悎鏍煎嚭搴�'"
+                 :type="type === 'qualified' ? 'success' : 'error'"></u-tag>
+        </u-form-item>
+        <u-form-item label="浜у搧鍚嶇О"
+                     border-bottom>
+          <u-input v-model="form.productName"
+                   readonly
+                   border="none"></u-input>
+        </u-form-item>
+        <u-form-item label="瑙勬牸鍨嬪彿"
+                     border-bottom>
+          <u-input v-model="form.model"
+                   readonly
+                   border="none"></u-input>
+        </u-form-item>
+        <u-form-item label="鍙敤搴撳瓨"
+                     border-bottom>
+          <u-input v-model="form.unLockedQuantity"
+                   readonly
+                   border="none"></u-input>{{form.unit}}
+        </u-form-item>
+        <u-form-item label="鍑哄簱鏁伴噺"
+                     prop="qualitity"
+                     required
+                     border-bottom>
+          <u-number-box v-model="form.qualitity"
+                        :min="1"
+                        :max="form.unLockedQuantity"
+                        :step="1"></u-number-box>
+          <text class="limit-tip">鏈�澶у彲棰嗙敤: {{form.unLockedQuantity}}</text>
+        </u-form-item>
+        <u-form-item label="澶囨敞"
+                     prop="remark"
+                     border-bottom>
+          <u-textarea v-model="form.remark"
+                      placeholder="璇疯緭鍏ュ娉�"
+                      count></u-textarea>
+        </u-form-item>
+      </u-form>
+      <view class="footer-btns">
+        <u-button class="cancel-btn"
+                  @click="cancelForm">鍙栨秷</u-button>
+        <u-button class="save-btn"
+                  @click="handleSubmit"
+                  :loading="loading">纭鍑哄簱</u-button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+  import { ref, reactive } from "vue";
+  import PageHeader from "@/components/PageHeader.vue";
+  import {
+    subtractStockInventory,
+    getStockInventoryListPage,
+  } from "@/api/inventoryManagement/stockInventory.js";
+  import {
+    subtractStockUnInventory,
+    getStockUninventoryListPage,
+  } from "@/api/inventoryManagement/stockUninventory.js";
+  import modal from "@/plugins/modal";
+
+  const showForm = ref(false);
+  const type = ref("qualified"); // qualified | unqualified
+  const loading = ref(false);
+  const formRef = ref(null);
+
+  const form = ref({
+    id: undefined,
+    productId: undefined,
+    productModelId: undefined,
+    productName: "",
+    model: "",
+    unit: "",
+    qualitity: 1,
+    unLockedQuantity: 0,
+    remark: "",
+  });
+
+  const formRules = {
+    qualitity: [
+      {
+        required: true,
+        type: "number",
+        message: "璇疯緭鍏ュ嚭搴撴暟閲�",
+        trigger: ["blur", "change"],
+      },
+      {
+        validator: (rule, value, callback) => {
+          if (value > form.value.unLockedQuantity) {
+            callback(new Error("涓嶈兘瓒呰繃鍙敤搴撳瓨"));
+          } else {
+            callback();
+          }
+        },
+        trigger: ["blur", "change"],
+      },
+    ],
+  };
+
+  const goBack = () => {
+    if (showForm.value) {
+      showForm.value = false;
+    } else {
+      uni.navigateBack();
+    }
+  };
+
+  const cancelForm = () => {
+    showForm.value = false;
+  };
+
+  const startScan = scanType => {
+    type.value = scanType;
+    uni.scanCode({
+      success: res => {
+        handleScanResult(res.result);
+      },
+      fail: err => {
+        modal.msgError("鎵爜澶辫触");
+      },
+    });
+  };
+
+  const handleScanResult = async result => {
+    try {
+      // 瑙f瀽浜岀淮鐮佹暟鎹�
+      const scanData = JSON.parse(result);
+      if (!scanData.id) {
+        modal.msgError("鏃犳晥鐨勪簩缁寸爜鏁版嵁");
+        return;
+      }
+
+      // 鑾峰彇瀹炴椂搴撳瓨璇︽儏
+      modal.loading("鑾峰彇浜у搧搴撳瓨璇︽儏...");
+      const apiCall =
+        type.value === "qualified"
+          ? getStockInventoryListPage
+          : getStockUninventoryListPage;
+
+      const res = await apiCall({ productModelId: scanData.id });
+      modal.closeLoading();
+
+      if (res.code === 200 && res.data.records && res.data.records.length > 0) {
+        const detail = res.data.records[0];
+        form.value.id = detail.id;
+        form.value.productId = detail.productId;
+        form.value.productName = detail.productName;
+        form.value.productModelId = detail.productModelId;
+        form.value.model = detail.model;
+        form.value.unit = detail.unit;
+        form.value.unLockedQuantity = detail.unLockedQuantity;
+        form.value.qualitity = 1;
+        form.value.remark = "";
+
+        if (form.value.unLockedQuantity <= 0) {
+          modal.msgError("褰撳墠搴撳瓨涓嶈冻锛屾棤娉曞嚭搴�");
+          return;
+        }
+
+        showForm.value = true;
+      } else {
+        modal.msgError("鏈壘鍒拌浜у搧鍨嬪彿鐨勫簱瀛樿褰�");
+      }
+    } catch (error) {
+      modal.closeLoading();
+      console.error("澶勭悊鎵爜缁撴灉澶辫触", error);
+      modal.msgError("鎵爜澶勭悊澶辫触锛岃閲嶈瘯");
+    }
+  };
+
+  const handleSubmit = async () => {
+    try {
+      const valid = await formRef.value.validate();
+      if (!valid) return;
+
+      loading.value = true;
+      const apiCall =
+        type.value === "qualified"
+          ? subtractStockInventory
+          : subtractStockUnInventory;
+
+      const res = await apiCall(form.value);
+      if (res.code === 200) {
+        modal.msgSuccess("鍑哄簱鎴愬姛");
+        setTimeout(() => {
+          showForm.value = false;
+        }, 1500);
+      }
+    } catch (error) {
+      console.error("鎻愪氦澶辫触", error);
+    } finally {
+      loading.value = false;
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .scan-container {
+    min-height: 100vh;
+    background-color: #f5f7fa;
+  }
+
+  .module-selector {
+    display: flex;
+    flex-direction: column;
+    padding: 40rpx;
+    height: 80vh;
+    justify-content: center;
+  }
+
+  .module-card {
+    display: flex;
+    align-items: center;
+    background-color: #fff;
+    padding: 80rpx 50rpx;
+    border-radius: 32rpx;
+    box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
+    margin-bottom: 50rpx;
+    transition: all 0.3s ease;
+    border: 2rpx solid transparent;
+
+    &:active {
+      transform: scale(0.98);
+      background-color: #f9f9f9;
+    }
+  }
+
+  .module-icon {
+    width: 140rpx;
+    height: 140rpx;
+    border-radius: 32rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-right: 40rpx;
+
+    &.qualified {
+      background: linear-gradient(135deg, #52c41a, #73d13d);
+      box-shadow: 0 10rpx 20rpx rgba(82, 196, 26, 0.2);
+    }
+
+    &.unqualified {
+      background: linear-gradient(135deg, #ff4d4f, #ff7875);
+      box-shadow: 0 10rpx 20rpx rgba(255, 77, 79, 0.2);
+    }
+  }
+
+  .module-info {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .module-label {
+    font-size: 40rpx;
+    font-weight: 700;
+    color: #1a1a1a;
+    margin-bottom: 12rpx;
+  }
+
+  .module-desc {
+    font-size: 28rpx;
+    color: #999;
+  }
+
+  .form-content {
+    background-color: #fff;
+    margin: 20rpx;
+    padding: 30rpx;
+    border-radius: 16rpx;
+  }
+
+  .limit-tip {
+    font-size: 24rpx;
+    color: #999;
+    margin-left: 20rpx;
+  }
+
+  .footer-btns {
+    margin-top: 60rpx;
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 40rpx;
+  }
+
+  .cancel-btn {
+    width: 30%;
+    background-color: #f5f5f5;
+    color: #666;
+    border: none;
+  }
+
+  .save-btn {
+    width: 65%;
+    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
+    color: #fff;
+    border: none;
+  }
+</style>
diff --git a/src/pages/works.vue b/src/pages/works.vue
index 7948ada..9ddae95 100644
--- a/src/pages/works.vue
+++ b/src/pages/works.vue
@@ -15,7 +15,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -37,7 +38,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -59,7 +61,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -81,9 +84,24 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
+          </up-grid-item>
+          <up-grid-item @click="jumpUrl('/pages/inventoryManagement/scanIn/index')">
+            <view class="icon-container">
+              <image src="/static/images/icon/xiaoshoutaizhang.svg"
+                     class="item-icon"></image>
+            </view>
+            <text class="item-label">鎵爜鍏ュ簱</text>
+          </up-grid-item>
+          <up-grid-item @click="jumpUrl('/pages/inventoryManagement/scanOut/index')">
+            <view class="icon-container">
+              <image src="/static/images/icon/xiaoshoutaizhang.svg"
+                     class="item-icon"></image>
+            </view>
+            <text class="item-label">鎵爜鍑哄簱</text>
           </up-grid-item>
         </up-grid>
       </view>
@@ -149,7 +167,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -171,7 +190,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -193,7 +213,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -215,7 +236,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -237,7 +259,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -259,7 +282,8 @@
                         :key="index"
                         @click="handleCommonItemClick(item)">
             <view class="icon-container">
-              <image :src="item.icon" class="item-icon"></image>
+              <image :src="item.icon"
+                     class="item-icon"></image>
             </view>
             <text class="item-label">{{item.label}}</text>
           </up-grid-item>
@@ -532,8 +556,8 @@
   // 璁惧绠$悊鍔熻兘鏁版嵁
   const equipmentItems = reactive([
     {
-    	icon: '/static/images/icon/shengchanbaogong.svg',
-    	label: '璁惧鍙拌处',
+      icon: "/static/images/icon/shengchanbaogong.svg",
+      label: "璁惧鍙拌处",
     },
     {
       icon: "/static/images/icon/yunxingguanli.svg",
@@ -912,6 +936,11 @@
         });
     }
   };
+  const jumpUrl = url => {
+    uni.navigateTo({
+      url: url,
+    });
+  };
 
   // 鍒涘缓瀵瑰瓙缁勪欢鐨勫紩鐢�
   const uToastRef = ref(null);
@@ -1092,10 +1121,16 @@
 
     // 瀹氫箟鑿滃崟閰嶇疆鏄犲皠
     const menuMapping = {
-      collaboration: { target: collaborationItems, specialMapping: { "瑙勭珷鍒跺害": "瑙勭珷鍒跺害绠$悊" } },
-      archiveManagement: { target: archiveManagementItems, specialMapping: { "渚涘簲鍟嗘。妗�": "渚涘簲鍟嗙鐞�" } },
+      collaboration: {
+        target: collaborationItems,
+        specialMapping: { 瑙勭珷鍒跺害: "瑙勭珷鍒跺害绠$悊" },
+      },
+      archiveManagement: {
+        target: archiveManagementItems,
+        specialMapping: { 渚涘簲鍟嗘。妗�: "渚涘簲鍟嗙鐞�" },
+      },
     };
-    console.log(allowedMenuTitles)
+    console.log(allowedMenuTitles);
     // 閫氱敤杩囨护鍑芥暟
     const filterArray = (targetArray, specialMapping) => {
       const filtered = targetArray.filter(item => {
@@ -1112,7 +1147,10 @@
     filterArray(marketingItems);
     filterArray(purchaseItems);
     filterArray(financeManagementItems);
-    filterArray(archiveManagementItems, menuMapping.archiveManagement.specialMapping);
+    filterArray(
+      archiveManagementItems,
+      menuMapping.archiveManagement.specialMapping
+    );
     filterArray(collaborationItems, menuMapping.collaboration.specialMapping);
     filterArray(safetyItems);
     filterArray(humanResourcesItems);
@@ -1125,14 +1163,22 @@
   // 妫�鏌ユā鍧楁槸鍚︽湁鑿滃崟椤归渶瑕佹樉绀�
   const hasMarketingItems = computed(() => marketingItems.length > 0);
   const hasPurchaseItems = computed(() => purchaseItems.length > 0);
-  const hasFinanceManagementItems = computed(() => financeManagementItems.length > 0);
-  const hasArchiveManagementItems = computed(() => archiveManagementItems.length > 0);
-  const hasAfterSalesServiceItems = computed(() => afterSalesServiceItems.length > 0);
+  const hasFinanceManagementItems = computed(
+    () => financeManagementItems.length > 0
+  );
+  const hasArchiveManagementItems = computed(
+    () => archiveManagementItems.length > 0
+  );
+  const hasAfterSalesServiceItems = computed(
+    () => afterSalesServiceItems.length > 0
+  );
   const hasCollaborationItems = computed(() => collaborationItems.length > 0);
   const hasSafetyItems = computed(() => safetyItems.length > 0);
   const hasQualityItems = computed(() => qualityItems.length > 0);
   // const hasHumanResourcesItems = computed(() => humanResourcesItems.length > 0);
-  const hasWarehouseLogisticsItems = computed(() => warehouseLogisticsItems.length > 0);
+  const hasWarehouseLogisticsItems = computed(
+    () => warehouseLogisticsItems.length > 0
+  );
   // const hasProductionItems = computed(() => productionItems.length > 0);
   const hasEquipmentItems = computed(() => equipmentItems.length > 0);
 

--
Gitblit v1.9.3