src/views/mes/workbench/components/tabs/FeedbackTab.vue
@@ -1,8 +1,10 @@
<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';
import dayjs from 'dayjs';
import {
  MesAutoCodeRuleCode,
@@ -10,14 +12,15 @@
} from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { Button, message, 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';
import {
  approveFeedback,
  createFeedback,
  createFeedback, getBatchRequirements,
  getFeedback,
  getFeedbackPage,
  rejectFeedback,
  submitFeedback,
  updateFeedback,
@@ -26,7 +29,14 @@
import { $t } from '#/locales';
import { useFormSchema } from '../../../pro/feedback/data';
import {
  loadParamDetails,
  paramDetails,
} from '../../../pro/feedback/param-state';
import BatchInputDialog from '../../../pro/feedback/modules/batch-input-dialog.vue';
const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
const batchActionType = ref<'' | 'submit' | 'approve'>('');
const pendingSubmitMode = ref<'saveAndSubmit' | 'submit'>('submit');
defineOptions({ name: 'WorkbenchFeedbackTab' });
const props = defineProps<{
@@ -67,26 +77,29 @@
  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;
  }
  data.uncheckQuantity = data.checkFlag ? data.feedbackQuantity : 0;
  delete data.qualifiedQuantity;
  delete data.unqualifiedQuantity;
  delete data.laborScrapQuantity;
  delete data.materialScrapQuantity;
  delete data.otherScrapQuantity;
}
async function getAlignedData() {
  const data = (await formApi.getValues()) as any;
  syncParamValues(data);
  alignQuantity(data);
  return data;
}
/** 同步工艺参数值到报工数据(与提交数据一同发送给后端) */
function syncParamValues(data: any) {
  data.paramValues = paramDetails.value.map((detail) => ({
    detailId: detail.id,
    value: detail.paramValue,
  }));
}
/** 加载 checkFlag */
@@ -111,11 +124,13 @@
  const code = await generateAutoCode(MesAutoCodeRuleCode.PRO_FEEDBACK_CODE);
  const checkFlag = await resolveCheckFlag(task.routeId, task.processId);
  // 加载任务绑定的工艺参数(必填参数校验 + 参数值回显)
  await loadParamDetails(task.id);
  await formApi.setValues({
    code,
    type: 1,
    feedbackTime: Date.now(),
    feedbackTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
    feedbackUserId: userStore.userInfo?.id,
    taskId: task.id,
    workOrderId: task.workOrderId,
@@ -127,10 +142,11 @@
    itemName: task.itemName,
    itemSpecification: task.itemSpecification,
    unitMeasureName: task.unitMeasureName,
    reportableQuantity: task.reportableQuantity,
    scheduledQuantity: task.quantity,
    feedbackApprove: task.feedbackApprove,
    checkFlag,
  });
  originalSnapshot.value = JSON.stringify(await getAlignedData());
}
@@ -140,6 +156,7 @@
  if (!valid) return;
  const data = await getAlignedData();
  const code = data.code as string;
  if (formType.value === 'create') {
    const id = await createFeedback(data);
@@ -148,12 +165,78 @@
    formType.value = 'update';
    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
    await formApi.setFieldValue('id', id);
    message.success($t('common.createSuccess'));
  } else {
    await updateFeedback(data);
    message.success($t('common.updateSuccess'));
  }
  originalSnapshot.value = JSON.stringify(data);
  // 显示显眼的成功提示
  Modal.success({
    title: '保存成功',
    content: `报工单 ${code} 已保存`,
    centered: true,
  });
  // 重置表单为新建模式,重新生成报订单号
  if (props.task) {
    await resetForTask(props.task);
  }
  emit('reported');
}
/** 保存并提交(先保存,再查询并提交) */
async function handleSaveAndSubmit() {
  const { valid } = await formApi.validate();
  if (!valid) return;
  const data = await getAlignedData();
  const code = data.code as string;
  // 1. 先保存
  if (formType.value === 'create') {
    const id = await createFeedback(data);
    feedbackId.value = id;
    feedbackStatus.value = MesProFeedbackStatusEnum.PREPARE;
    formType.value = 'update';
    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
    await formApi.setFieldValue('id', id);
  } else {
    await updateFeedback(data);
  }
  originalSnapshot.value = JSON.stringify(data);
  // 2. 查询报工记录,匹配该报工单号
  const pageResult = await getFeedbackPage({ code, pageNo: 1, pageSize: 1 });
  const record = pageResult.list?.[0];
  // 3. 检查是否可提交(草稿状态)
  if (!record || record.status !== MesProFeedbackStatusEnum.PREPARE) {
    message.warning('保存成功,但当前报工单状态不可提交');
    emit('reported');
    return;
  }
  pendingSubmitMode.value = 'saveAndSubmit';
  // 4. 调用提交接口
  const isDone = await handleOnSubmit(feedbackId.value!,props.task?.feedbackApprove);
  if (!isDone) return;
  feedbackId.value = record.id;
  feedbackStatus.value = record.status;
  // 显示显眼的成功提示
  Modal.success({
    title: '保存并提交成功',
    content: `报工单 ${code} 已提交`,
    centered: true,
  });
  // 重置表单为新建模式,重新生成报订单号
  if (props.task) {
    await resetForTask(props.task);
  }
  emit('reported');
}
@@ -167,8 +250,12 @@
    await updateFeedback(data);
    originalSnapshot.value = JSON.stringify(data);
  }
  await submitFeedback(feedbackId.value!);
  message.success('报订单已提交');
  pendingSubmitMode.value = 'submit';
  const isDone = await handleOnSubmit(feedbackId.value!,props.task?.feedbackApprove);
  if (!isDone) return;
  message.success('报工单已提交');
  // 重置表单为新建模式
  if (props.task) {
    await resetForTask(props.task);
@@ -176,10 +263,53 @@
  emit('reported');
}
/** 提交方法 */
async function handleOnSubmit(id:any,feedbackRate:any): Promise<boolean> {
  if (feedbackRate&&feedbackRate==true){
    await submitFeedback({ id: feedbackId.value! });
    return true;
  }
  const checkResult = await getBatchRequirements(id);
  if (checkResult?.needBatchInput) {
    batchActionType.value = 'submit';
    batchDialogRef.value?.open({ checkResult });
    return false;
  } else {
    await submitFeedback({ id: feedbackId.value! });
    return true;
  }
}
/** 批次确认回调 */
async function handleBatchConfirm(batchData: Record<string, unknown>) {
  if (batchActionType.value === 'submit') {
    await submitFeedback({ id: feedbackId.value!, ...batchData });
    if (pendingSubmitMode.value === 'saveAndSubmit') {
      const data = await getAlignedData();
      const code = data.code as string;
      Modal.success({
        title: '保存并提交成功',
        content: `报工单 ${code} 已提交`,
        centered: true,
      });
    } else {
      message.success('报工单已提交');
    }
    if (props.task) {
      await resetForTask(props.task);
    }
    emit('reported');
  }
}
/** 审批通过 */
async function handleApprove() {
  if (!feedbackId.value) return;
  const finished = await approveFeedback(feedbackId.value);
  const finished = await approveFeedback({ id: feedbackId.value });
  message.success(
    finished ? '报订单已审批完成' : '报工成功,请等待质量检验完成!',
  );
@@ -208,16 +338,18 @@
  formType.value = 'update';
  formApi.setState({ schema: useFormSchema(formType.value, formApi) });
  const checkFlag = await resolveCheckFlag(data.routeId, data.processId);
  await loadParamDetails(data.taskId);
  await formApi.setValues({ ...data, checkFlag });
  originalSnapshot.value = JSON.stringify(await getAlignedData());
}
// 监听任务变更
// 监听任务变更:任务对象变化(选择任务 / 列表刷新后同步的最新数据)时重置表单,
// 确保"可报工数量"等任务派生字段始终展示最新值
watch(
  () => props.task,
  (task) => {
    if (task) {
      resetForTask(task);
  () => {
    if (props.task) {
      resetForTask(props.task);
    }
  },
  { immediate: true },
@@ -229,6 +361,7 @@
<template>
  <div class="feedback-tab">
    <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" />
    <div v-if="!task" class="feedback-tab__empty">
      请从左侧工单列表选择一条任务
    </div>
@@ -244,6 +377,34 @@
      <Form class="feedback-tab__form" />
      <!-- 工艺参数(任务绑定参数时展示,必填参数未填会被后端拦截) -->
      <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"
                >*&nbsp;</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
          v-if="isEditable"
@@ -251,6 +412,13 @@
          @click="handleSave"
        >
          保存
        </Button>
        <Button
          v-if="isEditable"
          type="primary"
          @click="handleSaveAndSubmit"
        >
          保存并提交
        </Button>
        <Popconfirm
          v-if="canSubmit"
@@ -328,4 +496,48 @@
  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>