| | |
| | | <script lang="ts" setup> |
| | | import type { PendingTask } from '../../../types'; |
| | | import type { PendingTask } from '../../types'; |
| | | import type { FormType } from '../../../pro/feedback/data'; |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | |
| | | } from '@vben/constants'; |
| | | import { useUserStore } from '@vben/stores'; |
| | | |
| | | import { Button, message, Modal, Popconfirm } from 'ant-design-vue'; |
| | | import { Button, Input, message, Modal, Popconfirm } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | |
| | | updateFeedback, |
| | | } from '#/api/mes/pro/feedback'; |
| | | import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process'; |
| | | import { getTask } from '#/api/mes/pro/task'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../../../pro/feedback/data'; |
| | | import { |
| | | loadParamDetails, |
| | | paramDetails, |
| | | resetWaitConfig, |
| | | waitDuration, |
| | | waitStartTime, |
| | | waitTaskId, |
| | | } from '../../../pro/feedback/param-state'; |
| | | import BatchInputDialog from '../../../pro/feedback/modules/batch-input-dialog.vue'; |
| | | import WaitCountdown from '../../../pro/feedback/modules/wait-countdown.vue'; |
| | | const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>(); |
| | | const batchActionType = ref<'submit' | 'approve'>(''); |
| | | const batchActionType = ref<'' | 'submit' | 'approve'>(''); |
| | | const pendingSubmitMode = ref<'saveAndSubmit' | 'submit'>('submit'); |
| | | defineOptions({ name: 'WorkbenchFeedbackTab' }); |
| | | |
| | |
| | | wrapperClass: 'grid-cols-3', |
| | | }); |
| | | |
| | | /** 根据 checkFlag 对齐数量 */ |
| | | /** 提交前对齐数量:报工只填写报工数量,合格/不良/废品数量由质检确定,报工环节不采集 */ |
| | | function alignQuantity(data: any) { |
| | | if (data.checkFlag) { |
| | | data.uncheckQuantity = data.feedbackQuantity; |
| | | data.qualifiedQuantity = 0; |
| | | data.unqualifiedQuantity = 0; |
| | | data.laborScrapQuantity = 0; |
| | | data.materialScrapQuantity = 0; |
| | | data.otherScrapQuantity = 0; |
| | | } else { |
| | | data.feedbackQuantity = |
| | | (data.qualifiedQuantity || 0) + (data.unqualifiedQuantity || 0); |
| | | data.uncheckQuantity = 0; |
| | | if (data.needReportQuantity === false) { |
| | | data.feedbackQuantity = 0; |
| | | } |
| | | data.uncheckQuantity = data.checkFlag ? data.feedbackQuantity : 0; |
| | | delete data.qualifiedQuantity; |
| | | delete data.unqualifiedQuantity; |
| | | delete data.laborScrapQuantity; |
| | | delete data.materialScrapQuantity; |
| | | delete data.otherScrapQuantity; |
| | | delete data.needReportQuantity; |
| | | } |
| | | |
| | | async function getAlignedData() { |
| | | const data = (await formApi.getValues()) as any; |
| | | syncParamValues(data); |
| | | alignQuantity(data); |
| | | return data; |
| | | } |
| | | |
| | | /** 加载 checkFlag */ |
| | | async function resolveCheckFlag(routeId?: number, processId?: number) { |
| | | if (!routeId || !processId) return true; |
| | | /** 同步工艺参数值到报工数据(与提交数据一同发送给后端) */ |
| | | function syncParamValues(data: any) { |
| | | data.paramValues = paramDetails.value.map((detail) => ({ |
| | | detailId: detail.id, |
| | | value: detail.paramValue, |
| | | })); |
| | | } |
| | | |
| | | /** 加载工序配置(checkFlag / 等待时长 / 报工数量规则) */ |
| | | async function resolveRouteProcessConfig(routeId?: number, processId?: number) { |
| | | if (!routeId || !processId) { |
| | | return { checkFlag: true, needReportQuantity: true }; |
| | | } |
| | | try { |
| | | const rp = await getRouteProcessByRouteAndProcess(routeId, processId); |
| | | return rp?.checkFlag ?? false; |
| | | waitDuration.value = rp?.waitDuration; |
| | | return { |
| | | checkFlag: rp?.checkFlag ?? false, |
| | | needReportQuantity: rp?.needReportQuantity ?? true, |
| | | }; |
| | | } catch { |
| | | return true; |
| | | waitDuration.value = undefined; |
| | | return { checkFlag: true, needReportQuantity: true }; |
| | | } |
| | | } |
| | | |
| | |
| | | feedbackId.value = undefined; |
| | | feedbackStatus.value = undefined; |
| | | originalSnapshot.value = ''; |
| | | resetWaitConfig(); |
| | | |
| | | formApi.setState({ schema: useFormSchema(formType.value, formApi) }); |
| | | |
| | | const code = await generateAutoCode(MesAutoCodeRuleCode.PRO_FEEDBACK_CODE); |
| | | const checkFlag = await resolveCheckFlag(task.routeId, task.processId); |
| | | const config = await resolveRouteProcessConfig(task.routeId, task.processId); |
| | | // 加载任务绑定的工艺参数(必填参数校验 + 参数值回显) |
| | | await loadParamDetails(task.id); |
| | | |
| | | waitTaskId.value = task.id; |
| | | waitStartTime.value = task.waitStartTime; |
| | | |
| | | await formApi.setValues({ |
| | | code, |
| | |
| | | itemName: task.itemName, |
| | | itemSpecification: task.itemSpecification, |
| | | unitMeasureName: task.unitMeasureName, |
| | | reportableQuantity: task.reportableQuantity, |
| | | scheduledQuantity: task.quantity, |
| | | feedbackApprove: task.feedbackApprove, |
| | | checkFlag, |
| | | checkFlag: config.checkFlag, |
| | | needReportQuantity: config.needReportQuantity, |
| | | }); |
| | | // 清空上次编辑/创建时写入表单的 id,避免新增报工时误带旧主键导致主键冲突 |
| | | await formApi.setFieldValue('id', undefined); |
| | | originalSnapshot.value = JSON.stringify(await getAlignedData()); |
| | | } |
| | | |
| | |
| | | pendingSubmitMode.value = 'saveAndSubmit'; |
| | | |
| | | // 4. 调用提交接口 |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task.feedbackApprove); |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task?.feedbackApprove); |
| | | if (!isDone) return; |
| | | |
| | | feedbackId.value = record.id; |
| | |
| | | } |
| | | |
| | | pendingSubmitMode.value = 'submit'; |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task.feedbackApprove); |
| | | const isDone = await handleOnSubmit(feedbackId.value!,props.task?.feedbackApprove); |
| | | if (!isDone) return; |
| | | |
| | | message.success('报工单已提交'); |
| | |
| | | feedbackStatus.value = data.status; |
| | | formType.value = 'update'; |
| | | formApi.setState({ schema: useFormSchema(formType.value, formApi) }); |
| | | const checkFlag = await resolveCheckFlag(data.routeId, data.processId); |
| | | await formApi.setValues({ ...data, checkFlag }); |
| | | const config = await resolveRouteProcessConfig(data.routeId, data.processId); |
| | | await loadParamDetails(data.taskId); |
| | | waitTaskId.value = data.taskId; |
| | | waitStartTime.value = undefined; |
| | | if (data.taskId) { |
| | | try { |
| | | const task = await getTask(data.taskId); |
| | | waitStartTime.value = task?.waitStartTime; |
| | | } catch { |
| | | // 任务不存在时忽略倒计时回显 |
| | | } |
| | | } |
| | | await formApi.setValues({ |
| | | ...data, |
| | | checkFlag: config.checkFlag, |
| | | needReportQuantity: config.needReportQuantity, |
| | | }); |
| | | originalSnapshot.value = JSON.stringify(await getAlignedData()); |
| | | } |
| | | |
| | | // 监听任务变更(仅当任务 ID 真正变化时才重置表单) |
| | | // 监听任务变更:任务对象变化(选择任务 / 列表刷新后同步的最新数据)时重置表单, |
| | | // 确保"可报工数量"等任务派生字段始终展示最新值 |
| | | watch( |
| | | () => props.task?.id, |
| | | () => props.task, |
| | | () => { |
| | | if (props.task) { |
| | | resetForTask(props.task); |
| | |
| | | </div> |
| | | |
| | | <Form class="feedback-tab__form" /> |
| | | |
| | | <!-- 报工倒计时(工序配置等待时长时展示) --> |
| | | <div v-if="waitDuration" class="feedback-tab__params"> |
| | | <div class="feedback-tab__params-title">报工倒计时</div> |
| | | <WaitCountdown |
| | | :task-id="waitTaskId" |
| | | :wait-duration="waitDuration" |
| | | :wait-start-time="waitStartTime" |
| | | :disabled="!isEditable" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- 工艺参数(任务绑定参数时展示,必填参数未填会被后端拦截) --> |
| | | <div v-if="paramDetails.length" class="feedback-tab__params"> |
| | | <div class="feedback-tab__params-title">工艺参数</div> |
| | | <div class="feedback-tab__params-grid"> |
| | | <div |
| | | v-for="detail in paramDetails" |
| | | :key="detail.id" |
| | | class="feedback-tab__params-item" |
| | | > |
| | | <span class="feedback-tab__params-label"> |
| | | <span v-if="detail.required" class="feedback-tab__params-required" |
| | | >* </span |
| | | > |
| | | {{ detail.paramName || detail.paramCode |
| | | }}{{ |
| | | detail.unitMeasureName ? `(${detail.unitMeasureName})` : '' |
| | | }} |
| | | </span> |
| | | <Input |
| | | v-model:value="detail.paramValue" |
| | | :disabled="!isEditable" |
| | | placeholder="请输入参数值" |
| | | class="feedback-tab__params-input" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="feedback-tab__actions"> |
| | | <Button |
| | |
| | | border-top: 1px solid #f0f0f0; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .feedback-tab__params { |
| | | margin-top: 16px; |
| | | padding: 14px 16px; |
| | | border: 1px solid #f0f0f0; |
| | | border-radius: 8px; |
| | | background: #fafbfc; |
| | | } |
| | | |
| | | .feedback-tab__params-title { |
| | | margin-bottom: 12px; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | } |
| | | |
| | | .feedback-tab__params-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 12px 20px; |
| | | } |
| | | |
| | | .feedback-tab__params-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .feedback-tab__params-label { |
| | | flex-shrink: 0; |
| | | width: 176px; |
| | | text-align: right; |
| | | font-size: 14px; |
| | | color: #595959; |
| | | } |
| | | |
| | | .feedback-tab__params-required { |
| | | color: #ff4d4f; |
| | | } |
| | | |
| | | .feedback-tab__params-input { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | </style> |