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