From e9cc8c82c196247484aa4d5ddd384bd347ab8afc Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 18 九月 2025 17:37:28 +0800
Subject: [PATCH] 库存报表前端页面

---
 src/api/inventoryManagement/stockReport.js          |   56 ++++
 src/views/inventoryManagement/stockReport/index.vue |  678 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 734 insertions(+), 0 deletions(-)

diff --git a/src/api/inventoryManagement/stockReport.js b/src/api/inventoryManagement/stockReport.js
new file mode 100644
index 0000000..580933b
--- /dev/null
+++ b/src/api/inventoryManagement/stockReport.js
@@ -0,0 +1,56 @@
+import request from "@/utils/request";
+
+// 鑾峰彇搴撳瓨鏃ユ姤缁熻
+export const getStockDailyReport = (params) => {
+    return request({
+        url: "/stockreport/daily",
+        method: "get",
+        params,
+    });
+};
+
+// 鑾峰彇搴撳瓨鏈堟姤缁熻
+export const getStockMonthlyReport = (params) => {
+    return request({
+        url: "/stockreport/monthly",
+        method: "get",
+        params,
+    });
+};
+
+// 鑾峰彇浣滀笟鎶ヨ〃缁熻
+export const getWorkReport = (params) => {
+    return request({
+        url: "/stockreport/work",
+        method: "get",
+        params,
+    });
+};
+
+// 鑾峰彇搴撳瓨杩涘嚭瀛樼粺璁�
+export const getStockInOutReport = (params) => {
+    return request({
+        url: "/stockreport/inout",
+        method: "get",
+        params,
+    });
+};
+
+// 瀵煎嚭搴撳瓨鎶ヨ〃
+export const exportStockReport = (params) => {
+    return request({
+        url: "/stockreport/export",
+        method: "get",
+        params,
+        responseType: 'blob'
+    });
+};
+
+// 鑾峰彇搴撳瓨瓒嬪娍鏁版嵁
+export const getStockTrendData = (params) => {
+    return request({
+        url: "/stockreport/trend",
+        method: "get",
+        params,
+    });
+};
diff --git a/src/views/inventoryManagement/stockReport/index.vue b/src/views/inventoryManagement/stockReport/index.vue
new file mode 100644
index 0000000..79f1a02
--- /dev/null
+++ b/src/views/inventoryManagement/stockReport/index.vue
@@ -0,0 +1,678 @@
+<template>
+  <div class="app-container">
+    <!-- 鎼滅储琛ㄥ崟 -->
+    <div class="search_form">
+      <div class="search_left">
+        <span class="search_title">鎶ヨ〃绫诲瀷锛�</span>
+        <el-select
+          v-model="searchForm.reportType"
+          style="width: 150px;"
+          placeholder="璇烽�夋嫨"
+          @change="handleReportTypeChange"
+        >
+          <el-option label="鏃ユ姤" value="daily" />
+          <el-option label="鏈堟姤" value="monthly" />
+          <el-option label="浣滀笟鎶ヨ〃" value="work" />
+          <el-option label="杩涘嚭瀛樻姤琛�" value="inout" />
+        </el-select>
+        
+        <span class="search_title ml10">鏃堕棿鑼冨洿锛�</span>
+         <el-date-picker
+           v-if="searchForm.reportType === 'daily'"
+           v-model="searchForm.singleDate"
+           type="date"
+           placeholder="璇烽�夋嫨鏃ユ湡"
+           format="YYYY-MM-DD"
+           value-format="YYYY-MM-DD"
+           style="width: 200px;"
+         />
+        <el-date-picker
+          v-else-if="searchForm.reportType === 'monthly'"
+          v-model="searchForm.monthRange"
+          type="monthrange"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫湀浠�"
+          end-placeholder="缁撴潫鏈堜唤"
+          format="YYYY-MM"
+          value-format="YYYY-MM"
+          style="width: 240px;"
+        />
+        <el-date-picker
+          v-else
+          v-model="searchForm.dateRange"
+          type="daterange"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+          format="YYYY-MM-DD"
+          value-format="YYYY-MM-DD"
+          style="width: 240px;"
+        />
+        
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
+          鏌ヨ
+        </el-button>
+        <el-button @click="handleReset">閲嶇疆</el-button>
+      </div>
+      
+      <div class="search_right">
+        <el-button type="success" @click="handleExport" icon="Download">
+          瀵煎嚭鎶ヨ〃
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 缁熻鍗$墖 -->
+    <div class="stats_cards" v-if="reportData.summary">
+      <el-row :gutter="20">
+        <el-col :span="6">
+          <el-card class="stats_card">
+            <div class="stats_content">
+              <div class="stats_icon in">
+                <el-icon><TrendCharts /></el-icon>
+              </div>
+              <div class="stats_info">
+                <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>
+                <div class="stats_label">鎬诲叆搴撻噺</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+        <el-col :span="6">
+          <el-card class="stats_card">
+            <div class="stats_content">
+              <div class="stats_icon out">
+                <el-icon><TrendCharts /></el-icon>
+              </div>
+              <div class="stats_info">
+                <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>
+                <div class="stats_label">鎬诲嚭搴撻噺</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+        <el-col :span="6">
+          <el-card class="stats_card">
+            <div class="stats_content">
+              <div class="stats_icon stock">
+                <el-icon><Box /></el-icon>
+              </div>
+              <div class="stats_info">
+                <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>
+                <div class="stats_label">褰撳墠搴撳瓨</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+        <el-col :span="6">
+          <el-card class="stats_card">
+            <div class="stats_content">
+              <div class="stats_icon turnover">
+                <el-icon><Refresh /></el-icon>
+              </div>
+              <div class="stats_info">
+                <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>
+                <div class="stats_label">鍛ㄨ浆鐜�</div>
+              </div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 鍥捐〃鍖哄煙 -->
+    <div class="chart_section" v-if="reportData.chartData">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-card>
+            <template #header>
+              <span>搴撳瓨瓒嬪娍鍥�</span>
+            </template>
+            <div ref="trendChart" style="height: 300px;"></div>
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card>
+            <template #header>
+              <span>杩涘嚭搴撳姣�</span>
+            </template>
+            <div ref="comparisonChart" style="height: 300px;"></div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 璇︾粏鏁版嵁琛ㄦ牸 -->
+    <div class="table_section">
+      <el-card>
+        <template #header>
+          <span>{{ getTableTitle() }}</span>
+        </template>
+         <el-table
+           v-loading="tableLoading"
+           :data="reportData.tableData"
+           border
+           height="400"
+           style="width: 100%"
+           :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+         >
+          <el-table-column
+            align="center"
+            label="搴忓彿"
+            type="index"
+            width="60"
+          />
+           <el-table-column
+             v-if="searchForm.reportType === 'daily'"
+             label="鏃ユ湡"
+             prop="date"
+             width="100"
+             align="center"
+           />
+           <el-table-column
+             v-if="searchForm.reportType === 'monthly'"
+             label="鏈堜唤"
+             prop="month"
+             width="100"
+             align="center"
+           />
+           <el-table-column
+             label="鍏ュ簱鏃堕棿"
+             prop="createTime"
+             width="100"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="鍏ュ簱鎵规"
+             prop="inboundBatches"
+             width="160"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="渚涘簲鍟嗗悕绉�"
+             prop="supplierName"
+             min-width="240"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="浜у搧澶х被"
+             prop="productCategory"
+             width="100"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="瑙勬牸鍨嬪彿"
+             prop="specificationModel"
+             min-width="200"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="鍗曚綅"
+             prop="unit"
+             width="70"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="鏈熷垵搴撳瓨"
+             prop="beginStock"
+             width="100"
+             align="center"
+           />
+           <el-table-column
+             label="鍏ュ簱鏁伴噺"
+             prop="inboundNum"
+             width="100"
+             align="center"
+           />
+           <el-table-column
+             label="鍑哄簱鏁伴噺"
+             prop="outboundNum"
+             width="100"
+             align="center"
+           />
+           <el-table-column
+             label="鏈熸湯搴撳瓨"
+             prop="endStock"
+             width="100"
+             align="center"
+           />
+           <el-table-column
+             label="鍚◣鍗曚环"
+             prop="taxInclusiveUnitPrice"
+             width="100"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="鍚◣鎬讳环"
+             prop="taxInclusiveTotalPrice"
+             width="100"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="绋庣巼(%)"
+             prop="taxRate"
+             width="80"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="涓嶅惈绋庢�讳环"
+             prop="taxExclusiveTotalPrice"
+             width="100"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             label="鍏ュ簱浜�"
+             prop="createBy"
+             width="80"
+             show-overflow-tooltip
+           />
+           <el-table-column
+             v-if="searchForm.reportType === 'work'"
+             label="鎿嶄綔浜哄憳"
+             prop="operator"
+             width="80"
+             align="center"
+           />
+           <el-table-column
+             v-if="searchForm.reportType === 'work'"
+             label="鎿嶄綔鏃堕棿"
+             prop="operateTime"
+             width="150"
+             align="center"
+           />
+        </el-table>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+import {
+  getStockDailyReport,
+  getStockMonthlyReport,
+  getWorkReport,
+  getStockInOutReport,
+  exportStockReport
+} from '@/api/inventoryManagement/stockReport'
+
+// 鍝嶅簲寮忔暟鎹�
+const tableLoading = ref(false)
+const trendChart = ref(null)
+const comparisonChart = ref(null)
+
+const searchForm = reactive({
+  reportType: 'daily',
+  singleDate: '',
+  dateRange: [],
+  monthRange: []
+})
+
+const reportData = ref({
+  summary: null,
+  chartData: null,
+  tableData: []
+})
+
+// 鑾峰彇琛ㄦ牸鏍囬
+const getTableTitle = () => {
+  const typeMap = {
+    daily: '鏃ユ姤璇︾粏鏁版嵁',
+    monthly: '鏈堟姤璇︾粏鏁版嵁',
+    work: '浣滀笟鎶ヨ〃璇︾粏鏁版嵁',
+    inout: '杩涘嚭瀛樻姤琛ㄨ缁嗘暟鎹�'
+  }
+  return typeMap[searchForm.reportType] || '鎶ヨ〃璇︾粏鏁版嵁'
+}
+
+// 鎶ヨ〃绫诲瀷鏀瑰彉
+const handleReportTypeChange = () => {
+  reportData.value = {
+    summary: null,
+    chartData: null,
+    tableData: []
+  }
+}
+
+// 鏌ヨ鏁版嵁
+const handleQuery = async () => {
+  if (!validateSearchForm()) {
+    return
+  }
+  
+  tableLoading.value = true
+  try {
+    const params = getQueryParams()
+    let response
+    
+    switch (searchForm.reportType) {
+      case 'daily':
+        response = await getStockDailyReport(params)
+        break
+      case 'monthly':
+        response = await getStockMonthlyReport(params)
+        break
+      case 'work':
+        response = await getWorkReport(params)
+        break
+      case 'inout':
+        response = await getStockInOutReport(params)
+        break
+      default:
+        throw new Error('鏈煡鐨勬姤琛ㄧ被鍨�')
+    }
+    
+    if (response.code === 200) {
+      reportData.value = response.data
+      nextTick(() => {
+        initCharts()
+      })
+    }
+  } catch (error) {
+    ElMessage.error('鏌ヨ澶辫触锛�' + error.message)
+  } finally {
+    tableLoading.value = false
+  }
+}
+
+// 楠岃瘉鎼滅储琛ㄥ崟
+const validateSearchForm = () => {
+  if (searchForm.reportType === 'daily') {
+    if (!searchForm.singleDate) {
+      ElMessage.warning('璇烽�夋嫨鏃ユ湡')
+      return false
+    }
+  } else if (searchForm.reportType === 'work' || searchForm.reportType === 'inout') {
+    if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
+      ElMessage.warning('璇烽�夋嫨鏃ユ湡鑼冨洿')
+      return false
+    }
+  } else if (searchForm.reportType === 'monthly') {
+    if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
+      ElMessage.warning('璇烽�夋嫨鏈堜唤鑼冨洿')
+      return false
+    }
+  }
+  return true
+}
+
+// 鑾峰彇鏌ヨ鍙傛暟
+const getQueryParams = () => {
+  const params = {
+    reportType: searchForm.reportType
+  }
+  
+  if (searchForm.reportType === 'daily') {
+    params.reportDate = searchForm.singleDate
+  } else if (searchForm.reportType === 'monthly') {
+    params.startMonth = searchForm.monthRange[0]
+    params.endMonth = searchForm.monthRange[1]
+  } else {
+    params.startDate = searchForm.dateRange[0]
+    params.endDate = searchForm.dateRange[1]
+  }
+  
+  return params
+}
+
+// 閲嶇疆鎼滅储
+const handleReset = () => {
+  searchForm.reportType = 'daily'
+  searchForm.singleDate = ''
+  searchForm.dateRange = []
+  searchForm.monthRange = []
+  reportData.value = {
+    summary: null,
+    chartData: null,
+    tableData: []
+  }
+}
+
+// 瀵煎嚭鎶ヨ〃
+const handleExport = async () => {
+  if (!validateSearchForm()) {
+    return
+  }
+  
+  try {
+    const params = getQueryParams()
+    const response = await exportStockReport(params)
+    
+    // 鍒涘缓涓嬭浇閾炬帴
+    const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    window.URL.revokeObjectURL(url)
+    
+    ElMessage.success('瀵煎嚭鎴愬姛')
+  } catch (error) {
+    ElMessage.error('瀵煎嚭澶辫触锛�' + error.message)
+  }
+}
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+  if (!reportData.value.chartData) return
+  
+  initTrendChart()
+  initComparisonChart()
+}
+
+// 鍒濆鍖栬秼鍔垮浘
+const initTrendChart = () => {
+  if (!trendChart.value) return
+  
+  const chart = echarts.init(trendChart.value)
+  const option = {
+    title: {
+      text: '搴撳瓨鍙樺寲瓒嬪娍',
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: ['搴撳瓨閲�'],
+      top: 30
+    },
+    xAxis: {
+      type: 'category',
+      data: reportData.value.chartData.trendDates || []
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [{
+      name: '搴撳瓨閲�',
+      type: 'line',
+      data: reportData.value.chartData.trendValues || [],
+      smooth: true,
+      itemStyle: {
+        color: '#409EFF'
+      }
+    }]
+  }
+  chart.setOption(option)
+}
+
+// 鍒濆鍖栧姣斿浘
+const initComparisonChart = () => {
+  if (!comparisonChart.value) return
+  
+  const chart = echarts.init(comparisonChart.value)
+  const option = {
+    title: {
+      text: '杩涘嚭搴撳姣�',
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: ['鍏ュ簱', '鍑哄簱'],
+      top: 30
+    },
+    xAxis: {
+      type: 'category',
+      data: reportData.value.chartData.comparisonDates || []
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [
+      {
+        name: '鍏ュ簱',
+        type: 'bar',
+        data: reportData.value.chartData.inValues || [],
+        itemStyle: {
+          color: '#67C23A'
+        }
+      },
+      {
+        name: '鍑哄簱',
+        type: 'bar',
+        data: reportData.value.chartData.outValues || [],
+        itemStyle: {
+          color: '#F56C6C'
+        }
+      }
+    ]
+  }
+  chart.setOption(option)
+}
+
+// 缁勪欢鎸傝浇鏃惰缃粯璁ゆ椂闂�
+onMounted(() => {
+  const today = new Date()
+  searchForm.singleDate = today.toISOString().split('T')[0]
+  
+  const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
+  searchForm.dateRange = [
+    yesterday.toISOString().split('T')[0],
+    today.toISOString().split('T')[0]
+  ]
+})
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+
+.search_form {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.search_left {
+  display: flex;
+  align-items: center;
+}
+
+.search_title {
+  font-weight: 500;
+  color: #333;
+  margin-right: 8px;
+}
+
+.ml10 {
+  margin-left: 10px;
+}
+
+.stats_cards {
+  margin-bottom: 20px;
+}
+
+.stats_card {
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.stats_content {
+  display: flex;
+  align-items: center;
+  padding: 10px 0;
+}
+
+.stats_icon {
+  width: 50px;
+  height: 50px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 15px;
+  font-size: 24px;
+  color: #fff;
+}
+
+.stats_icon.in {
+  background: linear-gradient(135deg, #67C23A, #85CE61);
+}
+
+.stats_icon.out {
+  background: linear-gradient(135deg, #F56C6C, #F78989);
+}
+
+.stats_icon.stock {
+  background: linear-gradient(135deg, #409EFF, #66B1FF);
+}
+
+.stats_icon.turnover {
+  background: linear-gradient(135deg, #E6A23C, #EEBE77);
+}
+
+.stats_info {
+  flex: 1;
+}
+
+.stats_value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  line-height: 1;
+  margin-bottom: 5px;
+}
+
+.stats_label {
+  font-size: 14px;
+  color: #666;
+}
+
+.chart_section {
+  margin-bottom: 20px;
+}
+
+.table_section {
+  margin-bottom: 20px;
+}
+
+:deep(.el-card__header) {
+  background: #f8f9fa;
+  border-bottom: 1px solid #e9ecef;
+  font-weight: 500;
+}
+
+:deep(.el-table .el-table__header-wrapper th) {
+  background-color: #F0F1F5 !important;
+  color: #333333;
+  font-weight: 600;
+}
+
+:deep(.el-table .el-table__body-wrapper td) {
+  padding: 8px 0;
+}
+</style>

--
Gitblit v1.9.3