update 添加机加工单页面、报工页面

master
yinq 4 weeks ago
parent 58826493a4
commit 9a9eb80d40

@ -231,7 +231,7 @@
width="150"
>
<template #default="scope">
{{scope.row.planStatus}}
{{ getPlanStatusText(scope.row.planStatus) }}
</template>
</el-table-column>
<el-table-column
@ -242,13 +242,12 @@
<template #default="scope">
<el-button
type="danger"
icon="el-icon-delete"
size="small"
@click="handleDeleteMesProductPlan(scope)"
v-if="scope.row.newFlag === '1' &&
formData.orderStatus!==ORDER_STATUS.FINISHED &&
formData.orderStatus!==ORDER_STATUS.RECALLED &&
formData.orderStatus!==ORDER_STATUS.DELETED">
formData.orderStatus!==ORDER_STATUS.DELETED">删除
</el-button>
</template>
</el-table-column>
@ -278,7 +277,14 @@ import { reactive, onMounted, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getOrderInfo } from '@/api/mes/orderInfo'
import { getBaseRouteProcesses, getDispatchCode, getPlanCode, selectProductPlans, orderAddProductPlanList } from '@/api/mes/planInfo'
import {
getBaseRouteProcesses,
getDispatchCode,
getPlanCode,
selectProductPlans,
orderAddProductPlanList,
orderAddProductPlanJJList
} from '@/api/mes/planInfo';
import { getStationInfoList } from '@/api/mes/baseStationInfo'
@ -305,6 +311,13 @@ const PLAN_STATUS = {
FINISHED: '3' //
}
const planStatusTextMap = {
'0': '待派工',
'1': '已派工',
'2': '已开始',
'3': '已完成'
}
const orderStatusTextMap = {
'0': '待生产',
'1': '生产中',
@ -360,6 +373,10 @@ const getOrderStatusText = (status) => {
return orderStatusTextMap[status] || `未知状态(${status})`
}
const getPlanStatusText = (status) => {
return planStatusTextMap[status] || `未知状态(${status})`
}
const fetchOrderInfo = async (productOrderId) => {
// console.log('Fetching order info for ID', productOrderId)
try {
@ -458,7 +475,7 @@ const groupProductPlans = async (productPlans) => {
planBeginTime: productPlan.planBeginTime || formData.value.planBeginTime || '',
planEndTime: productPlan.planEndTime || formData.value.planEndTime || '',
planStatus: productPlan.planStatus,
materialId: productPlan.materialId || formData.value.materialId,
materialId: formData.value.materialId,
materialBomId: productPlan.materialBomId || formData.value.materialBomId,
productOrderId: productPlan.productOrderId || formData.value.productOrderId,
saleOrderId: productPlan.saleOrderId || formData.value.saleOrderId || null,
@ -583,7 +600,7 @@ const submitForm = async () => {
planBeginTime: planBeginTime,
planEndTime: planEndTime,
planStatus: PLAN_STATUS.TO_DISPATCH,
materialId: Number(formData.value.materialId),
materialId: formData.value.materialId,
materialBomId: Number(formData.value.materialBomId),
productOrderId: Number(formData.value.productOrderId),
saleOrderId: formData.value.saleOrderId ? Number(formData.value.saleOrderId) : null,
@ -622,7 +639,7 @@ const submitForm = async () => {
console.log('提交数据:', JSON.stringify(submitData, null, 2))
const res = await orderAddProductPlanList(submitData);
const res = await orderAddProductPlanJJList(submitData);
if (res.code === 200) {
ElMessage.success('提交成功');

@ -469,15 +469,63 @@ const data = reactive<PageData<OrderInfoForm, OrderInfoQuery>>({
factoryId: undefined,
params: {}
},
rules: {
planAmount: [
{ required: true, message: '计划数量不能为空', trigger: 'blur' }
],
}
rules: {}
});
const { queryParams, form, rules } = toRefs(data);
//
data.rules = {
planAmount: [
{ required: true, message: '计划数量不能为空', trigger: 'blur' }
],
planBeginTime: [
{
validator: (rule: any, value: any, callback: any) => {
if (!value) {
callback();
return;
}
if (data.form.planEndTime && value > data.form.planEndTime) {
callback(new Error('计划开始时间不能大于计划结束时间'));
} else {
callback();
}
},
trigger: 'change'
}
],
planEndTime: [
{
validator: (rule: any, value: any, callback: any) => {
if (!value) {
callback();
return;
}
if (data.form.planBeginTime && value < data.form.planBeginTime) {
callback(new Error('计划结束时间不能小于计划开始时间'));
} else {
callback();
}
},
trigger: 'change'
}
]
};
//
watch(() => form.value.planBeginTime, () => {
if (form.value.planEndTime) {
orderInfoFormRef.value?.validateField('planEndTime');
}
});
watch(() => form.value.planEndTime, () => {
if (form.value.planBeginTime) {
orderInfoFormRef.value?.validateField('planBeginTime');
}
});
/** 查询生产订单信息列表 */
const getList = async () => {
loading.value = true;

@ -0,0 +1,861 @@
<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="生产订单ID" prop="productOrderId">-->
<!-- <el-input v-model="queryParams.productOrderId" placeholder="请输入生产订单ID" clearable @keyup.enter="handleQuery" />-->
<!-- </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 label="物料BOM" prop="materialBomId">-->
<!-- <el-input v-model="queryParams.materialBomId" placeholder="请输入物料BOM" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="工序ID" prop="processId">-->
<!-- <el-input v-model="queryParams.processId" placeholder="请输入工序ID" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<el-form-item label='工位' prop='releaseId'>
<el-select v-model='queryParams.releaseId' placeholder='请选择工位' clearable
@keyup.enter='handleQuery'>
<el-option
v-for='item in stationList'
:key='item.stationId'
:label='item.stationName'
:value='item.stationId'
/>
</el-select>
</el-form-item>
<el-form-item label='计划时间' prop='planRangeTime'>
<el-date-picker clearable
v-model='queryParams.planRangeTime'
type='datetimerange'
range-separator='至'
start-placeholder='开始日期'
end-placeholder='结束日期'
value-format='YYYY-MM-DD HH:mm:ss'
/>
</el-form-item>
<el-form-item label='计划状态' prop='planStatus'>
<el-select v-model='queryParams.planStatus' placeholder='请选择计划状态' clearable
@keyup.enter='handleQuery'>
<el-option v-for='dict in mes_plan_status' :key='dict.value' :label='dict.label' :value='dict.value' />
</el-select>
</el-form-item>
<!-- <el-form-item label="班次" prop="shiftId">-->
<!-- <el-input v-model="queryParams.shiftId" placeholder="请输入班次" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="班组" prop="classTeamId">-->
<!-- <el-input v-model="queryParams.classTeamId" placeholder="请输入班组" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label='模号' prop='modelCode'>-->
<!-- <el-select v-model='queryParams.modelCode' placeholder='请选择模号' clearable-->
<!-- @keyup.enter='handleQuery'>-->
<!-- <el-option v-for='dict in mes_model_code' :key='dict.value' :label='dict.label' :value='dict.value' />-->
<!-- </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'>
<template #header>
<el-row :gutter='10' class='mb8'>
<!-- <el-col :span='1.5'>-->
<!-- <el-button type='primary' plain icon='Plus' @click='handleAdd' v-hasPermi="['mes:planInfo:add']">-->
<!-- </el-button>-->
<!-- </el-col>-->
<el-col :span='1.5'>
<el-button type='success' plain icon='Edit' :disabled='multiple' @click='handleUpdatePlanStatus()'
v-hasPermi="['mes:planInfo:edit']">下发选中工单
</el-button>
</el-col>
<!-- <el-col :span='1.5'>-->
<!-- <el-button type='success' plain icon='Edit' :disabled='single' @click='handleUpdate()'-->
<!-- v-hasPermi="['mes:planInfo:edit']">修改-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span='1.5'>-->
<!-- <el-button type='danger' plain icon='Delete' :disabled='multiple' @click='handleDelete()'-->
<!-- v-hasPermi="['mes:planInfo:remove']">删除-->
<!-- </el-button>-->
<!-- </el-col>-->
<!-- <el-col :span='1.5'>-->
<!-- <el-button type='warning' plain icon='Download' @click='handleExport' v-hasPermi="['mes:planInfo:export']">-->
<!-- 导出-->
<!-- </el-button>-->
<!-- </el-col>-->
<right-toolbar v-model:showSearch='showSearch' :columns='columns' :search='true'
@queryTable='getList'></right-toolbar>
</el-row>
</template>
<el-table v-loading='loading' :data='planInfoList' @selection-change='handleSelectionChange'>
<el-table-column type='selection' width='55' align='center' />
<el-table-column label='主键标识' align='center' prop='planId' v-if='columns[0].visible' />
<el-table-column label='生产订单ID' align='center' prop='productOrderId' v-if='columns[2].visible'
width='100' />
<el-table-column label='销售订单ID' align='center' prop='saleOrderId' v-if='columns[3].visible' />
<el-table-column label='销售订单编号' align='center' prop='saleorderCode' v-if='columns[4].visible' />
<el-table-column label='工单编号' align='center' prop='planCode' v-if='columns[5].visible' width='130' />
<el-table-column label='派工单号' align='center' prop='dispatchCode' v-if='columns[6].visible' width='120' />
<el-table-column label='物料名称' align='center' prop='materialName' v-if='columns[7].visible' width='180' show-overflow-tooltip />
<el-table-column label='BOM版本' align='center' prop='materialBomVersion' v-if='columns[8].visible' width='120' />
<el-table-column label='工序名称' align='center' prop='processName' v-if='columns[9].visible' width='120' />
<el-table-column label='顺序' align='center' prop='processOrder' v-if='columns[10].visible' width='80' />
<el-table-column label='上一工序ID' align='center' prop='lastProcessId' v-if='columns[11].visible'
width='100' />
<el-table-column label='最后工序标识' align='center' prop='finalProcessFlag' v-if='columns[12].visible'
width='110'>
<template #default='scope'>
<dict-tag :options='active_flag' :value='scope.row.finalProcessFlag' />
</template>
</el-table-column>
<el-table-column label='下达类型' align='center' prop='releaseType' v-if='columns[13].visible' width='100'>
<template #default='scope'>
<dict-tag :options='mes_release_type' :value='scope.row.releaseType' />
</template>
</el-table-column>
<el-table-column label='下达工位' align='center' prop='releaseName' v-if='columns[14].visible' width='120' />
<el-table-column label='单位生产时间(秒)' align='center' prop='productionTime' v-if='columns[15].visible' width='140' />
<el-table-column label='计划数量' align='center' prop='planAmount' v-if='columns[16].visible' width='100' />
<el-table-column label='派工数量' align='center' prop='dispatchAmount' v-if='columns[17].visible' width='100' />
<el-table-column label='完成数量' align='center' prop='completeAmount' v-if='columns[18].visible' width='100' />
<el-table-column label='计量单位' align='center' prop='unitName' v-if='columns[37].visible' />
<el-table-column label='计划时间' align='center' prop='planBeginTime' width='180' v-if='columns[19].visible'>
<template #default='scope'>
<span>{{ parseTime(scope.row.planBeginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label='计划结束时间' align='center' prop='planEndTime' width='180' v-if='columns[20].visible'>
<template #default='scope'>
<span>{{ parseTime(scope.row.planEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label='实际开始时间' align='center' prop='realBeginTime' width='180'
v-if='columns[21].visible'>
<template #default='scope'>
<span>{{ parseTime(scope.row.realBeginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label='实际完成时间' align='center' prop='realEndTime' width='180' v-if='columns[22].visible'>
<template #default='scope'>
<span>{{ parseTime(scope.row.realEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label='附件信息' align='center' prop='attachId' v-if='columns[23].visible' />
<el-table-column label='计划状态' align='center' prop='planStatus' v-if='columns[24].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='importFlag' v-if='columns[25].visible'>
<template #default='scope'>
<dict-tag :options='mes_import_flag' :value='scope.row.importFlag' />
</template>
</el-table-column>
<el-table-column label='完成标识' align='center' prop='finishFlag' v-if='columns[26].visible'>
<template #default='scope'>
<dict-tag :options='mes_finish_flag' :value="scope.row.finishFlag ? scope.row.finishFlag.split(',') : []" />
</template>
</el-table-column>
<el-table-column label='优先级' align='center' prop='priority' v-if='columns[27].visible' />
<el-table-column label='班次' align='center' prop='shiftName' v-if='columns[28].visible' />
<el-table-column label='班组' align='center' prop='teamName' v-if='columns[29].visible' />
<el-table-column label='备注' align='center' prop='remark' v-if='columns[31].visible' />
<el-table-column label='操作' align='center' class-name='small-padding fixed-width' fixed='right'>
<template #default='scope'>
<el-button link type='primary' @click='handleRecall(scope.row)'
v-if='["0", "1"].includes(scope.row.planStatus)'
v-hasPermi="['mes:planInfo:edit']">撤回
</el-button>
<!-- <el-tooltip content='删除' placement='top'>-->
<!-- <el-button link type='primary' icon='Delete' @click='handleDelete(scope.row)'-->
<!-- v-hasPermi="['mes:planInfo:remove']"></el-button>-->
<!-- </el-tooltip>-->
</template>
</el-table-column>
</el-table>
<pagination v-show='total > 0' :total='total' v-model:page='queryParams.pageNum'
v-model:limit='queryParams.pageSize' @pagination='getList' />
</el-card>
<!-- 添加或修改生产工单信息对话框 -->
<el-dialog :title='dialog.title' v-model='dialog.visible' width='500px' append-to-body>
<el-form ref='planInfoFormRef' :model='form' :rules='rules' label-width='100px'>
<!-- <el-form-item label="生产订单ID" prop="productOrderId">-->
<!-- <el-input v-model="form.productOrderId" placeholder="请输入生产订单ID" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="销售订单ID" prop="saleOrderId">-->
<!-- <el-input v-model="form.saleOrderId" placeholder="请输入销售订单ID" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="销售订单编号" prop="saleorderCode">-->
<!-- <el-input v-model="form.saleorderCode" placeholder="请输入销售订单编号" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="工单编号" prop="planCode">-->
<!-- <el-input v-model="form.planCode" placeholder="请输入工单编号" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="派工单号" prop="dispatchCode">-->
<!-- <el-input v-model="form.dispatchCode" placeholder="请输入派工单号" />-->
<!-- </el-form-item>-->
<el-form-item label='物料名称' prop='materialId'>
<el-input v-model='form.materialName' placeholder='请点击检索物料' @click='handleMaterialAdd' readonly>
<template #append>
<el-icon class='el-input__icon'>
<search />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label='BOM版本' prop='materialBomVersion'>
<el-select v-model='form.materialBomVersion' placeholder='请选择BOM版本'>
<el-option
v-for='item in materialBomVersionOptions'
:key='item.materialBomVersion'
:label='`${item.materialBomVersion} - ${item.materialBomDesc}`'
:value='item.materialBomVersion'
/>
</el-select>
</el-form-item>
<el-form-item label='工序名称' prop='processId'>
<el-select v-model='form.processId' placeholder='请选择工序'>
<el-option
v-for='item in processList'
:key='item.processId'
:label='item.processName'
:value='item.processId'
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="顺序" prop="processOrder">-->
<!-- <el-input-number v-model="form.processOrder" placeholder="请输入顺序" style="width: 200px;"/>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="上一工序ID" prop="lastProcessId">-->
<!-- <el-input v-model="form.lastProcessId" placeholder="请输入上一工序ID" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="最后工序标识" prop="finalProcessFlag">-->
<!-- <el-radio-group v-model="form.finalProcessFlag">-->
<!-- <el-radio-->
<!-- v-for="dict in active_flag"-->
<!-- :key="dict.value"-->
<!-- :value="dict.value"-->
<!-- >{{dict.label}}</el-radio>-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<el-form-item label='下达类型' prop='releaseType'>
<el-radio-group v-model='form.releaseType' :disabled='true'>
<el-radio
v-for='dict in mes_release_type'
:key='dict.value'
:value='dict.value'
>{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label='机台' prop='releaseId'>
<el-select v-model='form.releaseId' placeholder='请选择'>
<el-option
v-for='item in releaseList'
:key='item.machineId'
:label='item.machineName'
:value='item.machineId'
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="单位生产时间(秒)" prop="productionTime">-->
<!-- <el-input v-model="form.productionTime" placeholder="请输入单位生产时间(秒)" />-->
<!-- </el-form-item>-->
<el-form-item label='计划数量' prop='planAmount'>
<el-input-number v-model='form.planAmount' placeholder='请输入计划数量' style='width: 200px;' />
</el-form-item>
<!-- <el-form-item label="派工数量" prop="dispatchAmount">-->
<!-- <el-input-number v-model="form.dispatchAmount" placeholder="请输入派工数量" style="width: 200px;" />-->
<!-- </el-form-item>-->
<el-form-item label='计划时间' prop='planBeginTime'>
<el-date-picker clearable
v-model='form.planBeginTime'
type='datetime'
value-format='YYYY-MM-DD HH:mm:ss'
placeholder='请选择计划时间'>
</el-date-picker>
</el-form-item>
<!-- <el-form-item label='计划结束时间' prop='planEndTime'>-->
<!-- <el-date-picker clearable-->
<!-- v-model='form.planEndTime'-->
<!-- type='datetime'-->
<!-- value-format='YYYY-MM-DD HH:mm:ss'-->
<!-- placeholder='请选择计划结束时间'>-->
<!-- </el-date-picker>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="附件信息" prop="attachId">-->
<!-- <el-input v-model="form.attachId" placeholder="请输入附件信息" />-->
<!-- </el-form-item>-->
<el-form-item label='优先级' prop='priority'>
<el-input-number v-model='form.priority' placeholder='请输入优先级' style='width: 200px;' />
</el-form-item>
<el-form-item label='班次' prop='shiftId'>
<el-radio-group v-model='form.shiftId'>
<el-radio
v-for='dict in shiftList'
:key='dict.shiftId'
:value='dict.shiftId'
>{{ dict.shiftName }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label='班组' prop='classTeamId'>
<el-select v-model='form.classTeamId' placeholder='请选择班组'>
<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='remark'>
<el-input v-model='form.remark' placeholder='请输入备注' />
</el-form-item>
</el-form>
<template #footer>
<div class='dialog-footer'>
<el-button :loading='buttonLoading' type='primary' @click='submitForm'> </el-button>
<el-button @click='cancel'> </el-button>
</div>
</template>
</el-dialog>
<!-- 添加物料BOM信息对话框 -->
<!-- <el-dialog title='选择物料BOM信息' v-model='materialBomOpen' append-to-body>-->
<!-- <BomSelect @selection='handleSelection' ref='bomSelectRef' v-if='materialBomOpen'-->
<!-- :materialIdForm='materialIdForm'></BomSelect>-->
<!-- <div slot='footer' class='dialog-footer'>-->
<!-- <el-button type='primary' @click='submitMaterialBomForm'> </el-button>-->
<!-- <el-button @click='materialBomOpen = false'> </el-button>-->
<!-- </div>-->
<!-- </el-dialog>-->
<!-- 添加物料信息对话框 -->
<el-dialog title='选择物料信息' v-model='materialOpen' width='1200px' append-to-body>
<MaterialSelect ref='materialSelectRef' v-if='materialOpen'></MaterialSelect>
<div slot='footer' class='dialog-footer'>
<el-button type='primary' @click='submitMaterialForm'> </el-button>
<el-button @click='materialOpen = false'> </el-button>
</div>
</el-dialog>
</div>
</template>
<script setup name='PlanInfo' lang='ts'>
import {
listPlanInfo,
getPlanInfo,
delPlanInfo,
addPlanInfo,
updatePlanInfo,
issuePlanInfo,
materialBomTreeSelect,
materialBomVersionSelect
} from '@/api/mes/planInfo';
import { PlanInfoVO, PlanInfoQuery, PlanInfoForm } from '@/api/mes/planInfo/types';
import { getBaseShiftInfoList } from '@/api/mes/baseShiftInfo';
import { getBaseClassTeamInfoList } from '@/api/mes/baseClassTeamInfo';
import BomSelect from '@/views/mes/materialBom/addBom.vue';
import MaterialSelect from '@/views/mes/baseMaterialInfo/addMaterial.vue';
import { getProcessInfoList } from '@/api/mes/baseProcessInfo';
import { getProdBaseMachineInfoList } from '@/api/mes/prodBaseMachineInfo';
import { ProdBaseMachineInfoVO } from '@/api/mes/prodBaseMachineInfo/types';
import { getStationInfoList } from '@/api/mes/baseStationInfo';
import { BaseStationInfoVO } from '@/api/mes/baseStationInfo/types';
import { MaterialBomVO } from '@/api/mes/materialBom/types';
import { optionselect } from '@/api/system/post';
import { cloneDeep } from 'lodash-es';
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 planInfoList = ref<PlanInfoVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const planInfoFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const shiftList = ref([]);
const classTeamList = ref([]);
const processList = ref([]);
let releaseList = ref([]);
const stationList = ref<BaseStationInfoVO[]>([]);
const materialBomOpen = ref(false);
const bomSelectRef = ref();
const materialOpen = ref(false);
const materialSelectRef = ref();
const materialIdForm = ref();
const materialBomVersionOptions = ref<MaterialBomVO[]>([]);
const workshopId = ref();
const prodBaseMachineInfoList = ref<ProdBaseMachineInfoVO[]>([]);
const machineQueryParams = ref<any>({});
/*获取班次下拉框*/
const getShiftSelect = async () => {
let res = await getBaseShiftInfoList(null);
shiftList.value = res.data;
};
/*获取班组下拉框*/
const getClassTeamSelect = async () => {
let res = await getBaseClassTeamInfoList(null);
classTeamList.value = res.data;
};
/*获取工序下拉框*/
const getProcessSelect = async () => {
let res = await getProcessInfoList({ processType: 1 });
processList.value = res.data;
};
/*获取机台下拉框*/
const getReleaseSelect = async () => {
machineQueryParams.value.processId = form.value.processId;
// machineQueryParams.value.workshopId = workshopId.value;
let res = await getProdBaseMachineInfoList(machineQueryParams.value);
releaseList.value = res.data;
prodBaseMachineInfoList.value = res.data;
};
/*获取工位下拉框*/
const getStationSelect = async () => {
let res = await getStationInfoList({});
stationList.value = res.data || [];
};
//
const columns = ref<FieldOption[]>([
{ key: 0, label: `主键标识`, visible: false },
{ key: 1, label: `租户编号`, visible: false },
{ key: 2, label: `生产订单ID`, visible: false },
{ key: 3, label: `销售订单ID`, visible: false },
{ key: 4, label: `销售订单编号`, visible: false },
{ key: 5, label: `工单编号`, visible: true },
{ key: 6, label: `派工单号`, visible: true },
{ key: 7, label: `物料名称`, visible: true },
{ key: 8, label: `BOM版本`, visible: false },
{ key: 9, label: `工序名称`, visible: true },
{ key: 10, label: `顺序`, visible: false },
{ key: 11, label: `上一工序ID`, visible: false },
{ key: 12, label: `最后工序标识`, visible: false },
{ key: 13, label: `下达类型`, visible: false },
{ key: 14, label: `下达ID`, visible: true },
{ key: 15, label: `单位生产时间(秒)`, visible: false },
{ key: 16, label: `计划数量`, visible: true },
{ key: 17, label: `派工数量`, visible: false },
{ key: 18, label: `完成数量`, visible: true },
{ key: 19, label: `计划时间`, visible: true },
{ key: 20, label: `计划结束时间`, visible: false },
{ key: 21, label: `实际开始时间`, visible: true },
{ key: 22, label: `实际完成时间`, visible: true },
{ key: 23, label: `附件信息`, visible: false },
{ key: 24, label: `计划状态`, visible: true },
{ key: 25, label: `导入类型`, visible: false },
{ key: 26, label: `完成标识`, visible: true },
{ key: 27, label: `优先级`, visible: false },
{ key: 28, label: `班次`, visible: true },
{ key: 29, label: `班组`, visible: true },
{ key: 30, label: `模号`, visible: true },
{ key: 31, label: `备注`, visible: false },
{ key: 32, label: `创建部门`, visible: false },
{ key: 33, label: `创建人`, visible: false },
{ key: 34, label: `创建时间`, visible: false },
{ key: 35, label: `更新人`, visible: false },
{ key: 36, label: `更新时间`, visible: false },
{ key: 37, label: `计量单位`, visible: false },
]);
const initFormData: PlanInfoForm = {
planId: undefined,
productOrderId: undefined,
saleOrderId: undefined,
saleorderCode: undefined,
planCode: undefined,
dispatchCode: undefined,
materialId: undefined,
materialBomId: undefined,
processId: undefined,
processOrder: undefined,
lastProcessId: undefined,
finalProcessFlag: undefined,
releaseType: '1',
releaseId: undefined,
productionTime: undefined,
planAmount: 1,
dispatchAmount: undefined,
completeAmount: undefined,
planBeginTime: undefined,
planEndTime: undefined,
realBeginTime: undefined,
realEndTime: undefined,
attachId: undefined,
planStatus: undefined,
importFlag: undefined,
finishFlag: [],
priority: 1,
shiftId: undefined,
classTeamId: undefined,
modelCode: undefined,
remark: undefined,
materialBoMName: undefined,
workshopId: undefined,
materialBomVersion: undefined,
shiftName: '',
};
const data = reactive<PageData<PlanInfoForm, PlanInfoQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
planId: undefined,
productOrderId: undefined,
saleOrderId: undefined,
saleorderCode: undefined,
planCode: undefined,
dispatchCode: undefined,
materialId: undefined,
materialBomId: undefined,
processId: undefined,
processOrder: undefined,
lastProcessId: undefined,
finalProcessFlag: undefined,
releaseType: undefined,
releaseId: undefined,
productionTime: undefined,
planAmount: undefined,
dispatchAmount: undefined,
completeAmount: undefined,
planBeginTime: undefined,
planEndTime: undefined,
realBeginTime: undefined,
realEndTime: undefined,
attachId: undefined,
planStatus: undefined,
importFlag: undefined,
finishFlag: undefined,
priority: undefined,
shiftId: undefined,
classTeamId: undefined,
modelCode: undefined,
workshopId: undefined,
params: {},
planRangeTime: undefined,
materialName: undefined,
shiftName: '',
shifitId: undefined
},
rules: {
materialId: [
{ required: true, message: '物料名称不能为空', trigger: 'blur' }
],
processId: [
{ required: true, message: '工序不能为空', trigger: 'blur' }
],
releaseId: [
{ required: true, message: '机台名称不能为空', trigger: 'blur' }
],
materialName: [
{ required: true, message: '物料BOM不能为空', trigger: 'blur' }
],
planAmount: [
{ required: true, message: '计划数量不能为空', trigger: 'blur' }
],
planBeginTime: [
{ required: true, message: '计划日期不能为空', trigger: 'blur' }
],
// planAmount1: [
// { required: true, message: '', trigger: 'blur' }
// ],
// planAmount2: [
// { required: true, message: '', trigger: 'blur' }
// ],
// planAmount3: [
// { required: true, message: '', trigger: 'blur' }
// ]
}
});
const { queryParams, form, rules } = toRefs(data);
const getWorkshopId = async () => {
const router = useRouter();
workshopId.value = router.currentRoute.value.query && router.currentRoute.value.query.workshopId;
queryParams.value.workshopId = workshopId.value;
};
/** 查询生产计划信息列表 */
const getList = async () => {
loading.value = true;
try {
const params = {
...queryParams.value,
planBeginTime: queryParams.value.planRangeTime?.[0],
planEndTime: queryParams.value.planRangeTime?.[1]
};
const res = await listPlanInfo(params);
planInfoList.value = res.rows;
total.value = res.total;
} finally {
loading.value = false;
}
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
form.value.workshopId = workshopId.value;
planInfoFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: PlanInfoVO[]) => {
ids.value = selection.map(item => item.planId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加生产计划信息';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: PlanInfoVO) => {
reset();
const _planId = row?.planId || ids.value[0];
const res = await getPlanInfo(_planId);
Object.assign(form.value, res.data);
// finishFlag
if (Array.isArray(form.value.finishFlag)) {
// do nothing
} else if (typeof form.value.finishFlag === 'string') {
form.value.finishFlag = form.value.finishFlag.split(',').filter(Boolean).join(',');
} else {
form.value.finishFlag = [];
}
dialog.visible = true;
dialog.title = '修改生产计划信息';
};
/** 撤回按钮操作 */
const handleRecall = async (row?: PlanInfoVO) => {
buttonLoading.value = true;
let rowData = cloneDeep(row);
rowData.workshopId = workshopId.value;
rowData.planStatus = '4';
await proxy?.$modal.confirm('是否确认撤回工单编号为"' + row.planCode + '"的数据项?').finally(() => {
updatePlanInfo(rowData).finally(() => buttonLoading.value = false);
});
proxy?.$modal.msgSuccess('撤回成功');
await getList();
};
/** 提交按钮 */
const submitForm = () => {
planInfoFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// finishFlag
if (Array.isArray(form.value.finishFlag)) {
form.value.finishFlag = form.value.finishFlag.join(',');
} else if (typeof form.value.finishFlag === 'string') {
// do nothing
} else {
form.value.finishFlag = '';
}
if (form.value.planId) {
await updatePlanInfo(form.value).finally(() => buttonLoading.value = false);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addPlanInfo(form.value).finally(() => buttonLoading.value = false);
proxy?.$modal.msgSuccess('新增成功');
}
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: PlanInfoVO) => {
const _planIds = row?.planId || ids.value;
await proxy?.$modal.confirm('是否确认删除生产计划信息编号为"' + _planIds + '"的数据项?').finally(() => loading.value = false);
await delPlanInfo(_planIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 下发选中工单按钮操作 */
const handleUpdatePlanStatus = async () => {
if (ids.value.length === 0) {
proxy?.$modal.msgWarning('请先选择要下发的工单!');
return;
}
await proxy?.$modal.confirm('是否下发已选中的工单?').finally(() => loading.value = false);
buttonLoading.value = true;
try {
await issuePlanInfo(0, ids.value);
proxy?.$modal.msgSuccess('下发选中工单成功');
await getList();
} catch (error) {
proxy?.$modal.msgError('下发选中工单失败');
} finally {
buttonLoading.value = false;
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download('mes/planInfo/export', {
...queryParams.value
}, `planInfo_${new Date().getTime()}.xlsx`);
};
/** 新增物料按钮操作 */
const handleMaterialAdd = () => {
materialOpen.value = true;
};
/** 提交物料信息按钮 */
const submitMaterialForm = () => {
let selectedRow = materialSelectRef.value.tableRef.store.states.currentRow.value;
form.value.materialId = selectedRow.materialId;
form.value.materialName = selectedRow.materialName;
getMaterialBomVersionSelect();
materialIdForm.value = selectedRow.materialId;
materialOpen.value = false;
};
/** 新增物料BOM按钮操作 */
// const handleMaterialBomAdd = (modelCode) => {
// if (form.value.materialId == null && form.value.materialIdL == null
// && form.value.materialIdR == null) {
// proxy?.$modal.msgWarning('');
// return;
// }
// modelCodeRef.value = modelCode;
// materialBomOpen.value = true;
// };
/** 提交物料BOM信息按钮 */
const submitMaterialBomForm = () => {
let selectedRow = bomSelectRef.value.tableRef.store.states.currentRow.value;
form.value.materialBomId = selectedRow.materialBomId;
form.value.materialBoMName = selectedRow.materialName;
materialBomOpen.value = false;
};
/** 查询物料bom版本下拉列表 */
const getMaterialBomVersionSelect = async () => {
materialBomVersionOptions.value = [];
if (form.value.materialId && form.value.materialId !== '') {
const query = { parentId: form.value.materialId };
const res = await materialBomVersionSelect(query);
materialBomVersionOptions.value = res.data;
}
};
/** 查询物料bom下拉树结构 */
const getTreeSelect = async () => {
const res = await materialBomTreeSelect();
materialBomVersionOptions.value = res.data;
};
onMounted(() => {
// getWorkshopId();
// getShiftSelect();
// getClassTeamSelect();
getProcessSelect();
getStationSelect();
if (form.value.processId != null) {
// getReleaseSelect();
}
getList();
});
// processId
// watch(
// () => form.value.processId, (newValue, oldValue) => {
// //
// getReleaseSelect();
// }
// );
</script>
<style>
.my-header {
width: 100%;
text-align: center;
}
.el-overlay .el-overlay-dialog .el-dialog .el-dialog__header {
border-bottom: none;
}
</style>

@ -0,0 +1,446 @@
<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'
@click='handleSelfInspection'
>
自检
</el-button>
</div>
</div>
</template>
<script setup name='CurrentTask' lang='ts'>
import { ref, computed, onMounted, getCurrentInstance } from 'vue';
import { listPlanInfo, updatePlanInfo, stopPlanInfo, resumePlanInfo, reportPlanInfo } from '@/api/mes/planInfo';
import { PlanInfoVO, PlanInfoQuery } from '@/api/mes/planInfo/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 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 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];
} else {
currentWorkOrder.value = null;
}
} catch (error) {
console.error('获取当前工单失败:', error);
currentWorkOrder.value = null;
} 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 handleSelfInspection = () => {
proxy?.$modal.msgInfo('自检功能待开发');
};
//
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>

@ -0,0 +1,157 @@
<template>
<div class='pending-tasks'>
<el-table
v-loading='loading'
:data='taskList'
border
stripe
style='width: 100%'
@selection-change='handleSelectionChange'
>
<el-table-column type='selection' width='55' align='center' />
<el-table-column label='工单号' prop='planCode' width='150' align='center' />
<el-table-column label='产品编码' prop='materialCode' width='150' align='center' />
<el-table-column label='产品名称' prop='materialName' width='200' align='center' show-overflow-tooltip />
<el-table-column label='工序名称' prop='processName' width='150' align='center' />
<el-table-column label='计划数量' prop='planAmount' width='100' align='center' />
<el-table-column label='排产日期' prop='planBeginTime' width='150' align='center'>
<template #default='scope'>
<span>{{ parseTime(scope.row.planBeginTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label='计划状态' prop='planStatus' width='100' align='center'>
<template #default='scope'>
<dict-tag :options='mes_plan_status' :value='scope.row.planStatus' />
</template>
</el-table-column>
<el-table-column label='操作' width='120' align='center' fixed='right'>
<template #default='scope'>
<el-button
link
type='primary'
@click='handleStart(scope.row)'
:disabled='scope.row.planStatus === "2"'
>
开始
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show='total > 0'
:total='total'
v-model:page='queryParams.pageNum'
v-model:limit='queryParams.pageSize'
@pagination='getList'
/>
</div>
</template>
<script setup name='PendingTasks' lang='ts'>
import { ref, onMounted, getCurrentInstance, toRefs } from 'vue';
import { listPlanInfo, startPlanInfo } from '@/api/mes/planInfo';
import { PlanInfoVO, PlanInfoQuery } from '@/api/mes/planInfo/types';
import { parseTime } from '@/utils/ruoyi';
const { proxy } = getCurrentInstance() as any;
const { mes_plan_status } = toRefs<any>(proxy?.useDict('mes_plan_status'));
const props = defineProps<{
workstationId?: string | number;
}>();
const emit = defineEmits(['issue']);
const loading = ref(false);
const taskList = ref<PlanInfoVO[]>([]);
const total = ref(0);
const selectedRows = ref<PlanInfoVO[]>([]);
const queryParams = ref<any>({
pageNum: 1,
pageSize: 10,
planStatus: '1', //
releaseType: '3' //
});
//
const getList = async () => {
loading.value = true;
try {
const query: any = {
...queryParams.value
};
// ID
if (props.workstationId) {
query.releaseId = props.workstationId;
}
const res = await listPlanInfo(query as PlanInfoQuery);
taskList.value = res.rows || [];
total.value = res.total || 0;
} catch (error) {
console.error('获取待处理任务列表失败:', error);
} finally {
loading.value = false;
}
};
//
const handleSelectionChange = (selection: PlanInfoVO[]) => {
selectedRows.value = selection;
};
//
const handleStart = async (row?: PlanInfoVO) => {
const targetRow = row || selectedRows.value[0];
if (!targetRow) {
proxy?.$modal.msgWarning('请选择要开始的工单!');
return;
}
//
if (targetRow.planStatus === '2') {
proxy?.$modal.msgWarning('该工单已开始,不能重复开始!');
return;
}
try {
await proxy?.$modal.confirm(`是否开始工单 "${targetRow.planCode}"`);
loading.value = true;
//
await startPlanInfo(targetRow.planId);
proxy?.$modal.msgSuccess('开始成功');
emit('issue', targetRow.planCode);
await getList();
} catch (error: any) {
if (error !== 'cancel') {
proxy?.$modal.msgError('开始失败');
console.error('开始工单失败:', error);
}
} finally {
loading.value = false;
}
};
//
const refresh = () => {
getList();
};
defineExpose({
refresh
});
onMounted(() => {
getList();
});
</script>
<style scoped lang='scss'>
.pending-tasks {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,227 @@
<template>
<div class='process-documents'>
<el-card shadow='never'>
<template #header>
<div class='card-header'>
<span>工艺文件查看</span>
<el-button type='primary' icon='Refresh' @click='getList'>刷新</el-button>
</div>
</template>
<div v-if='documentList.length === 0' class='empty-state'>
<el-empty description='暂无工艺文件' />
</div>
<div v-else class='document-list'>
<div
v-for='doc in documentList'
:key='doc.id'
class='document-item'
@click='handleViewDocument(doc)'
>
<el-icon class='document-icon'>
<Document />
</el-icon>
<div class='document-info'>
<div class='document-name'>{{ doc.name }}</div>
<div class='document-meta'>
<span>类型: {{ doc.type }}</span>
<span>大小: {{ doc.size }}</span>
<span>更新时间: {{ parseTime(doc.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</div>
</div>
<el-button link type='primary' @click.stop='handleDownload(doc)'>
下载
</el-button>
</div>
</div>
<pagination
v-show='total > 0'
:total='total'
v-model:page='queryParams.pageNum'
v-model:limit='queryParams.pageSize'
@pagination='getList'
/>
</el-card>
<!-- 文档预览对话框 -->
<el-dialog
v-model='previewVisible'
:title='currentDocument?.name'
width='80%'
append-to-body
>
<div class='preview-container'>
<img
v-if='isImage(currentDocument?.type)'
:src='currentDocument?.url'
alt='工艺图纸'
style='max-width: 100%; height: auto;'
/>
<iframe
v-else
:src='currentDocument?.url'
style='width: 100%; height: 600px; border: none;'
/>
</div>
</el-dialog>
</div>
</template>
<script setup name='ProcessDocuments' lang='ts'>
import { ref, onMounted, getCurrentInstance } from 'vue';
import { Document } from '@element-plus/icons-vue';
import { parseTime } from '@/utils/ruoyi';
const props = defineProps<{
workstationId?: string | number;
}>();
const { proxy } = getCurrentInstance() as any;
const loading = ref(false);
const documentList = ref<any[]>([]);
const total = ref(0);
const previewVisible = ref(false);
const currentDocument = ref<any>(null);
const queryParams = ref({
pageNum: 1,
pageSize: 10
});
//
const getList = async () => {
loading.value = true;
try {
//
// /mes/technologyInfo/list /mes/planInfo/processDocuments
// 使
await new Promise(resolve => setTimeout(resolve, 500));
documentList.value = [
{
id: 1,
name: '外腔综合加工工艺图纸.pdf',
type: 'PDF',
size: '2.5MB',
updateTime: new Date(),
url: '/api/files/process/1'
},
{
id: 2,
name: '工序流程图.png',
type: '图片',
size: '1.2MB',
updateTime: new Date(),
url: '/api/files/process/2'
}
];
total.value = documentList.value.length;
} catch (error) {
console.error('获取工艺文件失败:', error);
proxy?.$modal.msgError('获取工艺文件失败');
} finally {
loading.value = false;
}
};
//
const isImage = (type?: string) => {
return type === '图片' || type?.toLowerCase().includes('image');
};
//
const handleViewDocument = (doc: any) => {
currentDocument.value = doc;
previewVisible.value = true;
};
//
const handleDownload = (doc: any) => {
//
proxy?.$modal.msgInfo(`下载文件: ${doc.name}`);
//
// window.open(doc.url, '_blank');
};
//
const refresh = () => {
getList();
};
defineExpose({
refresh
});
onMounted(() => {
getList();
});
</script>
<style scoped lang='scss'>
.process-documents {
width: 100%;
height: 100%;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.empty-state {
padding: 40px 0;
}
.document-list {
.document-item {
display: flex;
align-items: center;
padding: 15px;
margin-bottom: 10px;
background: #f5f7fa;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #e4e7ed;
transform: translateX(5px);
}
.document-icon {
font-size: 32px;
color: #409eff;
margin-right: 15px;
}
.document-info {
flex: 1;
.document-name {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 5px;
}
.document-meta {
font-size: 12px;
color: #909399;
span {
margin-right: 15px;
}
}
}
}
}
.preview-container {
text-align: center;
}
}
</style>

@ -0,0 +1,126 @@
<template>
<div class='work-report-record'>
<el-card shadow='never'>
<template #header>
<div class='card-header'>
<span>报工记录</span>
<el-button type='primary' icon='Refresh' @click='getList'>刷新</el-button>
</div>
</template>
<el-table
v-loading='loading'
:data='recordList'
border
stripe
style='width: 100%'
>
<el-table-column label='工单号' prop='planCode' width='150' align='center' />
<el-table-column label='产品名称' prop='materialName' width='200' align='center' show-overflow-tooltip />
<el-table-column label='工序名称' prop='processName' width='150' align='center' />
<el-table-column label='报工数量' prop='reportQuantity' width='100' align='center' />
<el-table-column label='不良数量' prop='defectiveQuantity' width='100' align='center' />
<el-table-column label='报工时间' prop='reportTime' width='180' align='center'>
<template #default='scope'>
<span>{{ parseTime(scope.row.reportTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label='报工人' prop='reportUser' width='120' align='center' />
<el-table-column label='工位' prop='workstation' width='120' align='center' />
<el-table-column label='备注' prop='remark' align='center' 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='WorkReportRecord' lang='ts'>
import { ref, onMounted } from 'vue';
import { listPlanInfo } from '@/api/mes/planInfo';
import { PlanInfoVO, PlanInfoQuery } from '@/api/mes/planInfo/types';
import { parseTime } from '@/utils/ruoyi';
const props = defineProps<{
workstationId?: string | number;
}>();
const loading = ref(false);
const recordList = ref<any[]>([]);
const total = ref(0);
const queryParams = ref<PlanInfoQuery>({
pageNum: 1,
pageSize: 10,
planStatus: '2,3' //
});
//
const getList = async () => {
loading.value = true;
try {
const query: PlanInfoQuery = {
...queryParams.value,
releaseType: '3' //
};
// ID
if (props.workstationId) {
query.releaseId = props.workstationId;
}
// 使
// /mes/prodReport/workReportRecord
const res = await listPlanInfo(query);
//
recordList.value = (res.rows || []).map((item: PlanInfoVO) => ({
planCode: item.planCode,
materialName: item.materialName,
processName: item.processName,
reportQuantity: item.completeAmount || 0,
defectiveQuantity: 0, //
reportTime: item.realBeginTime || item.planBeginTime,
reportUser: '操作员', //
workstation: '-', //
remark: item.remark || '-'
}));
total.value = res.total || 0;
} catch (error) {
console.error('获取报工记录失败:', error);
} finally {
loading.value = false;
}
};
//
const refresh = () => {
getList();
};
defineExpose({
refresh
});
onMounted(() => {
getList();
});
</script>
<style scoped lang='scss'>
.work-report-record {
width: 100%;
height: 100%;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>

@ -0,0 +1,468 @@
<template>
<div class='production-report-container'>
<!-- 顶部标题栏 -->
<div class='header-bar'>
<div class='header-left'>
<el-icon class='back-icon' @click='handleBack'>
<ArrowLeft />
</el-icon>
<span class='user-info'>用户: {{ userInfo.userName || 'admin' }}</span>
<span class='shift-info'>班次: {{ currentShift || '白班' }}</span>
<span class='line-info'>生产线: {{ productionLine || 'DJ01' }}</span>
<span class='workstation-info'>
工位:
<el-select
v-model='workstationId'
placeholder='请选择或搜索工位'
style='width: 200px; margin-left: 8px;'
:disabled='activeTab !== "pending"'
filterable
clearable
:filter-method='filterWorkstation'
@change='handleWorkstationChange'
>
<el-option
v-for='item in filteredWorkstationList'
:key='item.stationId'
:label='item.stationName || item.stationCode'
:value='item.stationId'
>
<span>{{ item.stationName || item.stationCode }}</span>
<span v-if='item.stationCode && item.stationName' style='font-size: 13px; margin-left: 8px;'>
({{ item.stationCode }})
</span>
</el-option>
</el-select>
</span>
</div>
<div class='header-center'>
<h2 class='page-title'>生产报工</h2>
</div>
<div class='header-right'>
<span class='current-time'>{{ currentTime }}</span>
<el-icon class='menu-icon'>
<Menu />
</el-icon>
</div>
</div>
<!-- Tab导航 - 显示在页面上方 -->
<div class='tabs-header-wrapper'>
<el-tabs v-model='activeTab' class='report-tabs' @tab-change='handleTabChange'>
<el-tab-pane label='待处理任务' name='pending' />
<el-tab-pane label='当前任务' name='current' />
<el-tab-pane label='报工记录' name='record' />
<el-tab-pane label='工艺文件查看' name='document' />
</el-tabs>
</div>
<!-- Tab内容区域 -->
<div class='tabs-content-wrapper'>
<div v-show='activeTab === "pending"' class='tab-content'>
<PendingTasks ref='pendingTasksRef' :workstationId='workstationId' @issue='handleIssue' />
</div>
<div v-show='activeTab === "current"' class='tab-content'>
<CurrentTask ref='currentTaskRef' :workstationId='workstationId' @report-success='handleReportSuccess' />
</div>
<div v-show='activeTab === "record"' class='tab-content'>
<WorkReportRecord ref='recordRef' :workstationId='workstationId' />
</div>
<div v-show='activeTab === "document"' class='tab-content'>
<ProcessDocuments ref='documentRef' :workstationId='workstationId' />
</div>
</div>
<!-- 消息提醒 -->
<el-alert
v-if='messageText'
:title='messageText'
type='success'
:closable='false'
show-icon
class='message-alert'
/>
</div>
</template>
<script setup name='ProductionReport' lang='ts'>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { ArrowLeft, Menu } from '@element-plus/icons-vue';
import PendingTasks from './components/PendingTasks.vue';
import CurrentTask from './components/CurrentTask.vue';
import WorkReportRecord from './components/WorkReportRecord.vue';
import ProcessDocuments from './components/ProcessDocuments.vue';
import { parseTime } from '@/utils/ruoyi';
import useUserStore from '@/store/modules/user';
import { getStationInfoList } from '@/api/mes/baseStationInfo';
import { BaseStationInfoVO } from '@/api/mes/baseStationInfo/types';
const router = useRouter();
const userStore = useUserStore();
//
const userInfo = computed(() => userStore.userInfo || {});
// Tab
const activeTab = ref('pending');
const pendingTasksRef = ref();
const currentTaskRef = ref();
const recordRef = ref();
const documentRef = ref();
//
const productionLine = ref('DJ01');
const workstation = ref('DJ01-01'); //
const workstationId = ref<string | number>(''); // ID
const currentShift = ref('白班');
const workstationList = ref<BaseStationInfoVO[]>([]);
const filteredWorkstationList = ref<BaseStationInfoVO[]>([]);
const workstationSearchText = ref('');
//
const currentTime = ref('');
let timeInterval: any = null;
//
const messageText = ref('');
//
const updateTime = () => {
currentTime.value = parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}');
};
//
const handleBack = () => {
router.back();
};
// Tab
const handleTabChange = (tabName: string) => {
if (tabName === 'pending') {
//
pendingTasksRef.value?.refresh?.();
} else if (tabName === 'current') {
//
currentTaskRef.value?.refresh?.();
} else if (tabName === 'record') {
//
recordRef.value?.refresh?.();
}
};
//
const getWorkstationList = async () => {
try {
const res = await getStationInfoList({});
const data = res?.data || res;
if (data && Array.isArray(data)) {
workstationList.value = data;
filteredWorkstationList.value = data;
//
if (data.length > 0) {
const foundWorkstation = data.find(item => item.stationCode === workstation.value);
if (foundWorkstation) {
// IDwatch
workstationId.value = foundWorkstation.stationId;
} else {
// watch
workstation.value = data[0].stationCode;
workstationId.value = data[0].stationId;
}
}
}
} catch (error) {
console.error('获取工位列表失败:', error);
}
};
//
const filterWorkstation = (query: string) => {
workstationSearchText.value = query;
if (!query) {
filteredWorkstationList.value = workstationList.value;
return;
}
const lowerQuery = query.toLowerCase();
filteredWorkstationList.value = workstationList.value.filter(item => {
const stationName = (item.stationName || '').toLowerCase();
const stationCode = (item.stationCode || '').toLowerCase();
return stationName.includes(lowerQuery) || stationCode.includes(lowerQuery);
});
};
//
const refreshAllData = () => {
//
pendingTasksRef.value?.refresh?.();
//
currentTaskRef.value?.refresh?.();
//
recordRef.value?.refresh?.();
//
documentRef.value?.refresh?.();
};
//
const handleWorkstationChange = (stationId: string | number) => {
// ID
const selectedWorkstation = workstationList.value.find(item => item.stationId === stationId);
if (selectedWorkstation) {
workstation.value = selectedWorkstation.stationCode;
}
// IDwatch
workstationId.value = stationId;
};
//
const handleIssue = (planId: string | number) => {
//
messageText.value = `消息提醒: 工单号: ${planId} 开工成功!`;
// 3
setTimeout(() => {
messageText.value = '';
}, 3000);
// Tab
activeTab.value = 'current';
//
setTimeout(() => {
currentTaskRef.value?.refresh?.();
}, 500);
};
//
const handleReportSuccess = () => {
//
pendingTasksRef.value?.refresh?.();
//
recordRef.value?.refresh?.();
};
onMounted(() => {
updateTime();
timeInterval = setInterval(updateTime, 1000);
//
const route = router.currentRoute.value;
if (route.query.workstation) {
workstation.value = route.query.workstation as string;
}
if (route.query.productionLine) {
productionLine.value = route.query.productionLine as string;
}
if (route.query.workstationId) {
workstationId.value = route.query.workstationId as string | number;
}
//
getWorkstationList();
});
// ID
watch(
() => workstationId.value,
(newVal, oldVal) => {
// ID
if (newVal && newVal !== oldVal && oldVal !== undefined && oldVal !== '') {
// 使 nextTick prop
setTimeout(() => {
refreshAllData();
}, 0);
}
}
);
onUnmounted(() => {
if (timeInterval) {
clearInterval(timeInterval);
}
});
</script>
<style scoped lang='scss'>
.production-report-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
.header-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
background: #fff;
border-bottom: 1px solid #e4e7ed;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.header-left {
display: flex;
align-items: center;
gap: 20px;
flex: 1;
.back-icon {
font-size: 20px;
cursor: pointer;
color: #409eff;
&:hover {
color: #66b1ff;
}
}
.user-info,
.shift-info,
.line-info {
font-size: 14px;
}
.workstation-info {
font-size: 14px;
display: flex;
align-items: center;
}
}
.header-center {
flex: 1;
text-align: center;
.page-title {
margin: 0;
font-size: 20px;
font-weight: 500;
}
}
.header-right {
display: flex;
align-items: center;
gap: 15px;
flex: 1;
justify-content: flex-end;
.current-time {
font-size: 14px;
}
.menu-icon {
font-size: 20px;
cursor: pointer;
color: #606266;
&:hover {
color: #409eff;
}
}
}
}
.tabs-header-wrapper {
background: #fff;
margin: 0 10px;
border-radius: 4px 4px 0 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
flex-shrink: 0;
}
.report-tabs {
width: 100%;
:deep(.el-tabs__header) {
margin: 0;
padding: 0;
background: #fff;
border-bottom: 2px solid #e4e7ed;
}
:deep(.el-tabs__nav-wrap) {
padding: 0 20px;
&::after {
display: none;
}
}
:deep(.el-tabs__nav) {
display: flex;
border: none;
}
:deep(.el-tabs__item) {
height: 56px;
line-height: 56px;
font-size: 16px;
padding: 0 32px;
margin-right: 8px;
cursor: pointer;
transition: all 0.3s;
border: none;
color: #606266;
position: relative;
background: transparent;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
background: transparent;
transition: all 0.3s;
}
&.is-active {
color: #409eff;
font-weight: 600;
&::after {
background: #409eff;
}
}
&:hover:not(.is-active) {
color: #409eff;
background: #f5f7fa;
}
}
:deep(.el-tabs__active-bar) {
display: none;
}
:deep(.el-tabs__content) {
display: none;
}
}
.tabs-content-wrapper {
flex: 1;
overflow: hidden;
background: #fff;
margin: 0 10px 10px 10px;
border-radius: 0 0 4px 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
min-height: 0;
}
.tab-content {
width: 100%;
height: 100%;
overflow: auto;
padding: 20px;
}
.message-alert {
position: fixed;
top: 120px;
left: 50%;
transform: translateX(-50%);
z-index: 2000;
min-width: 400px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
</style>
Loading…
Cancel
Save