feat(dms、wms、mes、qms): 新增报表

master
zangch@mesnac.com 4 months ago
parent a13ab4f2f9
commit e4ffc8c590

@ -1,6 +1,7 @@
// hwmom-ui/src/api/dms/report/faultTrace.ts
import request from '@/utils/request';
import { FaultTraceQuery, FaultTraceVO } from './types';
import { RealtimeAlarmQuery, RealtimeAlarmVO } from './types';
export function listFaultTrace(query: FaultTraceQuery) {
@ -18,4 +19,21 @@ export function exportFaultTrace(query: FaultTraceQuery) {
data: query,
responseType: 'blob',
});
}
export function listRealtimeAlarm(query: RealtimeAlarmQuery) {
return request({
url: '/dms/report/realtimeAlarm/list',
method: 'get',
params: query,
});
}
export function exportRealtimeAlarm(query: RealtimeAlarmQuery) {
return request({
url: '/dms/report/realtimeAlarm/export',
method: 'post',
data: query,
responseType: 'blob',
});
}

@ -27,3 +27,33 @@ export interface FaultTraceQuery {
topResolutionTime: string | null;
faultType?: string | number;
}
export interface RealtimeAlarmQuery {
pageNum?: number;
pageSize?: number;
startDate?: string; // 'YYYY-MM-DD HH:mm:ss' or ISO
endDate?: string; // 'YYYY-MM-DD HH:mm:ss' or ISO
deviceModeId?: number;
machineCode?: string;
machineId?: number;
alarmLevelId?: number;
alarmTypeId?: number;
alarmStatus?: string; // 0未处理、1已处理、2已恢复
alarmMode?: string; // 1云端处理2终端上报
alarmInfoType?: string; // 1设备报警3离线报警
params?: Record<string, any>;
}
export interface RealtimeAlarmVO {
machineCode: string;
machineName: string;
deviceModeName: string;
alarmLevelName: string;
alarmTypeName: string;
alarmStatus: string; // 字典 alarm_status
alarmMode: string; // 字典 alarm_mode
alarmBeginTime: string;
alarmEndTime: string | null;
durationMinutes: string; // as string from backend
alarmContent: string;
}

@ -17,3 +17,12 @@ export const getDefectAnalysisReport = (params: { startTime: string, endTime: st
params
});
};
// 新增获取进料检验效率IQC报表
export const getIncomingInspectionEfficiency = (params: { startTime: string, endTime: string }) => {
return request({
url: '/qms/report/incoming',
method: 'get',
params
});
};

@ -19,9 +19,9 @@
<!-- <el-form-item label="设备模型" prop="deviceModeId">-->
<!-- <el-input v-model="queryParams.deviceModeId" placeholder="输入设备模型ID" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="设备编号" prop="machineCode">-->
<!-- <el-input v-model="queryParams.machineCode" placeholder="输入设备编号" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<el-form-item label="设备编号" prop="machineCode">
<el-input v-model="queryParams.machineCode" placeholder="输入设备编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<!-- <el-form-item label="故障类型" prop="faultType">
<el-select v-model="queryParams.faultType" placeholder="选择故障类型" clearable>
<el-option label="外部故障" value="1" />
@ -52,8 +52,11 @@
<el-table-column label="设备类型" prop="deviceType" min-width="100" />
<el-table-column label="设备编号" prop="machineCode" min-width="120" />
<el-table-column label="故障类型" prop="faultType" min-width="100" >
<!-- <template #default="scope">-->
<!-- <dict-tag :options="activity_fault_type" :value="scope.row.faultType"/>-->
<!-- </template>-->
<template #default="scope">
<dict-tag :options="activity_fault_type" :value="scope.row.faultType"/>
<span>{{ formatFaultType(scope.row.faultType) }}</span>
</template>
</el-table-column>
<el-table-column label="故障次数" prop="faultCount" min-width="90" align="right" />
@ -129,11 +132,11 @@ const debugFaultType = (faultType: any) => {
console.log('=== 前端字典调试 ===');
console.log('faultType原始值:', JSON.stringify(faultType), '类型:', typeof faultType);
console.log('字典选项:', activity_fault_type.value);
//
const matchedDict = activity_fault_type.value.find(item => item.value === faultType);
console.log('匹配的字典项:', matchedDict);
return ''; //
};
@ -151,7 +154,7 @@ const getList = async () => {
console.log('第一条记录完整数据:', list.value[0]);
}
} finally {
loading.value = false;
}
@ -175,6 +178,21 @@ const resetQuery = () => {
getList();
};
//
const formatFaultType = (faultType: any) => {
switch (faultType) {
case 1:
case '1':
return '外部故障';
case 2:
case '2':
return '内部故障';
default:
return '其他';
}
};
const handleExport = async () => {
proxy?.download('/dms/report/faultTrace/export', { ...queryParams }, `fault_trace_${new Date().getTime()}.xlsx`);
};

