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

diff --git a/src/pages/inventoryManagement/stockReport/index.vue b/src/pages/inventoryManagement/stockReport/index.vue
new file mode 100644
index 0000000..1eba7a4
--- /dev/null
+++ b/src/pages/inventoryManagement/stockReport/index.vue
@@ -0,0 +1,274 @@
+<template>
+  <view class="report-page">
+    <PageHeader title="搴撳瓨鎶ヨ〃" @back="goBack" />
+
+    <!-- 鎶ヨ〃绫诲瀷 -->
+    <view class="tabs-wrap">
+      <view
+        v-for="t in reportTypes"
+        :key="t.value"
+        class="tab-item"
+        :class="{ active: searchForm.reportType === t.value }"
+        @click="searchForm.reportType = t.value"
+      >
+        <text>{{ t.label }}</text>
+      </view>
+    </view>
+
+    <!-- 鏃堕棿閫夋嫨鍖哄煙宸插幓闄わ紝浣跨敤榛樿鏃ユ湡閫昏緫 -->
+
+    <!-- 鍒楄〃 + 婊氬姩鍒嗛〉 -->
+    <view class="list-section">
+      <view class="section-header">
+        <text class="table-title">{{ tableTitle }}</text>
+      </view>
+      <view v-if="tableData.length > 0">
+        <view v-for="(item, index) in tableData" :key="index" class="card-item">
+          <view class="card-header">
+            <view class="header-main">
+              <text class="product-name">{{ item.productName }}</text>
+              <text class="sub-title">{{ item.model }}</text>
+            </view>
+          </view>
+          <up-divider />
+          <view class="card-body">
+            <view class="row"><text class="l">鍗曚綅</text><text class="r">{{ item.unit }}</text></view>
+            <view class="row" v-if="searchForm.reportType !== 'inout'"><text class="l">鍏ュ簱鏃堕棿</text><text class="r">{{ item.createTime }}</text></view>
+            <view class="row" v-if="searchForm.reportType !== 'inout'"><text class="l">鍏ュ簱鎵规</text><text class="r">{{ item.inboundBatches }}</text></view>
+            <view class="row"><text class="l">鍏ュ簱鏁伴噺</text><text class="r">{{ item.totalStockIn ?? item.stockInNum }}</text></view>
+            <view class="row" v-if="searchForm.reportType === 'inout'"><text class="l">鍑哄簱鏁伴噺</text><text class="r">{{ item.totalStockOut }}</text></view>
+            <view class="row"><text class="l">鐜板湪搴撳瓨</text><text class="r highlight">{{ item.currentStock }}</text></view>
+            <view class="row" v-if="item.createBy"><text class="l">鍏ュ簱浜�</text><text class="r">{{ item.createBy }}</text></view>
+          </view>
+        </view>
+        <view class="load-more-wrap">
+          <u-loadmore :status="loadStatus" @loadmore="loadMore" />
+        </view>
+      </view>
+      <view v-else class="no-data">鏆傛棤鏁版嵁</view>
+    </view>
+
+    <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
+      <up-datetime-picker
+        v-model="dateValue"
+        :mode="datePickerMode"
+        @confirm="onDateConfirm"
+        @cancel="showDatePicker = false"
+      />
+    </up-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, computed, watch } from 'vue'
+import dayjs from 'dayjs'
+import PageHeader from '@/components/PageHeader.vue'
+import { formatDateToYMD } from '@/utils/ruoyi'
+import { onShow, onReachBottom } from '@dcloudio/uni-app'
+import {
+  getStockInventoryReportList,
+  getStockInventoryInAndOutReportList
+} from '@/api/inventoryManagement/stockInventory.js'
+
+const reportTypes = [
+  { label: '鏃ユ姤', value: 'daily' },
+  { label: '鏈堟姤', value: 'monthly' },
+  { label: '杩涘嚭瀛樻姤琛�', value: 'inout' }
+]
+const tableData = ref([])
+const showDatePicker = ref(false)
+const dateValue = ref(Date.now())
+const datePickerTarget = ref('') // single | startMonth | endMonth
+const loadStatus = ref('loadmore') // loadmore | loading | nomore | error
+const page = reactive({ current: 1, size: 20 })
+const data = reactive({
+  searchForm: {
+    reportType: 'daily',
+    singleDate: '',
+    startMonth: '',
+    endMonth: '',
+    startDate: '',
+    endDate: ''
+  }
+})
+const { searchForm } = toRefs(data)
+
+const datePickerMode = computed(() => {
+  if (datePickerTarget.value === 'startMonth' || datePickerTarget.value === 'endMonth') return 'month'
+  return 'date'
+})
+
+const tableTitle = computed(() => {
+  const m = { daily: '鏃ユ姤璇︾粏鏁版嵁', monthly: '鏈堟姤璇︾粏鏁版嵁', inout: '杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�' }
+  return m[searchForm.value.reportType] || '鎶ヨ〃鏁版嵁'
+})
+
+const getQueryParams = () => {
+  const p = {
+    reportType: searchForm.value.reportType,
+    current: page.current,
+    size: page.size
+  }
+  if (searchForm.value.reportType === 'daily') {
+    p.reportDate = searchForm.value.singleDate
+  } else if (searchForm.value.reportType === 'monthly') {
+    p.startMonth = searchForm.value.startMonth
+    p.endMonth = searchForm.value.endMonth
+  } else if (searchForm.value.reportType === 'monthly') {
+    p.startMonth = searchForm.value.startMonth
+    p.endMonth = searchForm.value.endMonth
+  } else {
+    p.startDate = searchForm.value.startDate
+    p.endDate = searchForm.value.endDate
+  }
+  return p
+}
+
+const getList = () => {
+  const isFirstPage = page.current === 1
+  if (isFirstPage) {
+    uni.showLoading({ title: '鏌ヨ涓�...', mask: true })
+  }
+  const params = getQueryParams()
+  const isInout = searchForm.value.reportType === 'inout'
+  const api = isInout ? getStockInventoryInAndOutReportList : getStockInventoryReportList
+  api(params)
+    .then(res => {
+      uni.hideLoading()
+      const records = res.data?.records || []
+      const total = res.data?.total || records.length
+      if (isFirstPage) {
+        tableData.value = records
+      } else {
+        tableData.value = [...tableData.value, ...records]
+      }
+      if (tableData.value.length >= total || total === 0) {
+        loadStatus.value = 'nomore'
+      } else {
+        loadStatus.value = 'loadmore'
+      }
+    })
+    .catch(() => {
+      uni.hideLoading()
+      loadStatus.value = 'error'
+      if (isFirstPage) {
+        uni.showToast({ title: '鏌ヨ澶辫触', icon: 'none' })
+      }
+    })
+}
+
+const handleQuery = () => {
+  page.current = 1
+  loadStatus.value = 'loadmore'
+  getList()
+}
+
+const loadMore = () => {
+  if (loadStatus.value === 'nomore' || loadStatus.value === 'loading') return
+  loadStatus.value = 'loading'
+  page.current++
+  getList()
+}
+
+const openDatePicker = (target) => {
+  datePickerTarget.value = target
+  let val = ''
+  if (target === 'single') val = searchForm.value.singleDate
+  else if (target === 'startMonth') val = searchForm.value.startMonth
+  else if (target === 'endMonth') val = searchForm.value.endMonth
+  dateValue.value = val ? new Date(val).getTime() : Date.now()
+  showDatePicker.value = true
+}
+
+const onDateConfirm = (e) => {
+  const isMonth = datePickerTarget.value === 'startMonth' || datePickerTarget.value === 'endMonth'
+  const str = isMonth ? dayjs(e.value).format('YYYY-MM') : formatDateToYMD(e.value)
+  if (datePickerTarget.value === 'single') searchForm.value.singleDate = str
+  else if (datePickerTarget.value === 'startMonth') searchForm.value.startMonth = str
+  else if (datePickerTarget.value === 'endMonth') searchForm.value.endMonth = str
+  showDatePicker.value = false
+  handleQuery()
+}
+
+// 鍒濆鍖栵細鏃ユ姤榛樿浠婂ぉ锛屾湀鎶ラ粯璁ゆ湰鏈堬紝杩涘嚭瀛橀粯璁ゆ渶杩�7澶�
+const initDefaultDates = () => {
+  const today = dayjs()
+  if (!searchForm.value.singleDate) {
+    searchForm.value.singleDate = today.format('YYYY-MM-DD')
+  }
+  if (!searchForm.value.startMonth || !searchForm.value.endMonth) {
+    const startOfMonth = today.startOf('month').format('YYYY-MM-DD')
+    const endOfMonth = today.endOf('month').format('YYYY-MM-DD')
+    searchForm.value.startMonth = startOfMonth
+    searchForm.value.endMonth = endOfMonth
+  }
+  if (!searchForm.value.startDate || !searchForm.value.endDate) {
+    searchForm.value.endDate = today.format('YYYY-MM-DD')
+    searchForm.value.startDate = today.subtract(6, 'day').format('YYYY-MM-DD')
+  }
+}
+
+watch(
+  () => searchForm.value.reportType,
+  () => {
+    // 鍒囨崲鎶ヨ〃绫诲瀷鏃朵繚鐣欏凡绠楀ソ鐨勯粯璁ゆ椂闂达紝鍙噸鏌�
+    handleQuery()
+  }
+)
+
+onShow(() => {
+  initDefaultDates()
+  handleQuery()
+})
+
+const goBack = () => uni.navigateBack()
+</script>
+
+<style lang="scss" scoped>
+.report-page { min-height: 100vh; background: #f5f5f5; 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; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; }
+.search-row { display: flex; align-items: center; margin-bottom: 0; flex-wrap: wrap; }
+.search-row .label { width: 140rpx; font-size: 26rpx; color: #666; }
+.search-row .label.end { margin-left: 24rpx; }
+.date-picker { flex: 1; min-width: 200rpx; padding: 20rpx; background: #f5f5f5; border-radius: 12rpx; font-size: 28rpx; }
+.btn-row { display: flex; gap: 24rpx; margin-top: 24rpx; }
+.btn-query { flex: 1; text-align: center; padding: 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; }
+.btn-reset { flex: 1; text-align: center; padding: 24rpx; background: #e0e0e0; border-radius: 12rpx; }
+.list-section { margin: 24rpx; }
+.section-header {
+  margin-bottom: 16rpx;
+  padding: 16rpx 20rpx;
+}
+.table-title { font-size: 30rpx; font-weight: 500; color: #333; }
+.card-item {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 20rpx 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.card-header {
+  padding: 4rpx 0 12rpx;
+}
+.header-main {
+  display: flex;
+  flex-direction: column;
+  gap: 6rpx;
+}
+.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: 6rpx 0; font-size: 26rpx; }
+.card-body .l { color: #666; } .card-body .r { color: #333; } .card-body .r.highlight { color: #2979ff; font-weight: 500; }
+.no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
+.load-more-wrap { padding: 24rpx 0 8rpx; }
+</style>

--
Gitblit v1.9.3