feat: 新增生产日报报表功能(TASK-xxx)

新增生产日报页面,包含日报/月报切换、汇总卡片、多标签页数据展示,配套接口与路由配置
master
zch 1 month ago
parent 0023293abd
commit 1b0403e95b

@ -0,0 +1,37 @@
import request from '@/utils/request'
// 查询生产日报汇总
export function getProductionDailySummary(query) {
return request({
url: '/report/productionDailyReport/summary',
method: 'get',
params: query
})
}
// 查询生产日报按日汇总
export function listProductionDaily(query) {
return request({
url: '/report/productionDailyReport/dailyList',
method: 'get',
params: query
})
}
// 查询生产日报计划工单明细
export function listProductionDailyOrders(query) {
return request({
url: '/report/productionDailyReport/orderList',
method: 'get',
params: query
})
}
// 查询生产日报设备产出明细
export function listProductionDailyDevices(query) {
return request({
url: '/report/productionDailyReport/deviceList',
method: 'get',
params: query
})
}

@ -326,6 +326,20 @@ export const dynamicRoutes = [
},
],
},
{
path: '/report/productionDailyReport',
component: Layout,
hidden: true,
permissions: ['base:orderInfo:list'],
children: [
{
path: 'index',
component: () => import('@/views/report/productionDailyReport/index'),
name: 'ProductionDailyReport',
meta: { title: '生产日报', activeMenu: '/base/orderInfo/execution' }
}
]
},
{
path: "/base/bom-info",
component: Layout,

@ -0,0 +1,297 @@
<template>
<div class="app-container production-daily-report">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="80px">
<el-form-item label="报表类型">
<el-radio-group v-model="reportType" size="mini" @change="handleReportTypeChange">
<el-radio-button label="day">日报</el-radio-button>
<el-radio-button label="month">月报</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="reportType === 'day'" label="报表日期">
<el-date-picker
v-model="selectedDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item v-else label="报表月份">
<el-date-picker
v-model="selectedMonth"
type="month"
value-format="yyyy-MM"
placeholder="请选择月份"
style="width: 160px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
<el-button type="info" plain icon="el-icon-back" size="mini" @click="goBack"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="12" class="summary-row">
<el-col v-for="item in summaryCards" :key="item.label" :xs="12" :sm="8" :md="4">
<el-card shadow="never" class="summary-card">
<div class="summary-label">{{ item.label }}</div>
<div class="summary-value">{{ item.value }}</div>
</el-card>
</el-col>
</el-row>
<el-alert
title="实际产出口径取 DEVICE_DAILY_PRODUCTION 历史日汇总 + RT_DAILY_PROD_STATE 当天实时值;不依赖工单执行页人工及时操作,良品率按当前约定默认 100%。"
type="info"
:closable="false"
show-icon
class="report-tip"
/>
<el-tabs v-model="activeTab">
<el-tab-pane label="日汇总" name="daily">
<el-table v-loading="loading" :data="dailyList" border>
<el-table-column label="日期" prop="reportDate" width="120" align="center" />
<el-table-column label="计划工单数" prop="orderCount" width="110" align="center" />
<el-table-column label="计划数量" prop="planQty" width="120" align="right">
<template slot-scope="scope">{{ formatNumber(scope.row.planQty) }}</template>
</el-table-column>
<el-table-column label="实际产出" prop="actualQty" width="120" align="right">
<template slot-scope="scope">{{ formatNumber(scope.row.actualQty) }}</template>
</el-table-column>
<el-table-column label="良品数量" prop="goodQty" width="120" align="right">
<template slot-scope="scope">{{ formatNumber(scope.row.goodQty) }}</template>
</el-table-column>
<el-table-column label="不良数量" prop="defectQty" width="120" align="right">
<template slot-scope="scope">{{ formatNumber(scope.row.defectQty) }}</template>
</el-table-column>
<el-table-column label="完成率" prop="completionRate" width="100" align="right">
<template slot-scope="scope">{{ formatPercent(scope.row.completionRate) }}</template>
</el-table-column>
<el-table-column label="良品率" prop="goodRate" width="100" align="right">
<template slot-scope="scope">{{ formatPercent(scope.row.goodRate) }}</template>
</el-table-column>
<el-table-column label="产出机台数" prop="producedDeviceCount" min-width="110" align="center" />
</el-table>
</el-tab-pane>
<el-tab-pane label="计划工单" name="orders">
<el-table v-loading="orderLoading" :data="orderList" border>
<el-table-column label="工单编号" prop="orderCode" width="150" />
<el-table-column label="物料编码" prop="materialCode" width="150" />
<el-table-column label="物料名称" prop="materialName" min-width="220" show-overflow-tooltip />
<el-table-column label="工作中心" prop="workCenterCode" width="120" />
<el-table-column label="产线" prop="productLineName" width="140" show-overflow-tooltip />
<el-table-column label="计划数量" prop="planQty" width="120" align="right">
<template slot-scope="scope">{{ formatNumber(scope.row.planQty) }}</template>
</el-table-column>
<el-table-column label="开始日期" prop="beginDate" width="120" align="center" />
<el-table-column label="结束日期" prop="endDate" width="120" align="center" />
</el-table>
<pagination
v-show="orderTotal > 0"
:total="orderTotal"
:page.sync="orderQuery.pageNum"
:limit.sync="orderQuery.pageSize"
@pagination="getOrderList"
/>
</el-tab-pane>
<el-tab-pane label="设备产出" name="devices">
<el-table v-loading="deviceLoading" :data="deviceList" border>
<el-table-column label="设备编号" prop="deviceCode" width="160" />
<el-table-column label="设备名称" prop="deviceName" min-width="220" show-overflow-tooltip />
<el-table-column label="实际产出" prop="actualQty" width="140" align="right">
<template slot-scope="scope">{{ formatNumber(scope.row.actualQty) }}</template>
</el-table-column>
</el-table>
<pagination
v-show="deviceTotal > 0"
:total="deviceTotal"
:page.sync="deviceQuery.pageNum"
:limit.sync="deviceQuery.pageSize"
@pagination="getDeviceList"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { parseTime } from '@/utils/ruoyi'
import {
getProductionDailySummary,
listProductionDaily,
listProductionDailyDevices,
listProductionDailyOrders
} from '@/api/report/productionDailyReport'
export default {
name: 'ProductionDailyReport',
data() {
return {
reportType: 'day',
selectedDate: '',
selectedMonth: '',
activeTab: 'daily',
loading: false,
orderLoading: false,
deviceLoading: false,
summary: {},
dailyList: [],
orderList: [],
deviceList: [],
orderTotal: 0,
deviceTotal: 0,
queryParams: {
beginDate: '',
endDate: ''
},
orderQuery: {
pageNum: 1,
pageSize: 10
},
deviceQuery: {
pageNum: 1,
pageSize: 10
}
}
},
computed: {
summaryCards() {
return [
{ label: '计划数量', value: this.formatNumber(this.summary.planQty) },
{ label: '实际产出', value: this.formatNumber(this.summary.actualQty) },
{ label: '完成率', value: this.formatPercent(this.summary.completionRate) },
{ label: '良品率', value: this.formatPercent(this.summary.goodRate) },
{ label: '计划工单', value: this.summary.orderCount || 0 },
{ label: '产出机台', value: this.summary.producedDeviceCount || 0 }
]
}
},
created() {
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.selectedDate = today
this.selectedMonth = today.substring(0, 7)
this.handleQuery()
},
methods: {
handleReportTypeChange() {
this.handleQuery()
},
handleQuery() {
const range = this.buildDateRange()
if (!range) {
this.$modal.msgWarning('请选择报表日期')
return
}
this.queryParams.beginDate = range.beginDate
this.queryParams.endDate = range.endDate
this.orderQuery.pageNum = 1
this.deviceQuery.pageNum = 1
this.getSummary()
this.getDailyList()
this.getOrderList()
this.getDeviceList()
},
resetQuery() {
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.reportType = 'day'
this.selectedDate = today
this.selectedMonth = today.substring(0, 7)
this.handleQuery()
},
getSummary() {
getProductionDailySummary(this.queryParams).then(response => {
this.summary = response.data || {}
})
},
getDailyList() {
this.loading = true
listProductionDaily(this.queryParams).then(response => {
this.dailyList = response.data || []
}).finally(() => {
this.loading = false
})
},
getOrderList() {
this.orderLoading = true
listProductionDailyOrders({
...this.queryParams,
...this.orderQuery
}).then(response => {
this.orderList = response.rows || []
this.orderTotal = response.total || 0
}).finally(() => {
this.orderLoading = false
})
},
getDeviceList() {
this.deviceLoading = true
listProductionDailyDevices({
...this.queryParams,
...this.deviceQuery
}).then(response => {
this.deviceList = response.rows || []
this.deviceTotal = response.total || 0
}).finally(() => {
this.deviceLoading = false
})
},
buildDateRange() {
if (this.reportType === 'day') {
return this.selectedDate ? { beginDate: this.selectedDate, endDate: this.selectedDate } : null
}
if (!this.selectedMonth) {
return null
}
const parts = this.selectedMonth.split('-')
const year = Number(parts[0])
const month = Number(parts[1])
const lastDay = new Date(year, month, 0).getDate()
return {
beginDate: `${this.selectedMonth}-01`,
endDate: `${this.selectedMonth}-${String(lastDay).padStart(2, '0')}`
}
},
formatNumber(value) {
const numberValue = Number(value || 0)
return numberValue.toLocaleString('zh-CN', { maximumFractionDigits: 2 })
},
formatPercent(value) {
const numberValue = Number(value || 0)
return `${numberValue.toFixed(2)}%`
},
goBack() {
this.$router.back()
}
}
}
</script>
<style scoped>
.summary-row {
margin-bottom: 12px;
}
.summary-card {
margin-bottom: 12px;
}
.summary-label {
color: #606266;
font-size: 13px;
line-height: 20px;
}
.summary-value {
color: #303133;
font-size: 22px;
font-weight: 600;
line-height: 32px;
margin-top: 4px;
}
.report-tip {
margin-bottom: 12px;
}
</style>
Loading…
Cancel
Save