@ -0,0 +1,288 @@
<template>
<div class="mes-report-container realtime-alarm-report">
<!-- 查询条件卡片 -->
<el-card class="query-card" shadow="hover">
<template #header>
<div class="card-header">
<span>设备故障实时报警现场响应专用</span>
<div class="header-actions">
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="resetQuery" :disabled="loading">
<el-icon><Refresh /></el-icon>
重置
</el-button>
<el-button type="success" @click="handleExport" :disabled="loading || total===0">
<el-icon><Download /></el-icon>
导出Excel
</el-button>
</div>
</div>
</template>
<el-form :model="queryParams" label-width="110px" inline>
<el-form-item label="时间范围">
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled="loading"
/>
</el-form-item>
<!-- <el-form-item label="设备编号">
<el-input v-model="queryParams.machineCode" placeholder="支持模糊查询" clearable :disabled="loading" />
</el-form-item>
<el-form-item label="报警级别">
<el-select v-model="queryParams.alarmLevelId" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarmLevels" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="报警类型">
<el-select v-model="queryParams.alarmTypeId" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarmTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="报警状态">
<el-select v-model="queryParams.alarmStatus" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarm_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="报警方式">
<el-select v-model="queryParams.alarmMode" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="item in alarm_mode" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="设备型号">
<el-select v-model="queryParams.deviceModeId" clearable filterable placeholder="全部" :disabled="loading">
<el-option v-for="dm in deviceModeOptions" :key="dm.deviceModeId" :label="dm.deviceModeName" :value="dm.deviceModeId" />
</el-select>
</el-form-item>-->
</el-form>
</el-card>
<!-- 概览统计卡片 -->
<div class="overview-grid">
<el-card class="overview-card" shadow="never">
<div class="stat">
<div class="stat-value">{{ total }}</div>
<div class="stat-label">报警总数</div>
</div>
<div ref="pieRef" class="chart" />
</el-card>
<el-card class="overview-card" shadow="never">
<div ref="lineRef" class="chart" />
</el-card>
<el-card class="overview-card" shadow="never">
<div class="legend">
<div class="legend-item" v-for="lvl in alarmLevelStats" :key="lvl.name">
<span class="dot" :style="{ background: lvl.color }"></span>
<span class="name">{{ lvl.name }}</span>
<span class="value">{{ lvl.value }}</span>
</div>
</div>
</el-card>
</div>
<!-- 数据表格 -->
<el-card class="table-card" shadow="never">
<el-table v-loading="loading" :data="list" stripe height="480">
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="machineCode" label="设备编号" width="140" align="center" />
<el-table-column prop="machineName" label="设备名称" width="160" align="center" />
<el-table-column prop="deviceModeName" label="设备型号" width="140" align="center" />
<el-table-column prop="alarmBeginTime" label="故障发生时间" width="180" align="center" />
<el-table-column prop="alarmContent" label="故障现象描述" min-width="220" align="left" />
<el-table-column prop="alarmLevelName" label="故障等级" width="120" align="center" />
<el-table-column prop="alarmTypeName" label="报警类型" width="120" align="center" />
<el-table-column label="当前状态" width="120" align="center">
<template #default="scope">
<dict-tag :options="alarm_status" :value="scope.row.alarmStatus" />
</template>
</el-table-column>
<el-table-column label="报警方式" width="120" align="center">
<template #default="scope">
<dict-tag :options="alarm_mode" :value="scope.row.alarmMode" />
</template>
</el-table-column>
<el-table-column prop="alarmEndTime" label="结束时间" width="180" align="center" />
<el-table-column prop="durationMinutes" label="持续时长(分钟)" width="140" align="center" />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup lang="ts" name="RealtimeAlarmReport">
import { onMounted, ref, getCurrentInstance, nextTick, toRefs } from 'vue';
import * as echarts from 'echarts';
import { listRealtimeAlarm, exportRealtimeAlarm } from '@/api/dms/report';
import { RealtimeAlarmQuery, RealtimeAlarmVO } from '@/api/dms/report/types';
import { getDmsDeviceModeList } from '@/api/dms/deviceMode';
const { proxy } = getCurrentInstance() as any;
const loading = ref(false);
const list = ref<RealtimeAlarmVO[]>([]);
const total = ref(0);
const dateRange = ref<string[]>([]);
const queryParams = ref<RealtimeAlarmQuery>({
pageNum: 1,
pageSize: 10,
startDate: undefined,
endDate: undefined,
deviceModeId: undefined,
machineCode: undefined,
machineId: undefined,
alarmLevelId: undefined,
alarmTypeId: undefined,
alarmStatus: undefined,
alarmMode: undefined,
alarmInfoType: undefined,
});
//
const { alarm_status } = toRefs<any>(proxy?.useDict('alarm_status'));
const { alarm_mode } = toRefs<any>(proxy?.useDict('alarm_mode'));
//
const alarmLevels = ref<Array<{ label: string; value: string | number }>>([]);
const alarmTypes = ref<Array<{ label: string; value: string | number }>>([]);
//
const deviceModeOptions = ref<Array<{ deviceModeId: number; deviceModeName: string }>>([]);
//
const pieRef = ref<HTMLDivElement | null>(null);
const lineRef = ref<HTMLDivElement | null>(null);
let pieChart: echarts.ECharts | null = null;
let lineChart: echarts.ECharts | null = null;
function initCharts() {
nextTick(() => {
if (pieRef.value) {
pieChart = echarts.init(pieRef.value);
const levelGroup = groupBy(list.value, x => x.alarmLevelName || '未设定');
const pieData = Object.entries(levelGroup).map(([name, arr]) => ({ name, value: (arr as any[]).length }));
pieChart.setOption({
title: { text: '报警级别分布', left: 'center' },
tooltip: { trigger: 'item' },
series: [{ type: 'pie', radius: ['40%', '70%'], data: pieData }]
});
}
if (lineRef.value) {
lineChart = echarts.init(lineRef.value);
const byHour = groupBy(list.value, x => (x.alarmBeginTime || '').substring(0, 13));
const categories = Object.keys(byHour).sort();
const values = categories.map(k => (byHour[k] as any[]).length);
lineChart.setOption({
title: { text: '小时报警趋势', left: 'center' },
xAxis: { type: 'category', data: categories },
yAxis: { type: 'value' },
tooltip: { trigger: 'axis' },
series: [{ type: 'line', data: values, smooth: true }]
});
}
});
}
function groupBy<T>(arr: T[], keySelector: (x: T) => string) {
return arr.reduce((acc: any, cur: any) => {
const key = keySelector(cur) || '-';
acc[key] = acc[key] || [];
acc[key].push(cur);
return acc;
}, {} as Record<string, T[]>);
}
async function getDeviceModeOptions() {
const res = await getDmsDeviceModeList({});
deviceModeOptions.value = (res?.data || []).map((x: any) => ({ deviceModeId: x.deviceModeId, deviceModeName: x.deviceModeName }));
}
async function getList() {
loading.value = true;
try {
if (dateRange.value?.length === 2) {
queryParams.value.startDate = dateRange.value[0];
queryParams.value.endDate = dateRange.value[1];
} else {
queryParams.value.startDate = undefined;
queryParams.value.endDate = undefined;
}
const res = await listRealtimeAlarm(queryParams.value);
list.value = res.rows || [];
total.value = res.total || 0;
initCharts();
} finally {
loading.value = false;
}
}
function handleSearch() {
queryParams.value.pageNum = 1;
getList();
}
function resetQuery() {
dateRange.value = [];
queryParams.value = { pageNum: 1, pageSize: 10 };
getList();
}
function handleExport() {
proxy?.download('/dms/report/realtimeAlarm/export', { ...queryParams.value }, `realtime_alarm_${new Date().getTime()}.xlsx`);
}
onMounted(async () => {
await getDeviceModeOptions();
await getList();
});
</script>
<style scoped>
.realtime-alarm-report {
display: flex;
flex-direction: column;
gap: 12px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-actions {
display: flex;
gap: 8px;
}
.query-card {
border-radius: 8px;
}
.overview-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.overview-card {
height: 240px;
}
.chart {
width: 100%;
height: 180px;
}
.legend {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
}
.legend-item { display: flex; align-items: center; gap: 8px; }
.legend-item .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--el-color-primary); }
.stat { display: flex; align-items: baseline; gap: 12px; padding: 4px 12px; }
.stat-value { font-size: 28px; font-weight: 700; }
.stat-label { color: var(--el-text-color-secondary); }
.table-card { border-radius: 8px; }
</style>

