From 60530101d70fd5887ae1152962882919fceab502 Mon Sep 17 00:00:00 2001
From: 张诺 <zhang_12370@163.com>
Date: 星期四, 16 四月 2026 16:44:47 +0800
Subject: [PATCH] feat(库存管理): 重构库存分类并新增原材料库存页面

---
 src/pages/inventoryManagement/stockManagement/Qualified.vue         |   50 +++++--
 src/pages/works.vue                                                 |    3 
 src/pages/productionManagement/productionReport/index.vue           |   91 +++++++++++++
 src/pages/inventoryManagement/stockManagement/rawMaterialRecord.vue |  151 +++++++++++++++++++++
 src/pages/inventoryManagement/stockManagement/index.vue             |    9 +
 src/pages/inventoryManagement/stockManagement/Unqualified.vue       |   60 ++++++-
 6 files changed, 333 insertions(+), 31 deletions(-)

diff --git a/src/pages/inventoryManagement/stockManagement/Qualified.vue b/src/pages/inventoryManagement/stockManagement/Qualified.vue
index 12b9060..dc2a06c 100644
--- a/src/pages/inventoryManagement/stockManagement/Qualified.vue
+++ b/src/pages/inventoryManagement/stockManagement/Qualified.vue
@@ -27,7 +27,7 @@
             <text class="item-id">{{ item.productName }}</text>
           </view>
           <view class="item-right">
-            <text class="item-tag tag-type">鍚堟牸搴撳瓨</text>
+            <text class="item-tag tag-type">鎴愬搧搴撳瓨</text>
           </view>
         </view>
         
@@ -35,28 +35,28 @@
 
         <view class="item-details">
           <view class="detail-row">
