<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-DD"
|
value-format="YYYY-MM-DD"
|
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="createTime"
|
width="100"
|
align="center"
|
/>
|
<el-table-column
|
v-if="searchForm.reportType === 'monthly'"
|
label="月份"
|
prop="createTime"
|
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=""
|
width="100"
|
align="center"
|
/> -->
|
<el-table-column
|
label="现在库存"
|
prop="inboundNum0"
|
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 { proxy } = getCurrentInstance()
|
// 响应式数据
|
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) {
|
// generateMockData()
|
reportData.value.tableData = response.data.tableData
|
reportData.value.summary = response.data.summary
|
reportData.value.chartData = response.data.chartData
|
nextTick(() => {
|
initCharts()
|
})
|
|
}
|
} catch (error) {
|
ElMessage.error('查询失败:' + error.message)
|
} finally {
|
tableLoading.value = false
|
}
|
}
|
// // 生成假数据
|
// const generateMockData = () => {
|
// // 生成统计卡片假数据
|
// const summary = {
|
// totalIn: 1000,
|
// totalOut: 600,
|
// currentStock: 400,
|
// turnoverRate: 30
|
// }
|
|
// // 生成图表假数据
|
// const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19']
|
// const trendValues = [300, 350, 400, 380, 420]
|
// const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17']
|
// const inValues = [100, 150, 200]
|
// const outValues = [80, 120, 100]
|
|
// const chartData = {
|
// trendDates,
|
// trendValues,
|
// comparisonDates,
|
// inValues,
|
// outValues
|
// }
|
|
// reportData.value = {
|
// summary,
|
// chartData,
|
// tableData: []
|
// }
|
// }
|
// 验证搜索表单
|
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,
|
reportDate: "",
|
startMonth: "",
|
endMonth: "",
|
startDate: "",
|
endDate: ""
|
}
|
|
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)
|
proxy.download("/stockin/exportCopy", params, '库存报表.xlsx')
|
// 创建下载链接
|
// 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>
|