@ -0,0 +1,223 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="mb-[10px]">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<div ref="chartRef" style="width: 100%; height: 360px"></div>
</el-card>
<el-card shadow="never">
<el-table v-loading="loading" :data="reportList">
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[0].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[1].visible" />
<el-table-column label="操作员" align="center" prop="operatorName" v-if="columns[2].visible" />
<el-table-column label="产品" align="center" prop="productName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="生产数量" align="center" prop="productionQuantity" v-if="columns[5].visible" />
<el-table-column label="合格数量" align="center" prop="qualifiedQuantity" v-if="columns[6].visible" />
<el-table-column label="不合格数量" align="center" prop="unqualifiedQuantity" v-if="columns[7].visible" />
<el-table-column label="合格率(%)" align="center" prop="qualifiedRate" v-if="columns[8].visible" />
<el-table-column label="开始时间" align="center" prop="startTime" v-if="columns[9].visible" />
<el-table-column label="结束时间" align="center" prop="endTime" v-if="columns[10].visible" />
<el-table-column label="作业时长(h)" align="center" prop="workHours" v-if="columns[11].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[12].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportTeamWork" lang="ts">
import { getCurrentInstance, ref, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import * as echarts from 'echarts';
import { listTeamWorkReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
const chartRef = ref<HTMLDivElement | null>(null);
let chart: echarts.ECharts | null = null;
//
const processInfoList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '班组', visible: true },
{ key: 1, label: '工位', visible: true },
{ key: 2, label: '操作员', visible: true },
{ key: 3, label: '产品', visible: true },
{ key: 4, label: '工序', visible: true },
{ key: 5, label: '生产数量', visible: true },
{ key: 6, label: '合格数量', visible: true },
{ key: 7, label: '不合格数量', visible: true },
{ key: 8, label: '合格率(%)', visible: true },
{ key: 9, label: '开始时间', visible: false },
{ key: 10, label: '结束时间', visible: false },
{ key: 11, label: '作业时长(h)', visible: false },
{ key: 12, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 18,
classTeamId: undefined,
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listTeamWorkReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
await nextTick();
renderChart();
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 18;
queryParams.value.classTeamId = undefined;
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/teamWorkReport/export', { ...queryParams.value }, `班组作业报表_${new Date().getTime()}.xlsx`);
};
//
const renderChart = () => {
if (!chartRef.value) return;
if (!chart) chart = echarts.init(chartRef.value);
const categories = (reportList.value || []).map((x: any) => x.teamName || '-');
const prodQty = (reportList.value || []).map((x: any) => Number(x.productionQuantity || 0));
const unqualQty = (reportList.value || []).map((x: any) => Number(x.unqualifiedQuantity || 0));
const qualRate = (reportList.value || []).map((x: any) => Number(x.qualifiedRate || 0));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['生产数量', '不合格数量', '合格率(%)'] },
grid: { left: 40, right: 40, top: 40, bottom: 40 },
xAxis: { type: 'category', data: categories, axisLabel: { interval: 0, rotate: 30 } },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '合格率(%)', min: 0, max: 100 }
],
series: [
{ name: '生产数量', type: 'bar', data: prodQty, itemStyle: { color: '#5470C6' }, barMaxWidth: 30 },
{ name: '不合格数量', type: 'bar', data: unqualQty, itemStyle: { color: '#EE6666' }, barMaxWidth: 30 },
{ name: '合格率(%)', type: 'line', yAxisIndex: 1, data: qualRate, smooth: true, itemStyle: { color: '#91CC75' } }
]
};
chart.setOption(option);
chart.resize();
};
const resizeHandler = () => {
chart?.resize();
};
window.addEventListener('resize', resizeHandler);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getClassTeamSelect();
await getList();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
chart?.dispose();
chart = null;
});
</script>
<style scoped>
</style>

