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/stockManagement/index.vue |  456 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 456 insertions(+), 0 deletions(-)

diff --git a/src/pages/inventoryManagement/stockManagement/index.vue b/src/pages/inventoryManagement/stockManagement/index.vue
new file mode 100644
index 0000000..e36fac0
--- /dev/null
+++ b/src/pages/inventoryManagement/stockManagement/index.vue
@@ -0,0 +1,456 @@
+<template>
+  <view class="stock-mgmt-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-row">
+        <view class="search-input-wrap">
+          <up-input
+            v-model="searchForm.productName"
+            placeholder="浜у搧澶х被"
+            clearable
+          />
+        </view>
+        <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>
+
+    <!-- 鍒楄〃 + 婊氬姩鍒嗛〉 -->
+    <view class="list-section">
+      <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="sub-title">{{ item.model || item.updateTime }}</text>
+              </view>
+            </view>
+            <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.qualitity }}</text></view>
+              <view class="row"><text class="l">鍐荤粨鏁伴噺</text><text class="r">{{ item.lockedQuantity || 0 }}</text></view>
+              <view class="row"><text class="l">鍙敤搴撳瓨</text><text class="r">{{ item.unLockedQuantity ?? (item.qualitity - (item.lockedQuantity || 0)) }}</text></view>
+              <view class="row"><text class="l">鏈�杩戞洿鏂版椂闂�</text><text class="r">{{ item.updateTime }}</text></view>
+            </view>
+          </view>
+          <view class="card-actions">
+            <view
+              class="btn-link btn-link-primary"
+              :class="{ disabled: !(item.unLockedQuantity > 0) }"
+              @click="openSubtract(item)"
+            >鍑哄簱</view>
+            <view
+              class="btn-link btn-link-warn"
+              v-if="item.unLockedQuantity > 0"
+              @click="openFrozen(item)"
+            >鍐荤粨</view>
+            <view
+              class="btn-link btn-link-plain"
+              v-if="(item.lockedQuantity || 0) > 0"
+              @click="openThaw(item)"
+            >瑙e喕</view>
+          </view>
+        </view>
+        <view class="load-more-wrap">
+          <u-loadmore :status="loadStatus" @loadmore="loadMore" />
+        </view>
+      </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
+    </view>
+
+    <!-- 鍑哄簱/鍐荤粨/瑙e喕寮圭獥 -->
+    <up-popup :show="showQuantityPopup" mode="center" round="16" @close="closeQuantityPopup">
+      <view class="popup-inner">
+        <view class="popup-title">{{ quantityTitle }}</view>
+        <view class="form-row">
+          <text class="form-label">鏁伴噺</text>
+          <up-input v-model="quantityForm.num" type="number" :placeholder="'鏈�澶�' + maxQuantity" />
+        </view>
+        <view class="popup-footer">
+          <view class="btn-cancel" @click="closeQuantityPopup">鍙栨秷</view>
+          <view class="btn-ok" @click="submitQuantity">纭畾</view>
+        </view>
+      </view>
+    </up-popup>
+
+    <!-- 鍙充笅瑙掓柊澧炴寜閽細杩涘叆鏂板搴撳瓨椤甸潰 -->
+    <view class="fab-button" @click="goAdd">
+      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, watch, computed } from 'vue'
+import { onShow, onReachBottom } from '@dcloudio/uni-app'
+import PageHeader from '@/components/PageHeader.vue'
+import {
+  getStockInventoryListPage,
+  createStockInventory,
+  subtractStockInventory,
+  frozenStockInventory,
+  thawStockInventory
+} from '@/api/inventoryManagement/stockInventory.js'
+import {
+  getStockUninventoryListPage,
+  createStockUnInventory,
+  subtractStockUnInventory,
+  frozenStockUninventory,
+  thawStockUninventory
+} from '@/api/inventoryManagement/stockUninventory.js'
+
+const activeTab = ref('qualified')
+const tabs = [
+  { label: '鍚堟牸搴撳瓨', name: 'qualified' },
+  { label: '涓嶅悎鏍煎簱瀛�', name: 'unqualified' }
+]
+const tableData = ref([])
+const total = ref(0)
+const loadStatus = ref('loadmore') // loadmore | loading | nomore | error
+const showQuantityPopup = ref(false)
+const quantityOp = ref('') // subtract | frozen | thaw
+const currentRecord = ref(null)
+const page = reactive({ current: 1, size: 20 })
+const data = reactive({
+  searchForm: { productName: '' },
+  quantityForm: { num: '' }
+})
+const { searchForm, quantityForm } = toRefs(data)
+
+const isQualified = () => activeTab.value === 'qualified'
+const getList = () => {
+  const isFirstPage = page.current === 1
+  if (isFirstPage) {
+    uni.showLoading({ title: '鍔犺浇涓�...', mask: true })
+  }
+  const params = { ...page, productName: searchForm.value.productName }
+  const api = isQualified() ? getStockInventoryListPage : getStockUninventoryListPage
+  api(params)
+    .then(res => {
+      uni.hideLoading()
+      const records = res.data?.records || []
+      const totalCount = res.data?.total || 0
+      if (isFirstPage) {
+        tableData.value = records
+      } 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'
+  getList()
+})
+
+const handleQuery = () => {
+  page.current = 1
+  loadStatus.value = 'loadmore'
+  getList()
+}
+
+const goAdd = () => {
+  const type = isQualified() ? '0' : '1'
+  uni.navigateTo({
+    url: `/pages/inventoryManagement/stockManagement/add?type=${type}`
+  })
+}
+
+const quantityTitle = computed(() => {
+  if (quantityOp.value === 'frozen') return '鍐荤粨搴撳瓨'
+  if (quantityOp.value === 'thaw') return '瑙e喕搴撳瓨'
+  return ''
+})
+const maxQuantity = computed(() => {
+  if (!currentRecord.value) return 0
+  if (quantityOp.value === 'frozen') return currentRecord.value.unLockedQuantity || 0
+  if (quantityOp.value === 'thaw') return currentRecord.value.lockedQuantity || 0
+  return 0
+})
+
+const openSubtract = (row) => {
+  if (!(row.unLockedQuantity > 0)) return
+  try {
+    uni.setStorageSync('stockSubtractRecord', JSON.stringify({
+      item: row,
+      type: isQualified() ? '0' : '1'
+    }))
+  } catch (e) {}
+  const typeParam = isQualified() ? '0' : '1'
+  uni.navigateTo({
+    url: `/pages/inventoryManagement/stockManagement/subtract?type=${typeParam}&id=${row.id}`
+  })
+}
+const openFrozen = (row) => {
+  quantityOp.value = 'frozen'
+  currentRecord.value = row
+  quantityForm.value.num = ''
+  showQuantityPopup.value = true
+}
+const openThaw = (row) => {
+  quantityOp.value = 'thaw'
+  currentRecord.value = row
+  quantityForm.value.num = ''
+  showQuantityPopup.value = true
+}
+const closeQuantityPopup = () => {
+  showQuantityPopup.value = false
+  currentRecord.value = null
+  quantityOp.value = ''
+}
+const submitQuantity = () => {
+  const num = Number(quantityForm.value.num)
+  if (!num || num <= 0 || num > maxQuantity.value) {
+    uni.showToast({ title: `璇疯緭鍏� 1~${maxQuantity.value} 涔嬮棿鐨勬暟閲廯, icon: 'none' })
+    return
+  }
+  const id = currentRecord.value?.id
+  if (!id) return
+  const base = { id, lockedQuantity: num }
+  let promise
+  if (quantityOp.value === 'frozen') {
+    promise = isQualified() ? frozenStockInventory(base) : frozenStockUninventory(base)
+  } else {
+    promise = isQualified() ? thawStockInventory(base) : thawStockUninventory(base)
+  }
+  promise.then(() => {
+    uni.showToast({ title: '鎿嶄綔鎴愬姛', icon: 'success' })
+    closeQuantityPopup()
+    getList()
+  }).catch(() => uni.showToast({ title: '鎿嶄綔澶辫触', icon: 'none' }))
+}
+
+const goDetail = (item) => {
+  if (!item) return
+  try {
+    uni.setStorageSync('stockDetailItem', JSON.stringify({
+      item,
+      type: isQualified() ? '0' : '1'
+    }))
+  } catch (e) {}
+  if (!item.id) {
+    uni.navigateTo({ url: '/pages/inventoryManagement/stockManagement/view' })
+  } else {
+    uni.navigateTo({ url: '/pages/inventoryManagement/stockManagement/view?id=' + item.id })
+  }
+}
+
+const goBack = () => uni.navigateBack()
+onShow(() => getList())
+onReachBottom(() => {
+  loadMore()
+})
+</script>
+
+<style lang="scss" scoped>
+.stock-mgmt-page {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 120rpx;
+}
+.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;
+  margin: 24rpx;
+  padding: 24rpx;
+  border-radius: 16rpx;
+}
+.search-row {
+  display: flex;
+  align-items: center;
+}
+.search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; }
+.btn-search {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 180rpx;
+  min-height: 72rpx;
+  flex-shrink: 0;
+  padding: 20rpx 24rpx;
+  background: #2979ff;
+  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;
+}
+.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; }
+.sub-title { font-size: 24rpx; color: #999; }
+.card-body .row {
+  display: flex;
+  justify-content: space-between;
+  padding: 12rpx 0;
+  font-size: 26rpx;
+}
+.card-body .l { color: #666; }
+.card-body .r { color: #333; }
+.card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.card-actions {
+  display: flex;
+  gap: 16rpx;
+  margin-top: 16rpx;
+  padding-top: 16rpx;
+  border-top: 1rpx solid #eee;
+  justify-content: flex-end;
+}
+.btn-link {
+  min-width: 120rpx;
+  padding: 10rpx 20rpx;
+  border-radius: 24rpx;
+  font-size: 26rpx;
+  text-align: center;
+  border-width: 1rpx;
+  border-style: solid;
+}
+.btn-link-primary {
+  color: #ffffff;
+  background: #2979ff;
+  border-color: #2979ff;
+}
+.btn-link-warn {
+  color: #ff9f1a;
+  background: rgba(255, 159, 26, 0.08);
+  border-color: rgba(255, 159, 26, 0.6);
+}
+.btn-link-plain {
+  color: #666666;
+  background: #f5f5f5;
+  border-color: #e0e0e0;
+}
+.btn-link.disabled {
+  color: #cccccc;
+  background: #f5f5f5;
+  border-color: #e0e0e0;
+}
+.no-data { text-align: center; padding: 80rpx 0; color: #999; font-size: 28rpx; }
+
+/* 鍙充笅瑙掓诞鍔ㄦ柊澧炴寜閽� */
+.fab-button {
+  position: fixed;
+  bottom: calc(30px + env(safe-area-inset-bottom));
+  right: 30px;
+  width: 56px;
+  height: 56px;
+  background: #2979ff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3);
+  z-index: 1000;
+}
+
+.popup-inner { padding: 40rpx; min-width: 560rpx; background: #fff; }
+.popup-title { font-size: 32rpx; font-weight: 500; margin-bottom: 32rpx; }
+.form-row { margin-bottom: 24rpx; }
+.form-row .form-label { display: block; font-size: 26rpx; color: #666; margin-bottom: 12rpx; }
+.date-picker-trigger {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx 24rpx;
+  background: #f5f5f5;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+}
+.date-picker-text { color: #333; }
+.date-picker-text.placeholder { color: #999; }
+.popup-footer { display: flex; gap: 24rpx; margin-top: 40rpx; }
+.btn-cancel { flex: 1; text-align: center; padding: 24rpx; background: #f0f0f0; border-radius: 12rpx; }
+.btn-ok { flex: 1; text-align: center; padding: 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; }
+</style>

--
Gitblit v1.9.3