You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

534 lines
14 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class='current-task'>
<div v-if='!currentWorkOrder' class='empty-state'>
<el-empty description='暂无当前工单' />
</div>
<div v-else class='task-content'>
<!-- 左侧工单任务详情 -->
<div class='task-details'>
<div class='details-header'>
<h3>工单任务详情</h3>
</div>
<div class='details-body'>
<div class='detail-item'>
<span class='label'>工单号:</span>
<span class='value plan-code'>{{ currentWorkOrder.planCode }}</span>
</div>
<div class='detail-item'>
<span class='label'>任务状态:</span>
<span class='value status-text'>{{ getStatusText(currentWorkOrder.planStatus) }}</span>
</div>
<div class='detail-item'>
<span class='label'>产品编码:</span>
<span class='value'>{{ (currentWorkOrder as any).materialCode || currentWorkOrder.materialId }}</span>
</div>
<div class='detail-item'>
<span class='label'>工序名称:</span>
<span class='value'>{{ (currentWorkOrder as any).processName || '-' }}</span>
</div>
<div class='detail-item'>
<span class='label'>计划数量:</span>
<span class='value'>{{ currentWorkOrder.planAmount || 0 }}</span>
</div>
<div class='detail-item'>
<span class='label'>完成数量:</span>
<span class='value'>{{ currentWorkOrder.completeAmount || 0 }}</span>
</div>
<div class='detail-item'>
<span class='label'>产品名称:</span>
<span class='value'>{{ currentWorkOrder.materialName || '-' }}</span>
</div>
<div class='detail-item'>
<span class='label'>可报工量:</span>
<span class='value'>{{ reportableQuantity }}</span>
</div>
<div class='detail-item'>
<span class='label'>排产日期:</span>
<span class='value'>{{ parseTime(currentWorkOrder.planBeginTime, '{y}-{m}-{d}') }}</span>
</div>
</div>
</div>
<!-- 右侧报工采集 -->
<div class='report-form'>
<div class='form-header'>
<h3>报工采集</h3>
</div>
<div class='form-body'>
<el-form :model='reportForm' label-width='100px'>
<el-form-item label='报工数量:'>
<el-input-number
v-model='reportForm.reportQuantity'
:min='0'
:max='reportableQuantity'
:precision='0'
style='width: 100%'
placeholder='请输入报工数量'
/>
</el-form-item>
<el-form-item label='不良数量:'>
<el-input-number
v-model='reportForm.defectiveQuantity'
:min='0'
:precision='0'
style='width: 100%'
placeholder='请输入不良数量'
/>
</el-form-item>
</el-form>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class='action-buttons'>
<el-button
type='primary'
:disabled='!currentWorkOrder || currentWorkOrder.planStatus === "2"'
@click='handleStartWork'
>
开工
</el-button>
<el-button
type='primary'
:disabled='!currentWorkOrder || currentWorkOrder.planStatus !== "2"'
@click='handleStopWork'
>
停工
</el-button>
<el-button
type='primary'
:disabled='!currentWorkOrder || currentWorkOrder.planStatus === "2"'
@click='handleResumeWork'
>
复工
</el-button>
<el-button
type='primary'
:disabled='!currentWorkOrder || !reportForm.reportQuantity'
@click='handleReportWork'
>
报工
</el-button>
<el-button
type='primary'
:disabled='!currentWorkOrder || hasPendingInspection'
@click='handleFirstInspection'
>
首检
</el-button>
</div>
</div>
</template>
<script setup name='CurrentTask' lang='ts'>
import { ref, computed, onMounted, getCurrentInstance } from 'vue';
import { listPlanInfo, updatePlanInfo, stopPlanInfo, resumePlanInfo, reportPlanInfo, generateInspectionTask } from '@/api/mes/planInfo';
import { PlanInfoVO, PlanInfoQuery } from '@/api/mes/planInfo/types';
import { getProdProductPlanDetailList, listProductPlanDetail } from '@/api/mes/productPlanDetail';
import { ProductPlanDetailQuery } from '@/api/mes/productPlanDetail/types';
import { listQcInspectionMain } from '@/api/qms/QcInspectionMain';
import { QcInspectionMainQuery } from '@/api/qms/QcInspectionMain/types';
import { parseTime } from '@/utils/ruoyi';
const { proxy } = getCurrentInstance() as any;
const props = defineProps<{
workstationId?: string | number;
}>();
const emit = defineEmits<{
reportSuccess: [];
}>();
const loading = ref(false);
const currentWorkOrder = ref<PlanInfoVO | null>(null);
const hasPendingInspection = ref(false); // 是否有未处理的首检任务
const reportForm = ref({
reportQuantity: 0,
defectiveQuantity: 0
});
// 可报工量 = 计划数量 - 完成数量
const reportableQuantity = computed(() => {
if (!currentWorkOrder.value) return 0;
const planAmount = currentWorkOrder.value.planAmount || 0;
const completeAmount = currentWorkOrder.value.completeAmount || 0;
return Math.max(0, planAmount - completeAmount);
});
// 获取状态文本
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'0': '未派工',
'1': '已派工',
'2': '已开始',
'3': '已完成'
};
return statusMap[status] || '未知';
};
// 检查首检任务状态
const checkInspectionTask = async (planCode: string, planDetailId: string | number) => {
try {
const query: QcInspectionMainQuery = {
pageNum: 1,
pageSize: 1,
inspectionType: 0, // 首检
status: 0, // 未处理
planDetailId: planDetailId
};
const res = await listQcInspectionMain(query);
const inspectionList = res.rows || [];
// 检查是否有未处理的首检任务
// 如果有未处理的首检任务,则禁用按钮
hasPendingInspection.value = inspectionList.length > 0;
} catch (error) {
console.error('查询首检任务状态失败:', error);
hasPendingInspection.value = false;
}
};
// 获取当前工单
const getCurrentWorkOrder = async () => {
loading.value = true;
try {
const query: any = {
pageNum: 1,
pageSize: 1,
planStatus: '2', // 已开始状态
releaseType: '3' // 工位类型
};
if (props.workstationId) {
query.releaseId = props.workstationId;
}
const res = await listPlanInfo(query as PlanInfoQuery);
if (res.rows && res.rows.length > 0) {
currentWorkOrder.value = res.rows[0];
// 获取生产信息明细,用于检查首检任务
const detailRes = await getProdProductPlanDetailList({ planId: currentWorkOrder.value.planId });
const detailList = detailRes.data || [];
if (detailList && detailList.length > 0) {
const firstDetail = detailList[detailList.length - 1];
// 检查首检任务状态
await checkInspectionTask(currentWorkOrder.value.planCode, firstDetail.planDetailId);
} else {
hasPendingInspection.value = false;
}
} else {
currentWorkOrder.value = null;
hasPendingInspection.value = false;
}
} catch (error) {
console.error('获取当前工单失败:', error);
currentWorkOrder.value = null;
hasPendingInspection.value = false;
} finally {
loading.value = false;
}
};
// 开工
const handleStartWork = async () => {
if (!currentWorkOrder.value) return;
try {
await proxy?.$modal.confirm('确认开工?');
loading.value = true;
const updateData = {
...currentWorkOrder.value,
planStatus: '2', // 已开始
realBeginTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
};
await updatePlanInfo(updateData);
proxy?.$modal.msgSuccess('开工成功');
await getCurrentWorkOrder();
} catch (error: any) {
if (error !== 'cancel') {
proxy?.$modal.msgError('开工失败');
console.error('开工失败:', error);
}
} finally {
loading.value = false;
}
};
// 停工
const handleStopWork = async () => {
if (!currentWorkOrder.value) return;
try {
await proxy?.$modal.confirm('确认停工?');
loading.value = true;
// 调用停工接口
await stopPlanInfo(currentWorkOrder.value.planId);
proxy?.$modal.msgSuccess('停工成功');
await getCurrentWorkOrder();
} catch (error: any) {
if (error !== 'cancel') {
proxy?.$modal.msgError('停工失败');
console.error('停工失败:', error);
}
} finally {
loading.value = false;
}
};
// 复工
const handleResumeWork = async () => {
if (!currentWorkOrder.value) return;
try {
await proxy?.$modal.confirm('确认复工?');
loading.value = true;
// 调用复工接口
await resumePlanInfo(currentWorkOrder.value.planId);
proxy?.$modal.msgSuccess('复工成功');
await getCurrentWorkOrder();
} catch (error: any) {
if (error !== 'cancel') {
proxy?.$modal.msgError('复工失败');
console.error('复工失败:', error);
}
} finally {
loading.value = false;
}
};
// 报工
const handleReportWork = async () => {
if (!currentWorkOrder.value) return;
if (!reportForm.value.reportQuantity || reportForm.value.reportQuantity <= 0) {
proxy?.$modal.msgWarning('请输入报工数量');
return;
}
if (reportForm.value.reportQuantity > reportableQuantity.value) {
proxy?.$modal.msgWarning('报工数量不能超过可报工量');
return;
}
try {
await proxy?.$modal.confirm('确认报工?');
loading.value = true;
// 调用报工接口
const reportData = {
planId: currentWorkOrder.value.planId,
reportQuantity: reportForm.value.reportQuantity,
defectiveQuantity: reportForm.value.defectiveQuantity || 0,
workshopId: currentWorkOrder.value.workshopId
};
await reportPlanInfo(reportData);
proxy?.$modal.msgSuccess('报工成功');
// 清空表单
reportForm.value.reportQuantity = 0;
reportForm.value.defectiveQuantity = 0;
// 刷新当前工单数据
await getCurrentWorkOrder();
// 通知父组件刷新相关数据(待处理任务列表、报工记录等)
emit('reportSuccess');
} catch (error: any) {
if (error !== 'cancel') {
proxy?.$modal.msgError('报工失败');
console.error('报工失败:', error);
}
} finally {
loading.value = false;
}
};
// 首检
const handleFirstInspection = async () => {
if (!currentWorkOrder.value) return;
try {
// 先检查是否已存在未处理的首检任务
const detailRes = await getProdProductPlanDetailList({ planId: currentWorkOrder.value.planId });
const detailList = detailRes.data || [];
// 如果没有生产信息,提示用户
if (!detailList || detailList.length === 0) {
proxy?.$modal.msgWarning('本工单尚未生成生产信息,无法进行首检');
return;
}
const firstDetail = detailList[detailList.length - 1];
const planDetailId = firstDetail.planDetailId;
// 检查是否已存在未处理的首检任务
await checkInspectionTask(currentWorkOrder.value.planCode, planDetailId);
if (hasPendingInspection.value) {
proxy?.$modal.msgWarning('已存在未处理的首检任务,无法重复生成');
return;
}
await proxy?.$modal.confirm('确认生成首检任务?');
loading.value = true;
// 调用首检接口
const inspectionData = {
planDetailId: planDetailId,
processId: currentWorkOrder.value.processId,
qcInspectionType: '0' // 0首检 1专检 2自检 3互检 4原材料检 5抽检 6成品检 7入库检
};
await generateInspectionTask(inspectionData);
proxy?.$modal.msgSuccess('首检任务生成成功');
// 刷新首检任务状态
await checkInspectionTask(currentWorkOrder.value.planCode, planDetailId);
} catch (error: any) {
if (error !== 'cancel') {
console.error('首检任务生成失败:', error);
}
} finally {
loading.value = false;
}
};
// 刷新数据
const refresh = () => {
getCurrentWorkOrder();
};
defineExpose({
refresh
});
onMounted(() => {
getCurrentWorkOrder();
});
</script>
<style scoped lang='scss'>
.current-task {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.task-content {
flex: 1;
display: flex;
gap: 20px;
margin-bottom: 20px;
.task-details {
flex: 1;
background: #fff;
border-radius: 4px;
padding: 20px;
border: 1px solid #e4e7ed;
.details-header {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
.details-body {
.detail-item {
display: flex;
margin-bottom: 15px;
font-size: 14px;
.label {
width: 120px;
color: #606266;
flex-shrink: 0;
}
.value {
flex: 1;
color: #303133;
&.plan-code {
color: #67c23a;
font-weight: 500;
}
&.status-text {
color: #f56c6c;
font-weight: 500;
}
}
}
}
}
.report-form {
width: 400px;
background: #fff;
border-radius: 4px;
padding: 20px;
border: 1px solid #e4e7ed;
.form-header {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
.form-body {
:deep(.el-form-item) {
margin-bottom: 20px;
}
}
}
}
.action-buttons {
display: flex;
gap: 10px;
padding: 20px;
background: #fff;
border-radius: 4px;
border: 1px solid #e4e7ed;
.el-button {
flex: 1;
height: 40px;
}
}
}
</style>