-            <text class="detail-label">瑙勬牸鍨嬪彿</text>
-            <text class="detail-value">{{ item.model }}</text>
+            <text class="detail-label">瀛樿揣/瑙勬牸</text>
+            <text class="detail-value">{{item.productName || "鏃�" }} / {{ item.model || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">搴撳瓨鏁伴噺</text>
-            <text class="detail-value">{{ item.qualitity }} {{ item.unit }}</text>
+            <text class="detail-label">涓绘暟閲�/涓诲崟浣�</text>
+            <text class="detail-value">{{ item.qualitity || "鏃�"  }} / {{ item.unit || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">閿佸畾/鍐荤粨</text>
-            <text class="detail-value">{{ item.lockedQuantity }} {{ item.unit }}</text>
+            <text class="detail-label">杈呮暟閲�/杈呭崟浣�</text>
+            <text class="detail-value">{{ item.subQualitity || "鏃�"  }} / {{ item.subUnit || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">鍙敤搴撳瓨</text>
-            <text class="detail-value" style="color: #2979ff; font-weight: bold;">{{ item.unLockedQuantity }} {{ item.unit }}</text>
+            <text class="detail-label">浠撳簱缂栫爜/浠撳簱鍚嶇О</text>
+            <text class="detail-value">{{ item.warehouseCode || "鏃�"  }} / {{ item.warehouseName || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">搴撳瓨棰勮</text>
-            <text class="detail-value">{{ item.warnNum }}</text>
+            <text class="detail-label">瀛樿揣缂栫爜/搴撳瓨棰勮</text>
+            <text class="detail-value">{{ item.productCode || "鏃�" }} / {{ item.warnNum || "鏃�" }}</text>
           </view>
           <view class="detail-row">
             <text class="detail-label">鏇存柊鏃堕棿</text>
-            <text class="detail-value">{{ item.updateTime }}</text>
+            <text class="detail-value">{{ item.updateTime || "鏃�"  }}</text>
           </view>
         </view>
       </view>
@@ -77,7 +77,7 @@
 const loadStatus = ref('loadmore');
 const page = reactive({ current: 1, size: 10 });
 const total = ref(0);
-const searchForm = reactive({ productName: '' });
+const searchForm = reactive({ productName: '', rootName: '鎴愬搧' });
 
 const handleQuery = () => {
   page.current = 1;
@@ -89,7 +89,7 @@
   if (loading.value) return;
   loading.value = true;
   loadStatus.value = 'loading';
-  getStockInventoryListPage({ ...searchForm, ...page, type: 'qualified' }).then(res => {
+  getStockInventoryListPage({ ...searchForm, ...page }).then(res => {
     loading.value = false;
     const records = res.data.records || [];
     tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
@@ -148,4 +148,26 @@
 .no-data {
   padding-top: 100px;
 }
+
+.item-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+  padding: 5px 0;
+}
+
+.fab-button {
+  position: fixed;
+  right: 20px;
+  bottom: 80px;
+  width: 50px;
+  height: 50px;
+  background-color: #2979ff;
+  border-radius: 25px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+  z-index: 100;
+}
 </style>
diff --git a/src/pages/inventoryManagement/stockManagement/Unqualified.vue b/src/pages/inventoryManagement/stockManagement/Unqualified.vue
index 48dafc4..5b7205b 100644
--- a/src/pages/inventoryManagement/stockManagement/Unqualified.vue
+++ b/src/pages/inventoryManagement/stockManagement/Unqualified.vue
@@ -27,7 +27,7 @@
             <text class="item-id">{{ item.productName }}</text>
           </view>
           <view class="item-right">
-            <text class="item-tag tag-type" style="background-color: #fde2e2; color: #f56c6c;">涓嶅悎鏍煎簱瀛�</text>
+           <text class="item-tag tag-type">杈呮潗搴撳瓨</text>  
           </view>
         </view>
         
@@ -35,24 +35,28 @@
 
         <view class="item-details">
           <view class="detail-row">
-            <text class="detail-label">瑙勬牸鍨嬪彿</text>
-            <text class="detail-value">{{ item.model }}</text>
+            <text class="detail-label">瀛樿揣/瑙勬牸</text>
+            <text class="detail-value">{{item.productName || "鏃�" }} / {{ item.model || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">搴撳瓨鏁伴噺</text>
-            <text class="detail-value">{{ item.qualitity }} {{ item.unit }}</text>
+            <text class="detail-label">涓绘暟閲�/涓诲崟浣�</text>
+            <text class="detail-value">{{ item.qualitity || "鏃�"  }} / {{ item.unit || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">閿佸畾/鍐荤粨</text>
-            <text class="detail-value">{{ item.lockedQuantity }} {{ item.unit }}</text>
+            <text class="detail-label">杈呮暟閲�/杈呭崟浣�</text>
+            <text class="detail-value">{{ item.subQualitity || "鏃�"  }} / {{ item.subUnit || "鏃�"  }}</text>
           </view>
           <view class="detail-row">
-            <text class="detail-label">鍙敤搴撳瓨</text>
-            <text class="detail-value" style="color: #f56c6c; font-weight: bold;">{{ item.unLockedQuantity }} {{ item.unit }}</text>
+            <text class="detail-label">浠撳簱缂栫爜/浠撳簱鍚嶇О</text>
+            <text class="detail-value">{{ item.warehouseCode || "鏃�"  }} / {{ item.warehouseName || "鏃�"  }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="detail-label">瀛樿揣缂栫爜/搴撳瓨棰勮</text>
+            <text class="detail-value">{{ item.productCode || "鏃�" }} / {{ item.warnNum || "鏃�" }}</text>
           </view>
           <view class="detail-row">
             <text class="detail-label">鏇存柊鏃堕棿</text>
-            <text class="detail-value">{{ item.updateTime }}</text>
+            <text class="detail-value">{{ item.updateTime || "鏃�"  }}</text>
           </view>
         </view>
       </view>
@@ -66,14 +70,14 @@
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue';
-import { getStockUninventoryListPage } from "@/api/inventoryManagement/stockUninventory.js";
+import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js";
 
 const tableData = ref([]);
 const loading = ref(false);
 const loadStatus = ref('loadmore');
 const page = reactive({ current: 1, size: 10 });
 const total = ref(0);
-const searchForm = reactive({ productName: '' });
+const searchForm = reactive({ productName: '', rootName: '杈呮潗' });
 
 const handleQuery = () => {
   page.current = 1;
@@ -85,7 +89,7 @@
   if (loading.value) return;
   loading.value = true;
   loadStatus.value = 'loading';
-  getStockUninventoryListPage({ ...searchForm, ...page, type: 'unqualified' }).then(res => {
+  getStockInventoryListPage({ ...searchForm, ...page }).then(res => {
     loading.value = false;
     const records = res.data.records || [];
     tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
@@ -131,4 +135,34 @@
 .no-data {
   padding-top: 100px;
 }
+
+.tag-type {
+  background-color: #e8f5e9;
+  color: #4caf50;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
+.item-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+  padding: 5px 0;
+}
+
+.fab-button {
+  position: fixed;
+  right: 20px;
+  bottom: 80px;
+  width: 50px;
+  height: 50px;
+  background-color: #2979ff;
+  border-radius: 25px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+  z-index: 100;
+}
 </style>
diff --git a/src/pages/inventoryManagement/stockManagement/index.vue b/src/pages/inventoryManagement/stockManagement/index.vue
index 98ebf44..ee298d6 100644
--- a/src/pages/inventoryManagement/stockManagement/index.vue
+++ b/src/pages/inventoryManagement/stockManagement/index.vue
@@ -9,6 +9,9 @@
       <swiper-item class="swiper-item">
         <unqualified-record />
       </swiper-item>
+      <swiper-item class="swiper-item">
+        <raw-material-record />
+      </swiper-item>
     </swiper>
   </view>
 </template>
@@ -18,11 +21,13 @@
 import PageHeader from "@/components/PageHeader.vue";
 import QualifiedRecord from "./Qualified.vue";
 import UnqualifiedRecord from "./Unqualified.vue";
+import RawMaterialRecord from "./rawMaterialRecord.vue";
 
 const activeTab = ref(0);
 const tabs = ref([
-  { name: '鍚堟牸搴撳瓨' },
-  { name: '涓嶅悎鏍煎簱瀛�' }
+  { name: '鎴愬搧搴撳瓨' },
+  { name: '杈呮潗搴撳瓨' },
+  { name: '鍘熸潗鏂欏簱瀛�' }
 ]);
 
 const handleTabClick = (item) => {
diff --git a/src/pages/inventoryManagement/stockManagement/rawMaterialRecord.vue b/src/pages/inventoryManagement/stockManagement/rawMaterialRecord.vue
new file mode 100644
index 0000000..aadfe9f
--- /dev/null
+++ b/src/pages/inventoryManagement/stockManagement/rawMaterialRecord.vue
@@ -0,0 +1,151 @@
+<template>
+  <view class="raw-material-record-container">
+    <view class="search-section">
+      <view class="search-bar">
+        <view class="search-input">
+          <up-input
+            class="search-text"
+            placeholder="璇疯緭鍏ヤ骇鍝佸ぇ绫�"
+            v-model="searchForm.productName"
+            @confirm="handleQuery"
+            clearable
+          />
+        </view>
+        <view class="filter-button" @click="handleQuery">
+          <up-icon name="search" size="24" color="#999"></up-icon>
+        </view>
+      </view>
+    </view>
+
+    <scroll-view scroll-y class="ledger-list" v-if="tableData.length > 0" @scrolltolower="loadMore">
+      <view v-for="item in tableData" :key="item.id" class="ledger-item" :class="{ 'low-stock': isLowStock(item) }">
+        <view class="item-header">
+          <view class="item-left">
+            <view class="document-icon">
+              <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+            </view>
+            <text class="item-id">{{ item.productName }}</text>
+          </view>
+          <view class="item-right">
+            <text class="item-tag tag-type">鍘熸潗鏂欏簱瀛�</text>
+          </view>
+        </view>
+        
+        <up-divider></up-divider>
+
+        <view class="item-details">
+          <view class="detail-row">
+            <text class="detail-label">瀛樿揣/瑙勬牸</text>
+            <text class="detail-value">{{item.productName || "鏃�" }} / {{ item.model || "鏃�"  }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="detail-label">涓绘暟閲�/涓诲崟浣�</text>
+            <text class="detail-value">{{ item.qualitity || "鏃�"  }} / {{ item.unit || "鏃�"  }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="detail-label">杈呮暟閲�/杈呭崟浣�</text>
+            <text class="detail-value">{{ item.subQualitity || "鏃�"  }} / {{ item.subUnit || "鏃�"  }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="detail-label">浠撳簱缂栫爜/浠撳簱鍚嶇О</text>
+            <text class="detail-value">{{ item.warehouseCode || "鏃�"  }} / {{ item.warehouseName || "鏃�"  }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="detail-label">瀛樿揣缂栫爜/搴撳瓨棰勮</text>
+            <text class="detail-value">{{ item.productCode || "鏃�" }} / {{ item.warnNum || "鏃�" }}</text>
+          </view>
+          <view class="detail-row">
+            <text class="detail-label">鏇存柊鏃堕棿</text>
+            <text class="detail-value">{{ item.updateTime || "鏃�"  }}</text>
+          </view>
+        </view>
+      </view>
+      <up-loadmore :status="loadStatus" />
+    </scroll-view>
+    <view v-else-if="!loading" class="no-data">
+      <up-empty mode="data" text="鏆傛棤鍘熸潗鏂欏簱瀛樻暟鎹�"></up-empty>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue';
+import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js";
+
+const tableData = ref([]);
+const loading = ref(false);
+const loadStatus = ref('loadmore');
+const page = reactive({ current: 1, size: 10 });
+const total = ref(0);
+const searchForm = reactive({ productName: '', rootName: '鍘熸潗鏂�' });
+
+const handleQuery = () => {
+  page.current = 1;
+  tableData.value = [];
+  getList();
+};
+
+const getList = () => {
+  if (loading.value) return;
+  loading.value = true;
+  loadStatus.value = 'loading';
+  getStockInventoryListPage({ ...searchForm, ...page }).then(res => {
+    loading.value = false;
+    const records = res.data.records || [];
+    tableData.value = page.current === 1 ? records : [...tableData.value, ...records];
+    total.value = res.data.total;
+    loadStatus.value = tableData.value.length >= total.value ? 'nomore' : 'loadmore';
+  }).catch(() => {
+    loading.value = false;
+    loadStatus.value = 'loadmore';
+  });
+};
+
+const loadMore = () => {
+  if (loadStatus.value === 'nomore' || loading.value) return;
+  page.current++;
+  getList();
+};
+
+const isLowStock = (row) => {
+  const stock = Number(row?.unLockedQuantity ?? 0);
+  const warn = Number(row?.warnNum ?? 0);
+  return Number.isFinite(stock) && Number.isFinite(warn) && stock < warn;
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+@import '@/styles/sales-common.scss';
+
+.raw-material-record-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.tag-type {
+  background-color: #e8f5e9;
+  color: #4caf50;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
+.ledger-list {
+  flex: 1;
+  overflow-y: auto;
+}
+
+.low-stock {
+  background-color: #fde2e2;
+  color: #c45656;
+}
+
+.no-data {
+  padding-top: 100px;
+}
+</style>
diff --git a/src/pages/productionManagement/productionReport/index.vue b/src/pages/productionManagement/productionReport/index.vue
index ab3baf4..8d1e0e9 100644
--- a/src/pages/productionManagement/productionReport/index.vue
+++ b/src/pages/productionManagement/productionReport/index.vue
@@ -11,6 +11,31 @@
             error-message-align="right">
       <!-- 鍩烘湰淇℃伅 -->
       <view class="form-section">
+        <u-form-item label="鏈哄彴"
+                     prop="deviceName"
+                     required>
+          <u-input v-model="form.deviceName"
+          disabled
+                   placeholder="璇疯緭鍏ユ満鍙�" />
+        </u-form-item>
+        <u-form-item label="璁″垝寮�濮嬫椂闂�"
+                     prop="planStartTime"
+                     required>
+          <u-input v-model="form.planStartTime"
+                   placeholder="璇烽�夋嫨璁″垝寮�濮嬫椂闂�"
+                   readonly
+                   @click="showStartTimePicker = true"
+                   suffix-icon="calendar" />
+        </u-form-item>
+        <u-form-item label="璁″垝缁撴潫鏃堕棿"
+                     prop="planEndTime"
+                     required>
+          <u-input v-model="form.planEndTime"
+                   placeholder="璇烽�夋嫨璁″垝缁撴潫鏃堕棿"
+                   readonly
+                   @click="showEndTimePicker = true"
+                   suffix-icon="calendar" />
+        </u-form-item>
         <u-form-item label="寰呯敓浜ф暟閲�"
                      prop="planQuantity"
                      required>
@@ -57,6 +82,20 @@
                      title="閫夋嫨鐢熶骇浜�"
                      @select="onProducerConfirm"
                      @close="showProducerPicker = false" />
+
+    <!-- 寮�濮嬫椂闂撮�夋嫨鍣� -->
+    <up-datetime-picker :show="showStartTimePicker"
+                        v-model="startTimeValue"
+                        mode="datetime"
+                        @confirm="onStartTimeConfirm"
+                        @cancel="showStartTimePicker = false" />
+
+    <!-- 缁撴潫鏃堕棿閫夋嫨鍣� -->
+    <up-datetime-picker :show="showEndTimePicker"
+                        v-model="endTimeValue"
+                        mode="datetime"
+                        @confirm="onEndTimeConfirm"
+                        @cancel="showEndTimePicker = false" />
   </view>
 </template>
 
@@ -80,6 +119,9 @@
 
   // 琛ㄥ崟鏁版嵁
   const form = ref({
+    deviceName: "",
+    planStartTime: "",
+    planEndTime: "",
     planQuantity: "",
     quantity: "",
     scrapQty: "",
@@ -97,6 +139,12 @@
 
   // 鐢熶骇浜洪�夋嫨鍣ㄧ姸鎬�
   const showProducerPicker = ref(false);
+  // 鏃堕棿閫夋嫨鍣ㄧ姸鎬�
+  const showStartTimePicker = ref(false);
+  const showEndTimePicker = ref(false);
+  const startTimeValue = ref(Number(new Date()));
+  const endTimeValue = ref(Number(new Date()));
+
   const producerList = ref([]);
   const currentField = ref(""); // 褰撳墠閫夋嫨鐨勫瓧娈�
 
@@ -131,6 +179,30 @@
     showProducerPicker.value = false;
   };
 
+  // 鏍煎紡鍖栨棩鏈�
+  const formatDateTime = (timestamp) => {
+    const date = new Date(timestamp);
+    const y = date.getFullYear();
+    const m = (date.getMonth() + 1).toString().padStart(2, '0');
+    const d = date.getDate().toString().padStart(2, '0');
+    const h = date.getHours().toString().padStart(2, '0');
+    const min = date.getMinutes().toString().padStart(2, '0');
+    const s = date.getSeconds().toString().padStart(2, '0');
+    return `${y}-${m}-${d} ${h}:${min}:${s}`;
+  };
+
+  // 寮�濮嬫椂闂寸‘璁�
+  const onStartTimeConfirm = (e) => {
+    form.value.planStartTime = formatDateTime(e.value);
+    showStartTimePicker.value = false;
+  };
+
+  // 缁撴潫鏃堕棿纭
+  const onEndTimeConfirm = (e) => {
+    form.value.planEndTime = formatDateTime(e.value);
+    showEndTimePicker.value = false;
+  };
+
   // 鎻愪氦鐘舵��
   const submitting = ref(false);
 
@@ -142,6 +214,21 @@
   const submitForm = async () => {
     submitting.value = true;
     // 鏍¢獙琛ㄥ崟
+    if (!form.value.deviceName) {
+      submitting.value = false;
+      showToast("璇疯緭鍏ユ満鍙�");
+      return;
+    }
+    if (!form.value.planStartTime) {
+      submitting.value = false;
+      showToast("璇烽�夋嫨璁″垝寮�濮嬫椂闂�");
+      return;
+    }
+    if (!form.value.planEndTime) {
+      submitting.value = false;
+      showToast("璇烽�夋嫨璁″垝缁撴潫鏃堕棿");
+      return;
+    }
     if (!form.value.quantity) {
       submitting.value = false;
       showToast("璇疯緭鍏ユ湰娆$敓浜ф暟閲�");
@@ -202,10 +289,14 @@
   onLoad(options => {
     try {
       const orderRow = JSON.parse(options.orderRow);
+      console.log(orderRow, "orderRow");
       // 纭繚 planQuantity 杞崲涓哄瓧绗︿覆锛屼互渚垮湪 u-input 涓纭樉绀�
       form.value.planQuantity = orderRow.planQuantity != null ? String(orderRow.planQuantity) : "";
       form.value.productProcessRouteItemId = orderRow.productProcessRouteItemId || "";
       form.value.workOrderId = orderRow.id || "";
+      form.value.deviceName = orderRow.deviceName || "";
+      form.value.planStartTime = orderRow.planStartTime || "";
+      form.value.planEndTime = orderRow.planEndTime || "";
       getInfo().then(res => {
         // 榛樿浣跨敤褰撳墠鐧诲綍鐢ㄦ埛锛屼絾鍏佽鐢ㄦ埛淇敼
         form.value.userId.value = res.user.userId;
diff --git a/src/pages/works.vue b/src/pages/works.vue
index 964bd07..e30a14e 100644
--- a/src/pages/works.vue
+++ b/src/pages/works.vue
@@ -958,13 +958,12 @@
               orderRow = JSON.stringify({
                 id: workData.id || workOrderId,
                 planQuantity: workData.planQuantity - workData.completeQuantity,
+				deviceName:workData.deviceName,
                 productProcessRouteItemId:
                   workData.productProcessRouteItemId ||
                   workData.浜у搧宸ヨ壓璺嚎椤笽D ||
                   "",
               });
-
-              console.log("鏋勯�犵殑orderRow:", orderRow);
             } else {
               modal.msgError("鏈壘鍒板搴旂殑宸ュ崟淇℃伅");
               return;

--
Gitblit v1.9.3