refactor(wms-report): 统一报表页面模板,提取公共逻辑为action-template

1.  将merge、repair下的多个报表页面重构为使用action-template组件
2.  提取自定义导出功能为公共组件CustomExport并复用至receive.vue和action-template
3.  统一报表页面的查询、导出、列配置等公共逻辑
This commit is contained in:
2026-06-03 16:31:51 +08:00
parent 88149561c5
commit 12a887e074
17 changed files with 574 additions and 963 deletions

View File

@@ -137,15 +137,19 @@
</template>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button type="primary" @click="settingVisible = true">列配置</el-button>
<el-button v-if="['out', 'team', 'day', 'month', 'year', 'all'].includes(reportType)" type="primary"
@click="exportData">导出产出钢卷</el-button>
@click="exportData">导出产出</el-button>
<el-button v-if="['loss', 'team', 'day', 'month', 'year', 'all'].includes(reportType)" type="primary"
@click="exportLossData">导出消耗钢卷</el-button>
<el-button type="primary" @click="settingVisible = true">列设置</el-button>
@click="exportLossData">导出消耗</el-button>
<el-button v-if="['out', 'team', 'day', 'month', 'year', 'all'].includes(reportType)" type="primary"
@click="saveOutputReport">保存产出报表</el-button>
@click="openCustomExport('output')">自定义导出产出</el-button>
<el-button v-if="['loss', 'team', 'day', 'month', 'year', 'all'].includes(reportType)" type="primary"
@click="saveLossReport">保存消耗报表</el-button>
@click="openCustomExport('loss')">自定义导出消耗</el-button>
<el-button v-if="['out', 'team', 'day', 'month', 'year', 'all'].includes(reportType)" type="primary"
@click="saveOutputReport">保存产出</el-button>
<el-button v-if="['loss', 'team', 'day', 'month', 'year', 'all'].includes(reportType)" type="primary"
@click="saveLossReport">保存消耗</el-button>
</el-form-item>
</el-form>
</el-row>
@@ -278,6 +282,30 @@
<el-table-column label="产出总重" align="center" prop="outWeight" />
</el-table>
</template>
<!-- day 类型小时数据趋势图 -->
<div v-if="reportType === 'day'" style="margin-top: 20px; height: 400px;">
<el-card>
<template slot="header">
<div class="clearfix">
<span>小时数据趋势</span>
</div>
</template>
<div ref="dayChart" style="width: 100%; height: 350px;"></div>
</el-card>
</div>
<!-- month 类型日数据趋势图 -->
<div v-if="reportType === 'month'" style="margin-top: 20px; height: 400px;">
<el-card>
<template slot="header">
<div class="clearfix">
<span>日数据趋势</span>
</div>
</template>
<div ref="monthChart" style="width: 100%; height: 350px;"></div>
</el-card>
</div>
</template>
<!-- 明细信息和标签页 -->
@@ -318,6 +346,14 @@
</el-radio-group>
<columns-setting :reportType="activeColumnConfig"></columns-setting>
</el-dialog>
<custom-export
ref="customExport"
:visible.sync="customExportVisible"
:storage-key="customExportStorageKey"
:column-groups="columnGroups"
@export="handleCustomExport"
/>
</div>
</template>
@@ -337,6 +373,7 @@ import CoilTable from "@/views/wms/report/components/coilTable";
import ColumnsSetting from "@/views/wms/report/components/setting/columns";
import TimeRangePicker from "@/views/wms/report/components/timeRangePicker.vue";
import SplitSummary from "@/views/wms/report/components/summary/splitSummary.vue";
import CustomExport from "@/views/wms/report/components/CustomExport.vue";
import { saveReportFile } from "@/views/wms/report/js/reportFile";
import * as echarts from 'echarts';
@@ -372,6 +409,7 @@ export default {
ColumnsSetting,
TimeRangePicker,
SplitSummary,
CustomExport,
},
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer', 'coil_quality_status'],
data() {
@@ -437,6 +475,19 @@ export default {
viewType: 'custom',
yesterdaySummary: {},
monthChart: null,
dayChart: null,
customExportVisible: false,
customExportType: 'output',
columnGroups: {
'基本信息': ['itemTypeDesc', 'warehouseName', 'actualWarehouseName', 'dataTypeText'],
'钢卷号': ['enterCoilNo', 'supplierCoilNo', 'currentCoilNo'],
'时间': ['createTime', 'exportTime', 'exportBy'],
'物理属性': ['netWeight', 'length', 'specification', 'actualThickness', 'theoreticalThickness', 'theoreticalLength'],
'材质属性': ['material', 'manufacturer', 'surfaceTreatmentDesc', 'zincLayer', 'packingStatus', 'temperGrade', 'coatingType', 'chromePlateCoilNo'],
'用途': ['purpose', 'businessPurpose'],
'状态': ['qualityStatus', 'statusDesc', 'isRelatedToOrderText'],
'其他': ['itemName', 'itemId', 'packagingRequirement', 'trimmingRequirement', 'transferType', 'saleName', 'remark', 'team'],
},
queryParams: {
startTime: start,
endTime: end,
@@ -523,6 +574,9 @@ export default {
})
return commonIds
},
customExportStorageKey() {
return `coil-report-action-${this.actionType}`
},
},
watch: {
warehouseOptions: {
@@ -718,10 +772,21 @@ export default {
});
}
if (this.reportType === 'day') {
this.$nextTick(() => {
this.initDayChart()
})
}
if (this.reportType === 'month') {
this.$nextTick(() => {
this.initMonthChart()
})
}
this.loading = false;
},
// all 类型:获取昨日数据
getYesterdayData() {
// all 类型:获取昨日数据(逻辑同 getList时间向前推一天
async getYesterdayData() {
const yesterday = new Date(this.dayDate)
yesterday.setDate(yesterday.getDate() - 1)
const year = yesterday.getFullYear()
@@ -730,66 +795,37 @@ export default {
const yesterdayDate = `${year}-${month}-${day}`
const { start, end } = this.getDayTimeRange(yesterdayDate)
Promise.all([
listCoilWithIds({
selectType: 'raw_material',
itemType: 'raw_material',
warehouseIds: this.warehouseIds.join(','),
...this.queryParams,
startTime: start,
endTime: end,
pageSize: 99999,
pageNum: 1,
}),
listCoilWithIds({
selectType: 'product',
itemType: 'product',
warehouseIds: this.warehouseIds.join(','),
...this.queryParams,
startTime: start,
endTime: end,
pageSize: 99999,
pageNum: 1,
}),
]).then((resList) => {
const list = resList.flatMap(res => res.rows)
const yesterdayList = list.sort(
(a, b) => new Date(b.createTime) - new Date(a.createTime)
).map(item => {
if (!item.selectType) {
item.selectType = item.itemType === 'product' ? 'product' : 'raw_material'
}
return item
})
const yesterdayParams = {
...this.queryParams,
startTime: start,
endTime: end,
}
listLightPendingAction({
actionStatus: 2,
actionType: this.actionType,
pageSize: 99999,
pageNum: 1,
startTime: start,
endTime: end,
}).then((lossRes) => {
const lossActions = lossRes.rows
const lossCoilIds = lossActions.map(item => item.coilId).join(',')
const res = await listLightPendingAction({ ...yesterdayParams, actionTypes: this.actionType, actionStatus: 2 })
const lossIds = res.data.filter(item => item.coilId).map(item => item.coilId)
const lossActionIds = res.data.filter(item => item.actionId).map(item => item.actionId)
const outIds = [...new Set(res.data.filter(item => item.processedCoilIds).map(item => item.processedCoilIds))]
if (lossCoilIds) {
listCoilWithIds({
...this.queryParams,
startTime: undefined,
endTime: undefined,
coilIds: lossCoilIds,
pageSize: 99999,
pageNum: 1,
}).then(res => {
const yesterdayLossList = res.rows
this.yesterdaySummary = calcSummary(yesterdayList, yesterdayLossList)
})
} else {
this.yesterdaySummary = calcSummary(yesterdayList, [])
}
})
if (lossIds.length === 0 || outIds.length === 0) {
this.yesterdaySummary = {}
return
}
const [lossRes, outRes] = await Promise.all([
listCoilWithIds({ ...yesterdayParams, actionIds: lossActionIds.join(',') || '', startTime: '', endTime: '', selectType: 'raw_material' }),
listCoilWithIds({ ...yesterdayParams, coilIds: outIds.join(',') || '', startTime: '', endTime: '', byCreateTimeStart: start, byCreateTimeEnd: end, selectType: 'product' }),
])
const yesterdayLossList = lossRes.rows.map(item => {
const [thickness, width] = item.specification?.split('*') || []
return { ...item, computedThickness: parseFloat(thickness), computedWidth: parseFloat(width) }
})
const yesterdayOutList = outRes.rows.map(item => {
const [thickness, width] = item.specification?.split('*') || []
return { ...item, computedThickness: parseFloat(thickness), computedWidth: parseFloat(width) }
})
this.yesterdaySummary = calcSummary(yesterdayOutList, yesterdayLossList)
},
// all 类型:初始化月视图折线图
initMonthChart() {
@@ -852,6 +888,97 @@ export default {
this.monthChart.resize()
})
},
// day 类型:初始化小时数据折线图
initDayChart() {
if (!this.$refs.dayChart) return
if (this.dayChart) {
this.dayChart.dispose()
}
this.dayChart = echarts.init(this.$refs.dayChart)
const hourlyData = {}
for (let h = 0; h < 24; h++) {
hourlyData[h] = { outCount: 0, outTotalWeight: 0, lossCount: 0, lossTotalWeight: 0 }
}
this.outList.forEach(item => {
const timeStr = item.createTime?.split(' ')[1] || '00:00:00'
const hour = parseInt(timeStr.split(':')[0])
if (hourlyData[hour] !== undefined) {
hourlyData[hour].outCount++
hourlyData[hour].outTotalWeight += parseFloat(item.netWeight) || 0
}
})
this.lossList.forEach(item => {
const timeStr = item.actionCompleteTime?.split(' ')[1] || '00:00:00'
const hour = parseInt(timeStr.split(':')[0])
if (hourlyData[hour] !== undefined) {
hourlyData[hour].lossCount++
hourlyData[hour].lossTotalWeight += parseFloat(item.netWeight) || 0
}
})
const hours = Object.keys(hourlyData).map(Number).sort((a, b) => a - b)
const hourLabels = hours.map(h => `${String(h).padStart(2, '0')}:00`)
const outCountData = hours.map(h => hourlyData[h].outCount)
const outTotalWeightData = hours.map(h => hourlyData[h].outTotalWeight.toFixed(2))
const lossCountData = hours.map(h => hourlyData[h].lossCount)
const lossTotalWeightData = hours.map(h => hourlyData[h].lossTotalWeight.toFixed(2))
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } }
},
legend: { data: ['产出数量', '产出总重', '消耗数量', '消耗总重'] },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: [{ type: 'category', boundaryGap: false, data: hourLabels }],
yAxis: [
{ type: 'value', name: '数量', position: 'left' },
{ type: 'value', name: '重量(t)', position: 'right' }
],
series: [
{ name: '产出数量', type: 'line', data: outCountData, yAxisIndex: 0 },
{ name: '产出总重', type: 'line', data: outTotalWeightData, yAxisIndex: 1 },
{ name: '消耗数量', type: 'line', data: lossCountData, yAxisIndex: 0 },
{ name: '消耗总重', type: 'line', data: lossTotalWeightData, yAxisIndex: 1 }
]
}
this.dayChart.setOption(option)
window.addEventListener('resize', () => {
this.dayChart.resize()
})
},
// 自定义导出
openCustomExport(type) {
if (type === 'output' && this.outList.length === 0) {
this.$message({ message: '暂无产出数据可导出', type: 'warning' })
return
}
if (type === 'loss' && this.lossList.length === 0) {
this.$message({ message: '暂无消耗数据可导出', type: 'warning' })
return
}
this.customExportType = type
this.$refs.customExport.open()
},
handleCustomExport(orderedColumns) {
const { pageNum, pageSize, startTime, endTime, ...filters } = this.queryParams
const type = this.customExportType
const ids = type === 'loss'
? { actionIds: this.actionIds }
: { coilIds: this.outList.map(item => item.coilId).join(',') }
this.download('wms/materialCoil/exportCustomOrdered', {
...filters,
...ids,
columnsOrdered: orderedColumns.join(','),
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
exportData() {
if (this.outList.length === 0) {
this.$message.warning('暂无数据可导出')