@ -0,0 +1,223 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="mb-[10px]">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<div ref="chartRef" style="width: 100%; height: 360px"></div>
</el-card>
<el-card shadow="never">
<el-table v-loading="loading" :data="reportList">
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[0].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[1].visible" />
<el-table-column label="操作员" align="center" prop="operatorName" v-if="columns[2].visible" />
<el-table-column label="产品" align="center" prop="productName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="生产数量" align="center" prop="productionQuantity" v-if="columns[5].visible" />
<el-table-column label="合格数量" align="center" prop="qualifiedQuantity" v-if="columns[6].visible" />
<el-table-column label="不合格数量" align="center" prop="unqualifiedQuantity" v-if="columns[7].visible" />
<el-table-column label="合格率(%)" align="center" prop="qualifiedRate" v-if="columns[8].visible" />
<el-table-column label="开始时间" align="center" prop="startTime" v-if="columns[9].visible" />
<el-table-column label="结束时间" align="center" prop="endTime" v-if="columns[10].visible" />
<el-table-column label="作业时长(h)" align="center" prop="workHours" v-if="columns[11].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[12].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportTeamWork" lang="ts">
import { getCurrentInstance, ref, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import * as echarts from 'echarts';
import { listTeamWorkReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
const chartRef = ref<HTMLDivElement | null>(null);
let chart: echarts.ECharts | null = null;
//
const processInfoList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '班组', visible: true },
{ key: 1, label: '工位', visible: true },
{ key: 2, label: '操作员', visible: true },
{ key: 3, label: '产品', visible: true },
{ key: 4, label: '工序', visible: true },
{ key: 5, label: '生产数量', visible: true },
{ key: 6, label: '合格数量', visible: true },
{ key: 7, label: '不合格数量', visible: true },
{ key: 8, label: '合格率(%)', visible: true },
{ key: 9, label: '开始时间', visible: false },
{ key: 10, label: '结束时间', visible: false },
{ key: 11, label: '作业时长(h)', visible: false },
{ key: 12, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 17,
classTeamId: undefined,
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listTeamWorkReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
await nextTick();
renderChart();
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 17;
queryParams.value.classTeamId = undefined;
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/teamWorkReport/export', { ...queryParams.value }, `班组作业报表_${new Date().getTime()}.xlsx`);
};
//
const renderChart = () => {
if (!chartRef.value) return;
if (!chart) chart = echarts.init(chartRef.value);
const categories = (reportList.value || []).map((x: any) => x.teamName || '-');
const prodQty = (reportList.value || []).map((x: any) => Number(x.productionQuantity || 0));
const unqualQty = (reportList.value || []).map((x: any) => Number(x.unqualifiedQuantity || 0));
const qualRate = (reportList.value || []).map((x: any) => Number(x.qualifiedRate || 0));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['生产数量', '不合格数量', '合格率(%)'] },
grid: { left: 40, right: 40, top: 40, bottom: 40 },
xAxis: { type: 'category', data: categories, axisLabel: { interval: 0, rotate: 30 } },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '合格率(%)', min: 0, max: 100 }
],
series: [
{ name: '生产数量', type: 'bar', data: prodQty, itemStyle: { color: '#5470C6' }, barMaxWidth: 30 },
{ name: '不合格数量', type: 'bar', data: unqualQty, itemStyle: { color: '#EE6666' }, barMaxWidth: 30 },
{ name: '合格率(%)', type: 'line', yAxisIndex: 1, data: qualRate, smooth: true, itemStyle: { color: '#91CC75' } }
]
};
chart.setOption(option);
chart.resize();
};
const resizeHandler = () => {
chart?.resize();
};
window.addEventListener('resize', resizeHandler);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getClassTeamSelect();
await getList();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
chart?.dispose();
chart = null;
});
</script>
<style scoped>
</style>

