From cc3001ab9e0ab8ce673b812a22ebea1edd332f2a Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期六, 14 三月 2026 15:38:33 +0800
Subject: [PATCH] fix: 完成仓储物流的功能开发

---
 src/pages/inventoryManagement/receiptManagement/index.vue |  576 ++++++++++++++++++++++++++-------------------------------
 1 files changed, 264 insertions(+), 312 deletions(-)

diff --git a/src/pages/inventoryManagement/receiptManagement/index.vue b/src/pages/inventoryManagement/receiptManagement/index.vue
index 257ea3e..60a3ccc 100644
--- a/src/pages/inventoryManagement/receiptManagement/index.vue
+++ b/src/pages/inventoryManagement/receiptManagement/index.vue
@@ -1,406 +1,358 @@
 <template>
-  <view class="stock-in-page">
-    <PageHeader title="鑷畾涔夊叆搴�" @back="goBack" />
-    
+  <view class="receipt-page">
+    <PageHeader title="鍏ュ簱绠$悊" @back="goBack" />
+
+    <!-- 鏍囩锛氬悎鏍煎叆搴� / 涓嶅悎鏍煎叆搴� -->
+    <view class="tabs-wrap">
+      <view
+        v-for="tab in tabs"
+        :key="tab.name"
+        class="tab-item"
+        :class="{ active: activeTab === tab.name }"
+        @click="activeTab = tab.name"
+      >
+        <text>{{ tab.label }}</text>
+      </view>
+    </view>
+
     <!-- 鎼滅储鍖哄煙 -->
     <view class="search-section">
-      <view class="search-bar">
-        <view class="search-input">
+      <view class="search-row">
+        <view class="search-input-wrap">
           <up-input
-            v-model="searchForm.supplierName"
-            placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О"
+            v-model="searchForm.productName"
+            placeholder="浜у搧澶х被"
             clearable
           />
         </view>
-        <view class="search-button" @click="handleQuery">
-          <up-icon name="search" size="24" color="#999"></up-icon>
+        <view class="btn-search" @click="handleQuery">
+          <view class="btn-search-inner">
+            <up-icon name="search" size="22" color="#fff"></up-icon>
+            <text>鎼滅储</text>
+          </view>
         </view>
       </view>
-      <view class="date-filter" @click="openDatePickerHandler">
-        <text class="date-text">{{ searchForm.timeStr || '閫夋嫨鏃ユ湡' }}</text>
-        <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="item-header">
-          <view class="item-left">
-            <view class="batch-icon">
-              <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+
+    <!-- 鍒楄〃锛堝悎鏍�/涓嶅悎鏍煎叡鐢ㄦ帴鍙� type 鍖哄垎锛� -->
+    <view class="list-section" v-if="activeTab !== 'custom'">
+      <view v-if="tableData.length > 0">
+        <view
+          v-for="(item, index) in tableData"
+          :key="item.id || index"
+          class="card-item"
+        >
+          <view class="card-click" @click="goDetail(item)">
+            <view class="card-header">
+              <view class="header-main">
+                <text class="product-name">{{ item.productName }}</text>
+                <text class="inbound-date">{{ item.createTime || item.inboundDate }}</text>
+              </view>
             </view>
-            <text class="batch-text">{{ item.inboundBatches }}</text>
+            <up-divider />
+            <view class="card-body">
+              <view class="row"><text class="l">瑙勬牸鍨嬪彿</text><text class="r">{{ item.model }}</text></view>
+              <view class="row"><text class="l">鍗曚綅</text><text class="r">{{ item.unit }}</text></view>
+              <view class="row"><text class="l">鍏ュ簱鏁伴噺</text><text class="r highlight">{{ item.stockInNum }}</text></view>
+              <view class="row"><text class="l">鍏ュ簱浜�</text><text class="r">{{ item.createBy }}</text></view>
+              <view class="row" v-if="item.recordType !== undefined"><text class="l">鏉ユ簮</text><text class="r">{{ getRecordType(item.recordType) || item.recordType }}</text></view>
+            </view>
           </view>