@ -0,0 +1,223 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="mb-[10px]">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<div ref="chartRef" style="width: 100%; height: 360px"></div>
</el-card>
<el-card shadow="never">
<el-table v-loading="loading" :data="reportList">
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[0].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[1].visible" />
<el-table-column label="操作员" align="center" prop="operatorName" v-if="columns[2].visible" />
<el-table-column label="产品" align="center" prop="productName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="生产数量" align="center" prop="productionQuantity" v-if="columns[5].visible" />
<el-table-column label="合格数量" align="center" prop="qualifiedQuantity" v-if="columns[6].visible" />
<el-table-column label="不合格数量" align="center" prop="unqualifiedQuantity" v-if="columns[7].visible" />
<el-table-column label="合格率(%)" align="center" prop="qualifiedRate" v-if="columns[8].visible" />
<el-table-column label="开始时间" align="center" prop="startTime" v-if="columns[9].visible" />
<el-table-column label="结束时间" align="center" prop="endTime" v-if="columns[10].visible" />
<el-table-column label="作业时长(h)" align="center" prop="workHours" v-if="columns[11].visible" />
<el-table-column label="备注" align="center" prop="remark" v-if="columns[12].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportTeamWork" lang="ts">
import { getCurrentInstance, ref, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import * as echarts from 'echarts';
import { listTeamWorkReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
const chartRef = ref<HTMLDivElement | null>(null);
let chart: echarts.ECharts | null = null;
//
const processInfoList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '班组', visible: true },
{ key: 1, label: '工位', visible: true },
{ key: 2, label: '操作员', visible: true },
{ key: 3, label: '产品', visible: true },
{ key: 4, label: '工序', visible: true },
{ key: 5, label: '生产数量', visible: true },
{ key: 6, label: '合格数量', visible: true },
{ key: 7, label: '不合格数量', visible: true },
{ key: 8, label: '合格率(%)', visible: true },
{ key: 9, label: '开始时间', visible: false },
{ key: 10, label: '结束时间', visible: false },
{ key: 11, label: '作业时长(h)', visible: false },
{ key: 12, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 2,
classTeamId: undefined,
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listTeamWorkReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
await nextTick();
renderChart();
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 2;
queryParams.value.classTeamId = undefined;
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/teamWorkReport/export', { ...queryParams.value }, `班组作业报表_${new Date().getTime()}.xlsx`);
};
//
const renderChart = () => {
if (!chartRef.value) return;
if (!chart) chart = echarts.init(chartRef.value);
const categories = (reportList.value || []).map((x: any) => x.teamName || '-');
const prodQty = (reportList.value || []).map((x: any) => Number(x.productionQuantity || 0));
const unqualQty = (reportList.value || []).map((x: any) => Number(x.unqualifiedQuantity || 0));
const qualRate = (reportList.value || []).map((x: any) => Number(x.qualifiedRate || 0));
const option: echarts.EChartsOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['生产数量', '不合格数量', '合格率(%)'] },
grid: { left: 40, right: 40, top: 40, bottom: 40 },
xAxis: { type: 'category', data: categories, axisLabel: { interval: 0, rotate: 30 } },
yAxis: [
{ type: 'value', name: '数量' },
{ type: 'value', name: '合格率(%)', min: 0, max: 100 }
],
series: [
{ name: '生产数量', type: 'bar', data: prodQty, itemStyle: { color: '#5470C6' }, barMaxWidth: 30 },
{ name: '不合格数量', type: 'bar', data: unqualQty, itemStyle: { color: '#EE6666' }, barMaxWidth: 30 },
{ name: '合格率(%)', type: 'line', yAxisIndex: 1, data: qualRate, smooth: true, itemStyle: { color: '#91CC75' } }
]
};
chart.setOption(option);
chart.resize();
};
const resizeHandler = () => {
chart?.resize();
};
window.addEventListener('resize', resizeHandler);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getClassTeamSelect();
await getList();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
chart?.dispose();
chart = null;
});
</script>
<style scoped>
</style>

@ -76,7 +76,11 @@
<el-table-column label="标准工时(h)" align="center" prop="standardWorkHour" v-if="columns[6].visible" />
<el-table-column label="报工工时(h)" align="center" prop="reportWorkHour" v-if="columns[7].visible" />
<el-table-column label="生产效率" align="center" prop="productionEfficiency" v-if="columns[8].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" >
<template #default="scope">
<dict-tag :options="mes_plan_status" :value="scope.row.planStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" show-overflow-tooltip />
</el-table>
@ -96,6 +100,14 @@ import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const reportList = ref<[]>([]);
const loading = ref(true);

@ -0,0 +1,232 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="机台名称" prop="machineId">
<el-select v-model="queryParams.machineId" placeholder="请选择机台名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in machineInfoList" :key="item.machineId" :label="item.machineName" :value="item.machineId" />
</el-select>
</el-form-item>
<el-form-item label="班次名称" prop="shiftId">
<el-select v-model="queryParams.shiftId" placeholder="请选择班次名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in shiftList" :key="item.shiftId" :label="item.shiftName" :value="item.shiftId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
<el-form-item label="计划编号" prop="planCode">
<el-input v-model="queryParams.planCode" placeholder="请输入计划编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" :data="reportList">
<el-table-column label="派工单" align="center" prop="dispatchCode" v-if="columns[0].visible" />
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[1].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[2].visible" />
<el-table-column label="物料名称" align="center" prop="materialName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序名称" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="报工数量" align="center" prop="completeAmount" v-if="columns[5].visible" />
<el-table-column label="标准工时(h)" align="center" prop="standardWorkHour" v-if="columns[6].visible" />
<el-table-column label="报工工时(h)" align="center" prop="reportWorkHour" v-if="columns[7].visible" />
<el-table-column label="生产效率" align="center" prop="productionEfficiency" v-if="columns[8].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" >
<template #default="scope">
<dict-tag :options="mes_plan_status" :value="scope.row.planStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportWorkHour" lang="ts">
import { getCurrentInstance, ref, watch, onMounted } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import { listWorkHourReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getProdBaseMachineInfoList } from '@/api/mes/prodBaseMachineInfo';
import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
//
const processInfoList = ref([]);
const machineInfoList = ref([]);
const shiftList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '派工单', visible: true },
{ key: 1, label: '班组', visible: true },
{ key: 2, label: '工位', visible: true },
{ key: 3, label: '物料名称', visible: true },
{ key: 4, label: '工序名称', visible: true },
{ key: 5, label: '报工数量', visible: true },
{ key: 6, label: '标准工时(h)', visible: true },
{ key: 7, label: '报工工时(h)', visible: true },
{ key: 8, label: '生产效率', visible: true },
{ key: 9, label: '派工单状态', visible: true },
{ key: 10, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 17,
machineId: undefined,
shiftId: undefined,
classTeamId: undefined,
planCode: '',
materialName: '',
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getProdBaseMachineInfoListSelect = async () => {
const res = await getProdBaseMachineInfoList({ processId: queryParams.value.processId });
machineInfoList.value = res.data;
};
const getShiftSelect = async () => {
const res = await getBaseShiftInfoList(null);
shiftList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listWorkHourReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 17;
queryParams.value.machineId = undefined;
queryParams.value.shiftId = undefined;
queryParams.value.classTeamId = undefined;
queryParams.value.planCode = '';
queryParams.value.materialName = '';
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/workHourReport/export', { ...queryParams.value }, `工时报表_${new Date().getTime()}.xlsx`);
};
//
watch(
() => queryParams.value.processId,
async () => {
loading.value = true;
await getProdBaseMachineInfoListSelect();
await getList();
loading.value = false;
}
);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getProdBaseMachineInfoListSelect();
await getShiftSelect();
await getClassTeamSelect();
await getList();
});
</script>