-          <view class="item-right">
-            <text class="time-text">{{ item.inboundDate }}</text>
+          <view class="card-actions">
+            <view class="btn-delete" @click.stop="handleDeleteSingle(item)">鍒犻櫎</view>
           </view>
-        </view>
-        <up-divider></up-divider>
-        
-        <view class="item-details">
-          <view class="detail-row">
-            <text class="detail-label">渚涘簲鍟嗗悕绉�</text>
-            <text class="detail-value">{{ item.supplierName }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">浜у搧澶х被</text>
-            <text class="detail-value">{{ item.productCategory }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">瑙勬牸鍨嬪彿</text>
-            <text class="detail-value">{{ item.specificationModel }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">鐗╁搧绫诲瀷</text>
-            <text class="detail-value">{{ item.itemType }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">鍏ュ簱鏁伴噺</text>
-            <text class="detail-value highlight">{{ item.inboundNum }} {{ item.unit }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">鍚◣鍗曚环</text>
-            <text class="detail-value">楼{{ item.taxInclusiveUnitPrice }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">鍚◣鎬讳环</text>
-            <text class="detail-value price">楼{{ item.taxInclusiveTotalPrice }}</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">绋庣巼</text>
-            <text class="detail-value">{{ item.taxRate }}%</text>
-          </view>
-          <view class="detail-row">
-            <text class="detail-label">鍏ュ簱浜�</text>
-            <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>
         </view>
       </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
     </view>
-    
-    <view v-else class="no-data">
-      <text>鏆傛棤鏁版嵁</text>
+    <!-- 鍔犺浇鏇村 -->
+    <view class="load-more-wrap" v-if="tableData.length > 0">
+      <u-loadmore :status="loadStatus" @loadmore="loadMore" />
     </view>
-    
-    <!-- 娴姩鎿嶄綔鎸夐挳 -->
-    <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>
-    
-    <!-- 琛ㄥ崟寮圭獥 -->
-    <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 { ref, reactive, toRefs, watch } from 'vue'
+import { onShow, onReachBottom } from '@dcloudio/uni-app'
 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"
+  getStockInRecordListPage,
+  batchDeleteStockInRecords
+} from '@/api/inventoryManagement/stockInRecord.js'
+import {
+  findAllQualifiedStockInRecordTypeOptions,
+  findAllUnQualifiedStockInRecordTypeOptions
+} from '@/api/basicData/enum.js'
 
-const userStore = useUserStore()
+const activeTab = ref('qualified')
+const stockRecordTypeOptions = ref([])
+const tabs = [
+  { label: '鍚堟牸鍏ュ簱', name: 'qualified', type: '0' },
+  { label: '涓嶅悎鏍煎叆搴�', name: 'unqualified', type: '1' }
+]
 
 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 loadStatus = ref('loadmore') // loadmore | loading | nomore | error
 
+const page = reactive({ current: 1, size: 4 })
 const data = reactive({
   searchForm: {
-    supplierName: '',
-    timeStr: '',
-  },
+    productName: ''
+  }
 })
 const { searchForm } = toRefs(data)
 
-// 缁熶竴鐢� dayjs 杈撳嚭 YYYY-MM-DD
-const formatYMDLocal = (ts) => dayjs(Number(ts)).format('YYYY-MM-DD')
+const currentType = () => tabs.find(t => t.name === activeTab.value)?.type || '0'
 
-// 杩斿洖涓婁竴椤�
-const goBack = () => {
-  uni.navigateBack()
+function getRecordType(recordType) {
+  if (recordType == null || recordType === '') return ''
+  return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || ''
 }
 
-// 鏌ヨ鍒楄〃
-const handleQuery = () => {
-  page.current = 1
-  getList()
+function fetchRecordTypeOptions() {
+  const api = currentType() === '1'
+    ? findAllUnQualifiedStockInRecordTypeOptions
+    : findAllQualifiedStockInRecordTypeOptions
+  api()
+    .then(res => {
+      const data = res.data != null ? res.data : res
+      stockRecordTypeOptions.value = Array.isArray(data) ? data : []
+    })
+    .catch(() => {
+      stockRecordTypeOptions.value = []
+    })
 }
 
 const getList = () => {
-  uni.showLoading({
-    title: '鍔犺浇涓�...',
-    mask: true
-  })
-  
+  if (activeTab.value === 'custom') return
+  const isFirstPage = page.current === 1
+  if (isFirstPage) {
+    uni.showLoading({ title: '鍔犺浇涓�...', mask: true })
+  }
   const params = {
     ...page,
-    supplierName: searchForm.value.supplierName,
-    timeStr: searchForm.value.timeStr
+    type: currentType(),
+    productName: searchForm.value.productName
   }
-  
-  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'
+  getStockInRecordListPage(params)
+    .then(res => {
+      uni.hideLoading()
+      const records = res.data?.records || []
+      const totalCount = res.data?.total || 0
+      if (isFirstPage) {
+        tableData.value = records
+        fetchRecordTypeOptions()
+      } else {
+        tableData.value = [...tableData.value, ...records]
+      }
+      total.value = totalCount
+      if (tableData.value.length >= totalCount || totalCount === 0) {
+        loadStatus.value = 'nomore'
+      } else {
+        loadStatus.value = 'loadmore'
+      }
     })
+    .catch(() => {
+      uni.hideLoading()
+      loadStatus.value = 'error'
+      if (isFirstPage) {
+        uni.showToast({ title: '鍔犺浇澶辫触', icon: 'none' })
+      }
+    })
+}
+
+const loadMore = () => {
+  if (loadStatus.value === 'nomore' || loadStatus.value === 'loading') return
+  loadStatus.value = 'loading'
+  page.current++
+  getList()
+}
+
+watch(activeTab, () => {
+  page.current = 1
+  loadStatus.value = 'loadmore'
+  stockRecordTypeOptions.value = []
+  getList()
+})
+
+const handleQuery = () => {
+  page.current = 1
+  loadStatus.value = 'loadmore'
+  getList()
+}
+
+const goDetail = (item) => {
+  if (!item.id) return
+  try {
+    uni.setStorageSync('receiptDetailItem', JSON.stringify({
+      item,
+      type: currentType()
+    }))
+  } catch (e) {}
+  uni.navigateTo({
+    url: '/pages/inventoryManagement/receiptManagement/view?id=' + item.id
   })
 }
 
-// 鎵撳紑鏃ユ湡閫夋嫨鍣紙绠�鍗曞彲闈狅級
-const openDatePickerHandler = () => {
-  // 鑻ュ凡鏈夐�変腑鏃ユ湡锛岀敤瀹冨垵濮嬪寲锛涘惁鍒欑敤浠婂ぉ
-  dateValue.value = searchForm.value.timeStr
-    ? dayjs(searchForm.value.timeStr, 'YYYY-MM-DD').valueOf()
-    : Date.now()
-  showDatePicker.value = true
-}
-
-// 鏃ユ湡閫夋嫨纭锛堜笌鍏朵粬椤典竴鑷达細鎷挎椂闂存埑 -> YYYY-MM-DD锛�
-const onDateConfirm = (e) => {
-  searchForm.value.timeStr = formatDateToYMD(e.value)
-  showDatePicker.value = false
-  handleQuery()
-}
-
-// 鏂板鍏ュ簱
-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
-  }
-  
   uni.showModal({
     title: '鍒犻櫎',
-    content: '纭鍒犻櫎璇ュ叆搴撹褰曞悧锛�',
-    success: (res) => {
+    content: '纭鍒犻櫎璇ユ潯鍏ュ簱璁板綍锛�',
+    success: res => {
       if (res.confirm) {
-        delStockInCustom({ ids: [item.id] }).then(() => {
-          uni.showToast({
-            title: '鍒犻櫎鎴愬姛',
-            icon: 'success'
+        batchDeleteStockInRecords([item.id])
+          .then(() => {
+            uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
+            getList()
           })
-          getList()
-        }).catch(() => {
-          uni.showToast({
-            title: '鍒犻櫎澶辫触',
-            icon: 'none'
+          .catch(() => {
+            uni.showToast({ title: '鍒犻櫎澶辫触', icon: 'none' })
           })
-        })
       }
     }
   })
 }
 
+const goBack = () => uni.navigateBack()
+
 onShow(() => {
-  getList()
+  if (activeTab.value !== 'custom') getList()
+})
+
+onReachBottom(() => {
+  loadMore()
 })
 </script>
 
-<style scoped lang="scss">
-.stock-in-page {
+<style lang="scss" scoped>
+.receipt-page {
   min-height: 100vh;
   background: #f5f5f5;
-  padding-bottom: 80px;
+  padding-bottom: 40rpx;
 }
-
+.tabs-wrap {
+  display: flex;
+  background: #fff;
+  padding: 24rpx;
+  gap: 24rpx;
+}
+.tab-item {
+  flex: 1;
+  text-align: center;
+  padding: 20rpx;
+  border-radius: 12rpx;
+  background: #f0f0f0;
+  font-size: 28rpx;
+  color: #666;
+}
+.tab-item.active {
+  background: #2979ff;
+  color: #fff;
+}
 .search-section {
   background: #fff;
-  padding: 16px;
-  margin-bottom: 12px;
+  margin: 24rpx;
+  padding: 24rpx;
+  border-radius: 16rpx;
 }
-
-.search-bar {
+.search-row {
   display: flex;
   align-items: center;
-  gap: 12px;
-  margin-bottom: 12px;
+  margin-bottom: 20rpx;
 }
-
-.search-input {
-  flex: 1;
-}
-
-.search-button {
-  width: 44px;
-  height: 44px;
+.search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; }
+.btn-search {
   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-text {
-  font-size: 14px;
-  color: #666;
-}
-
-.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);
-}
-
-.item-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12px;
-}
-
-.item-left {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-
-.batch-icon {
-  width: 32px;
-  height: 32px;
+  width: 180rpx;
+  min-height: 72rpx;
+  flex-shrink: 0;
+  padding: 20rpx 24rpx;
   background: #2979ff;
-  border-radius: 8px;
+  color: #fff;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+  box-sizing: border-box;
+  text-align: center;
+}
+.btn-search-inner {
   display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 8rpx;
+}
+.btn-search text {
+  line-height: 1;
+  vertical-align: middle;
+}
+/* 鎸夐挳鍐呭浘鏍囦笌鏂囧瓧鍨傜洿姘村钩閮藉眳涓� */
+:deep(.btn-search-inner > *),
+:deep(.btn-search > *) {
+  flex-shrink: 0;
+  display: inline-flex;
   align-items: center;
   justify-content: center;
 }
-
-.batch-text {
-  font-size: 14px;
+.list-section {
+  padding: 0 24rpx;
+}
+.card-item {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.card-header {
+  padding: 8rpx 0;
+}
+.header-main {
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+.product-name {
+  font-size: 30rpx;
   font-weight: 500;
   color: #333;
 }
-
-.time-text {
-  font-size: 12px;
+.inbound-date {
+  font-size: 24rpx;
   color: #999;
 }
-
-.item-details {
-  margin: 12px 0;
-}
-
-.detail-row {
+.card-body .row {
   display: flex;
-  align-items: center;
   justify-content: space-between;
-  padding: 8px 0;
+  padding: 12rpx 0;
+  font-size: 26rpx;
 }
-
-.detail-label {
-  font-size: 14px;
-  color: #666;
-}
-
-.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.price {
-  color: #ff6b00;
-  font-weight: 500;
-}
-
-.item-actions {
+.card-body .l { color: #666; }
+.card-body .r { color: #333; }
+.card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.card-actions {
+  margin-top: 16rpx;
+  padding-top: 16rpx;
+  border-top: 1rpx solid #eee;
   display: flex;
-  gap: 12px;
-  margin-top: 12px;
-  padding-top: 12px;
-  border-top: 1px solid #f5f5f5;
+  justify-content: center;
+  align-items: center;
 }
-
+.btn-delete {
+  font-size: 28rpx;
+  color: #f56c6c;
+  padding: 12rpx 32rpx;
+}
 .no-data {
   text-align: center;
-  padding: 60px 0;
+  padding: 80rpx 0;
   color: #999;
-  font-size: 14px;
+  font-size: 28rpx;
 }
-
-.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;
+.load-more-wrap {
+  padding: 24rpx 0 40rpx;
 }
 </style>

--
Gitblit v1.9.3