@ -0,0 +1,232 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="日期" style="width: 300px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<!--
<el-form-item label="工序名称" prop="processId">
<el-select v-model="queryParams.processId" placeholder="请选择所属工序" @keyup.enter="handleQuery">
<el-option v-for="item in processInfoList" :key="item.processId" :label="item.processName" :value="item.processId" />
</el-select>
</el-form-item>
<el-form-item label="机台名称" prop="machineId">
<el-select v-model="queryParams.machineId" placeholder="请选择机台名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in machineInfoList" :key="item.machineId" :label="item.machineName" :value="item.machineId" />
</el-select>
</el-form-item>
<el-form-item label="班次名称" prop="shiftId">
<el-select v-model="queryParams.shiftId" placeholder="请选择班次名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in shiftList" :key="item.shiftId" :label="item.shiftName" :value="item.shiftId" />
</el-select>
</el-form-item>
<el-form-item label="班组名称" prop="classTeamId">
<el-select v-model="queryParams.classTeamId" placeholder="请选择班组名称" clearable @keyup.enter="handleQuery">
<el-option v-for="item in classTeamList" :key="item.classTeamId" :label="item.teamName" :value="item.classTeamId" />
</el-select>
</el-form-item>
<el-form-item label="计划编号" prop="planCode">
<el-input v-model="queryParams.planCode" placeholder="请输入计划编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:baseWorkshopInfo:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" :data="reportList">
<el-table-column label="派工单" align="center" prop="dispatchCode" v-if="columns[0].visible" />
<el-table-column label="班组" align="center" prop="teamName" v-if="columns[1].visible" />
<el-table-column label="工位" align="center" prop="stationName" v-if="columns[2].visible" />
<el-table-column label="物料名称" align="center" prop="materialName" v-if="columns[3].visible" width="260" />
<el-table-column label="工序名称" align="center" prop="processName" v-if="columns[4].visible" />
<el-table-column label="报工数量" align="center" prop="completeAmount" v-if="columns[5].visible" />
<el-table-column label="标准工时(h)" align="center" prop="standardWorkHour" v-if="columns[6].visible" />
<el-table-column label="报工工时(h)" align="center" prop="reportWorkHour" v-if="columns[7].visible" />
<el-table-column label="生产效率" align="center" prop="productionEfficiency" v-if="columns[8].visible" />
<el-table-column label="派工单状态" align="center" prop="planStatus" v-if="columns[9].visible" >
<template #default="scope">
<dict-tag :options="mes_plan_status" :value="scope.row.planStatus"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" show-overflow-tooltip />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportWorkHour" lang="ts">
import { getCurrentInstance, ref, watch, onMounted } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import type { ElFormInstance } from 'element-plus';
import { listWorkHourReport } from '@/api/mes/prodReport';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getProdBaseMachineInfoList } from '@/api/mes/prodBaseMachineInfo';
import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const {
mes_import_flag,
active_flag,
mes_plan_status,
mes_release_type,
mes_finish_flag,
mes_model_code
} = toRefs<any>(proxy?.useDict('mes_import_flag', 'active_flag', 'mes_plan_status', 'mes_release_type', 'mes_finish_flag', 'mes_model_code'));
const reportList = ref<[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const dateRange = ref<string[]>(['', '']);
//
const processInfoList = ref([]);
const machineInfoList = ref([]);
const shiftList = ref([]);
const classTeamList = ref([]);
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '派工单', visible: true },
{ key: 1, label: '班组', visible: true },
{ key: 2, label: '工位', visible: true },
{ key: 3, label: '物料名称', visible: true },
{ key: 4, label: '工序名称', visible: true },
{ key: 5, label: '报工数量', visible: true },
{ key: 6, label: '标准工时(h)', visible: true },
{ key: 7, label: '报工工时(h)', visible: true },
{ key: 8, label: '生产效率', visible: true },
{ key: 9, label: '派工单状态', visible: true },
{ key: 10, label: '备注', visible: true }
]);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
processId: 2,
machineId: undefined,
shiftId: undefined,
classTeamId: undefined,
planCode: '',
materialName: '',
beginDate: '',
endDate: '',
params: {}
});
//
const getProcessInfoListSelect = async () => {
const res = await getProcessInfoList(null);
processInfoList.value = res.data;
};
const getProdBaseMachineInfoListSelect = async () => {
const res = await getProdBaseMachineInfoList({ processId: queryParams.value.processId });
machineInfoList.value = res.data;
};
const getShiftSelect = async () => {
const res = await getBaseShiftInfoList(null);
shiftList.value = res.data;
};
const getClassTeamSelect = async () => {
const res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
//
const getList = async () => {
loading.value = true;
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
const res = await listWorkHourReport(queryParams.value);
reportList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
};
//
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
//
const resetQuery = () => {
queryFormRef.value?.resetFields?.();
const nowDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
dateRange.value = [nowDate, nowDate];
queryParams.value.processId = 2;
queryParams.value.machineId = undefined;
queryParams.value.shiftId = undefined;
queryParams.value.classTeamId = undefined;
queryParams.value.planCode = '';
queryParams.value.materialName = '';
handleQuery();
};
//
const handleExport = () => {
queryParams.value.beginDate = dateRange.value?.[0];
queryParams.value.endDate = dateRange.value?.[1];
proxy?.download('mes/prodReport/workHourReport/export', { ...queryParams.value }, `工时报表_${new Date().getTime()}.xlsx`);
};
//
watch(
() => queryParams.value.processId,
async () => {
loading.value = true;
await getProdBaseMachineInfoListSelect();
await getList();
loading.value = false;
}
);
onMounted(async () => {
resetQuery();
await getProcessInfoListSelect();
await getProdBaseMachineInfoListSelect();
await getShiftSelect();
await getClassTeamSelect();
await getList();
});
</script>

@ -89,9 +89,9 @@
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:prodReport:export']"></el-button>
</el-col>
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['mes:prodReport:export']"></el-button>-->
<!-- </el-col>-->
<right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @queryTable="getList" />
</el-row>
</template>
@ -172,7 +172,7 @@ let processChart: echarts.ECharts | null = null;
//
const columns = ref([
{ key: 0, label: '生产订单号', visible: true },
{ key: 1, label: '物料编号', visible: true },
{ key: 1, label: '物料编号', visible: false },
{ key: 2, label: '物料名称', visible: true },
{ key: 3, label: '规格型号', visible: true },
{ key: 4, label: '计划总数量', visible: true },
@ -405,7 +405,12 @@ function updateProcessChart() {
trigger: 'axis',
formatter: function(params: any) {
const data = params[0].data.value;
return `订单号: ${data[0]}<br/>
// return `: ${data[0]}<br/>
// : ${data[1]}%<br/>
// : ${data[2]}<br/>
// : ${data[3]}<br/>
// : ${data[4]}`;
return `
进度: ${data[1]}%<br/>
状态: ${data[2]}<br/>
在制工序: ${data[3]}<br/>

@ -0,0 +1,133 @@
<template>
<div class="p-4">
<el-card shadow="hover" class="mb-4">
<el-form :inline="true" :model="queryParams">
<el-form-item label="日期范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
@change="handleDateChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" v-loading="loading">
<template #header>
<div class="text-center text-xl font-bold">进料检验报表</div>
</template>
<el-table :data="tableData" border style="width: 100%" :height="tableHeight">
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column prop="inspectionNo" label="检验单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="purchaseOrderNo" label="采购单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="inspectionDate" label="检验日期" width="120" />
<el-table-column prop="inspectionStartTime" label="开始时间" width="160" />
<el-table-column prop="inspectionEndTime" label="结束时间" width="160" />
<el-table-column prop="materialCode" label="物料编码" min-width="120" show-overflow-tooltip />
<el-table-column prop="materialName" label="物料名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="materialSpec" label="规格型号" min-width="140" show-overflow-tooltip />
<el-table-column prop="materialUnit" label="单位" width="80" />
<el-table-column prop="supplierName" label="供应商" min-width="160" show-overflow-tooltip />
<el-table-column prop="inspectionQty" label="检验数量" width="110" align="right" />
<el-table-column prop="qualifiedQty" label="合格数" width="100" align="right" />
<el-table-column prop="unqualifiedQty" label="不合格数" width="110" align="right" />
<el-table-column prop="inspectionResult" label="检验结果" width="100" />
<el-table-column prop="inspectionTypeName" label="检验类型" width="120" />
<el-table-column prop="inspectorName" label="检验人" width="100" />
<el-table-column prop="items" label="检验项目" min-width="180" show-overflow-tooltip />
<el-table-column prop="methods" label="检验方法" min-width="180" show-overflow-tooltip />
<el-table-column prop="unqualifiedItems" label="不合格项" min-width="200" show-overflow-tooltip />
<el-table-column prop="returnReason" label="退货原因" min-width="160" show-overflow-tooltip />
<el-table-column prop="unqualifiedSummary" label="不合格汇总" min-width="220" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, onBeforeUnmount } from 'vue';
import dayjs from 'dayjs';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import { getIncomingInspectionEfficiency } from '@/api/qms/report';
dayjs.extend(weekOfYear);
//
const loading = ref(false);
const dateRange = ref<[string, string] | null>(null);
const queryParams = ref({ startTime: '', endTime: '' });
const tableData = ref<any[]>([]);
//
const windowHeight = ref(window.innerHeight);
const tableHeight = computed(() => {
return windowHeight.value - 280; //
});
const resizeHandler = () => {
windowHeight.value = window.innerHeight;
};
//
const handleDateChange = (val: any) => {
if (val && Array.isArray(val)) {
queryParams.value.startTime = val[0];
queryParams.value.endTime = val[1];
} else {
queryParams.value.startTime = '';
queryParams.value.endTime = '';
}
};
const resetQuery = () => {
//
const start = dayjs().startOf('week').format('YYYY-MM-DD');
const end = dayjs().endOf('week').format('YYYY-MM-DD');
dateRange.value = [start, end] as [string, string];
handleDateChange(dateRange.value);
handleQuery();
};
const handleQuery = async () => {
loading.value = true;
try {
const res: any = await getIncomingInspectionEfficiency(queryParams.value);
if (res && res.code === 200) {
tableData.value = res.data || [];
} else {
tableData.value = [];
}
} catch (e) {
console.error('获取进料检验效率报表失败', e);
tableData.value = [];
} finally {
loading.value = false;
}
};
onMounted(() => {
//
resetQuery();
window.addEventListener('resize', resizeHandler);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
});
</script>
<style scoped>
.el-form-item {
margin-bottom: 0;
}
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -166,10 +159,10 @@ const getList = async () => {
const res = await getInventoryDifference(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateCharts();
@ -182,8 +175,8 @@ const calculateStats = () => {
noDifference: reportList.value.filter(item => item.differenceType === '无差异').length,
profit: reportList.value.filter(item => item.differenceType === '盘盈').length,
loss: reportList.value.filter(item => item.differenceType === '盘亏').length,
avgRate: reportList.value.length > 0
? reportList.value.reduce((sum, item) => sum + (item.differenceRate || 0), 0) / reportList.value.length
avgRate: reportList.value.length > 0
? reportList.value.reduce((sum, item) => sum + (item.differenceRate || 0), 0) / reportList.value.length
: 0
};
};
@ -384,4 +377,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -102,7 +95,7 @@ const getList = async () => {
const res = await getInventoryTrendAnalysis(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
nextTick(() => {
updateChart();
@ -243,4 +236,4 @@ onUnmounted(() => {
.el-card {
margin-bottom: 20px;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -156,10 +149,10 @@ const getList = async () => {
const res = await getInventoryTurnover(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateCharts();
@ -365,4 +358,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -105,7 +98,7 @@ const getList = async () => {
const res = await getReturnReasonAnalysis(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
nextTick(() => {
updateCharts();
@ -255,4 +248,4 @@ onUnmounted(() => {
.el-card {
margin-bottom: 20px;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -87,7 +80,7 @@
<el-table-column label="物料名称" align="center" prop="materialName" show-overflow-tooltip />
<el-table-column label="物料大类" align="center" prop="materialCategoryName" />
<el-table-column label="当前库存数量" align="center" prop="currentInventoryQty" />
<el-table-column label="安全库存值" align="center" prop="safeStockAmount" />
<!-- <el-table-column label="安全库存值" align="center" prop="safeStockAmount" />-->
<el-table-column label="最小库存值" align="center" prop="minStockAmount" />
<el-table-column label="最大库存值" align="center" prop="maxStockAmount" />
<el-table-column label="预警状态" align="center" prop="alertStatus">
@ -146,10 +139,10 @@ const getList = async () => {
const res = await getSafetyStockAlert(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateChart();
@ -318,4 +311,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

@ -5,17 +5,10 @@
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物料大类" prop="materialCategoryId">
<el-select v-model="queryParams.materialCategoryId" placeholder="请选择物料大类" clearable>
<el-option v-for="item in materialCategoryOptions"
:key="item.materialCategoryId"
:label="item.materialCategoryName"
:value="item.materialCategoryId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<!-- <el-button icon="Refresh" @click="resetQuery"></el-button>-->
</el-form-item>
</el-form>
</el-card>
@ -147,10 +140,10 @@ const getList = async () => {
const res = await getStagnantInventory(queryParams.value);
reportList.value = res.data;
loading.value = false;
//
calculateStats();
//
nextTick(() => {
updateCharts();
@ -356,4 +349,4 @@ onUnmounted(() => {
font-size: 0.9rem;
color: #666;
}
</style>
</style>

Loading…
Cancel
Save