gaoluyang
15 小时以前 6c0b0dd81a98a3163e45f1b5b5417e02af165fd8
Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0
已修改9个文件
398 ■■■■■ 文件已修改
src/api/mes/pro/feedback/index.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pro/task/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/data.ts 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/modules/batch-feedback.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/modules/form.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/modules/task-form.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder-center/modules/quick-feedback.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/FeedbackTab.vue 124 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/workbench/components/tabs/TaskDetailTab.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/pro/feedback/index.ts
@@ -178,8 +178,6 @@
  taskId: number;
  paramValues?: MesProFeedbackApi.ParamValue[];
  feedbackQuantity: number;
  qualifiedQuantity?: number;
  unqualifiedQuantity?: number;
  remark?: string;
}
@@ -188,14 +186,14 @@
  feedbackId?: number;
  taskStatus?: number;
  workOrderStatus?: number;
  /** 报工后剩余可报工数量 */
  reportableQuantity?: number;
}
/** 批量报工请求 */
export interface BatchCreateReq {
  taskIds: number[];
  feedbackQuantity: number;
  qualifiedQuantity?: number;
  unqualifiedQuantity?: number;
  remark?: string;
}
src/api/mes/pro/task/index.ts
@@ -58,6 +58,7 @@
    pauseRecords?: PauseRecord[]; // 停工记录
    reportableQuantity?: number; // 可报工数量
    assigneeUserIds?: number[]; // 报工人
    feedbackApprove?: boolean; // 是否报工审批(派生自工单)
  }
  /** MES 生产任务分页查询参数 */
src/views/mes/pro/feedback/data.ts
@@ -174,9 +174,8 @@
 * - create / update:录入报工主体信息和数量
 * - submit / approve / detail:主体字段只读
 *
 * 数量区域根据 `checkFlag` 和 `unqualifiedQuantity` 动态显示:
 * - 非质检工序:报工数量 = 合格 + 不良;不良 > 0 时再展开工废/料废/其他废品
 * - 质检工序:只填报工数量(视为待检数量)
 * 报工只填写"报工数量";合格品/不良品数量由质检确定,报工环节不采集。
 * 质检工序报工后进入待检验(uncheckQuantity = 报工数量),非质检工序直接完结。
 */
export function useFormSchema(
  formType: FormType,
@@ -404,151 +403,9 @@
        class: 'w-full',
        min: 0,
        precision: 2,
      },
      dependencies: {
        triggerFields: ['checkFlag'],
        // 非质检工序时,报工数量 = 合格 + 不良,禁用直接编辑
        componentProps: (values) => ({
          class: 'w-full',
          disabled: !values.checkFlag,
          min: 0,
          placeholder: '请输入报工数量',
          precision: 2,
        }),
        placeholder: '请输入报工数量',
      },
      rules: 'required',
    },
    {
      fieldName: 'qualifiedQuantity',
      label: '合格品数量',
      component: 'InputNumber',
      componentProps: {
        class: 'w-full',
        min: 0,
        precision: 2,
        // 合格/不良变更,自动累计为报工数量
        onChange: async () => {
          const values = await formApi?.getValues();
          await formApi?.setFieldValue(
            'feedbackQuantity',
            (values?.qualifiedQuantity || 0) +
              (values?.unqualifiedQuantity || 0),
          );
        },
      },
      defaultValue: 0,
      dependencies: {
        triggerFields: ['checkFlag'],
        show: (values) => !values.checkFlag,
      },
    },
    {
      fieldName: 'unqualifiedQuantity',
      label: '不良品数量',
      component: 'InputNumber',
      componentProps: {
        class: 'w-full',
        min: 0,
        precision: 2,
        // 合格/不良变更,自动累计为报工数量
        onChange: async () => {
          const values = await formApi?.getValues();
          await formApi?.setFieldValue(
            'feedbackQuantity',
            (values?.qualifiedQuantity || 0) +
              (values?.unqualifiedQuantity || 0),
          );
        },
      },
      defaultValue: 0,
      dependencies: {
        triggerFields: ['checkFlag'],
        show: (values) => !values.checkFlag,
      },
    },
    {
      fieldName: 'laborScrapQuantity',
      label: '工废数量',
      component: 'InputNumber',
      componentProps: {
        class: 'w-full',
        min: 0,
        precision: 2,
        // 废品分类变更,自动累计为不良品数量及报工数量
        onChange: async () => {
          const values = await formApi?.getValues();
          const unqualified =
            (values?.laborScrapQuantity || 0) +
            (values?.materialScrapQuantity || 0) +
            (values?.otherScrapQuantity || 0);
          await formApi?.setValues({
            feedbackQuantity: (values?.qualifiedQuantity || 0) + unqualified,
            unqualifiedQuantity: unqualified,
          });
        },
      },
      defaultValue: 0,
      dependencies: {
        triggerFields: ['checkFlag', 'unqualifiedQuantity'],
        show: (values) =>
          !values.checkFlag && (values.unqualifiedQuantity || 0) > 0,
      },
    },
    {
      fieldName: 'materialScrapQuantity',
      label: '料废数量',
      component: 'InputNumber',
      componentProps: {
        class: 'w-full',
        min: 0,
        precision: 2,
        // 废品分类变更,自动累计为不良品数量及报工数量
        onChange: async () => {
          const values = await formApi?.getValues();
          const unqualified =
            (values?.laborScrapQuantity || 0) +
            (values?.materialScrapQuantity || 0) +
            (values?.otherScrapQuantity || 0);
          await formApi?.setValues({
            feedbackQuantity: (values?.qualifiedQuantity || 0) + unqualified,
            unqualifiedQuantity: unqualified,
          });
        },
      },
      defaultValue: 0,
      dependencies: {
        triggerFields: ['checkFlag', 'unqualifiedQuantity'],
        show: (values) =>
          !values.checkFlag && (values.unqualifiedQuantity || 0) > 0,
      },
    },
    {
      fieldName: 'otherScrapQuantity',
      label: '其他废品',
      component: 'InputNumber',
      componentProps: {
        class: 'w-full',
        min: 0,
        precision: 2,
        // 废品分类变更,自动累计为不良品数量及报工数量
        onChange: async () => {
          const values = await formApi?.getValues();
          const unqualified =
            (values?.laborScrapQuantity || 0) +
            (values?.materialScrapQuantity || 0) +
            (values?.otherScrapQuantity || 0);
          await formApi?.setValues({
            feedbackQuantity: (values?.qualifiedQuantity || 0) + unqualified,
            unqualifiedQuantity: unqualified,
          });
        },
      },
      defaultValue: 0,
      dependencies: {
        triggerFields: ['checkFlag', 'unqualifiedQuantity'],
        show: (values) =>
          !values.checkFlag && (values.unqualifiedQuantity || 0) > 0,
      },
    },
    {
      fieldName: 'feedbackUserId',
@@ -616,8 +473,8 @@
      render: (val) =>
        h(DictTag, { type: DICT_TYPE.MES_PRO_FEEDBACK_TYPE, value: val }),
    },
    { field: 'workOrderId', label: '生产订单', render: (_val, data) => data.workOrderCode },
    { field: 'taskId', label: '生产任务', render: (_val, data) => data.taskCode },
    { field: 'workOrderId', label: '生产订单', render: (_val, data) => data?.workOrderCode },
    { field: 'taskId', label: '生产任务', render: (_val, data) => data?.taskCode },
    { field: 'workstationName', label: '工作站' },
    { field: 'itemCode', label: '产品编码' },
    { field: 'itemName', label: '产品名称' },
src/views/mes/pro/feedback/modules/batch-feedback.vue
@@ -24,8 +24,6 @@
      selectedTasks.value = [];
      formData.value = {
        feedbackQuantity: undefined,
        qualifiedQuantity: undefined,
        unqualifiedQuantity: undefined,
        remark: '',
      };
    }
@@ -36,8 +34,6 @@
const selectedTasks = ref<MesProTaskApi.Task[]>([]);
const formData = ref({
  feedbackQuantity: undefined as number | undefined,
  qualifiedQuantity: undefined as number | undefined,
  unqualifiedQuantity: undefined as number | undefined,
  remark: '',
});
const submitting = ref(false);
@@ -68,8 +64,6 @@
    const result = await batchCreateFeedback({
      taskIds: selectedTasks.value.map((t) => t.id!),
      feedbackQuantity: formData.value.feedbackQuantity,
      qualifiedQuantity: formData.value.qualifiedQuantity,
      unqualifiedQuantity: formData.value.unqualifiedQuantity,
      remark: formData.value.remark || undefined,
    });
    if (result.failCount) {
@@ -113,24 +107,6 @@
          :min="0.01"
          :precision="2"
          placeholder="请输入报工数量(应用到所有选中任务)"
          class="!w-full"
        />
      </a-form-item>
      <a-form-item label="合格品数量">
        <a-input-number
          v-model:value="formData.qualifiedQuantity"
          :min="0"
          :precision="2"
          placeholder="请输入合格品数量"
          class="!w-full"
        />
      </a-form-item>
      <a-form-item label="不良品数量">
        <a-input-number
          v-model:value="formData.unqualifiedQuantity"
          :min="0"
          :precision="2"
          placeholder="请输入不良品数量"
          class="!w-full"
        />
      </a-form-item>
src/views/mes/pro/feedback/modules/form.vue
@@ -87,20 +87,14 @@
  wrapperClass: 'grid-cols-3',
});
/** 提交前对齐数量:根据 checkFlag 决定 uncheck/合格/不良归零策略 */
/** 提交前对齐数量:报工只填写报工数量,合格/不良/废品数量由质检确定,报工环节不采集 */
function alignQuantity(data: MesProFeedbackApi.Feedback) {
  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;
}
/** 读取并按提交规则对齐后的表单数据,用于快照和保存 */
@@ -149,7 +143,7 @@
}
const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
const batchActionType = ref<'submit' | 'approve'>('');
const batchActionType = ref<'' | 'submit' | 'approve'>('');
function syncParamValues(data: MesProFeedbackApi.Feedback) {
  data.paramValues = paramDetails.value.map((detail) => ({
src/views/mes/pro/task/modules/task-form.vue
@@ -85,6 +85,8 @@
    let wid: number | undefined;
    if (data?.id) {
      // 编辑模式:先设置表单 schema,再回填任务数据,避免字段未建立时 setValues 丢失
      formApi.setState({ schema: useTaskFormSchema(formApi, workOrderQuantity) });
      modalApi.lock();
      try {
        formData.value = await getTask(data.id);
@@ -104,14 +106,14 @@
      };
      wid = data.workOrderId;
      workOrderQuantity.value = data.quantity;
      // 新增模式:先设置表单 schema,再设置初始值
      formApi.setState({ schema: useTaskFormSchema(formApi, workOrderQuantity) });
      await formApi.setValues({
        colorCode: data.colorCode || '#00AEF3',
        duration: 1,
        quantity: data.quantity,
      });
    }
    formApi.setState({ schema: useTaskFormSchema(formApi, workOrderQuantity) });
  },
});
</script>
src/views/mes/pro/workorder-center/modules/quick-feedback.vue
@@ -9,6 +9,10 @@
import { message } from 'ant-design-vue';
import { quickCreateFeedback } from '#/api/mes/pro/feedback';
import {
  loadParamDetails,
  paramDetails,
} from '#/views/mes/pro/feedback/param-state';
import ProTaskSelect from '#/views/mes/pro/task/components/select.vue';
defineOptions({ name: 'QuickFeedbackForm' });
@@ -35,10 +39,14 @@
      selectedTask.value = undefined;
      formData.value = {
        feedbackQuantity: undefined,
        qualifiedQuantity: undefined,
        unqualifiedQuantity: undefined,
        remark: '',
      };
      // 预设任务时加载其工艺参数;否则等待选择任务后再加载
      if (props.taskId) {
        loadParamDetails(props.taskId);
      } else {
        paramDetails.value = [];
      }
    }
  },
});
@@ -48,8 +56,6 @@
const selectedTask = ref<MesProTaskApi.Task>();
const formData = ref({
  feedbackQuantity: undefined as number | undefined,
  qualifiedQuantity: undefined as number | undefined,
  unqualifiedQuantity: undefined as number | undefined,
  remark: '',
});
const submitting = ref(false);
@@ -70,6 +76,7 @@
function handleTaskChange(task: MesProTaskApi.Task | undefined) {
  selectedTask.value = task;
  selectedTaskId.value = task?.id;
  loadParamDetails(task?.id);
}
async function handleSubmit() {
@@ -87,8 +94,10 @@
    await quickCreateFeedback({
      taskId,
      feedbackQuantity: formData.value.feedbackQuantity,
      qualifiedQuantity: formData.value.qualifiedQuantity,
      unqualifiedQuantity: formData.value.unqualifiedQuantity,
      paramValues: paramDetails.value.map((detail) => ({
        detailId: detail.id,
        value: detail.paramValue,
      })),
      remark: formData.value.remark || undefined,
    });
    message.success('报工成功');
@@ -128,24 +137,27 @@
          class="!w-full"
        />
      </a-form-item>
      <a-form-item label="合格品数量">
        <a-input-number
          v-model:value="formData.qualifiedQuantity"
          :min="0"
          :precision="2"
          placeholder="请输入合格品数量"
          class="!w-full"
        />
      </a-form-item>
      <a-form-item label="不良品数量">
        <a-input-number
          v-model:value="formData.unqualifiedQuantity"
          :min="0"
          :precision="2"
          placeholder="请输入不良品数量"
          class="!w-full"
        />
      </a-form-item>
      <div v-if="paramDetails.length" class="mb-4">
        <div class="mb-1 font-medium">工艺参数</div>
        <div
          v-for="detail in paramDetails"
          :key="detail.id"
          class="mb-2 flex items-center gap-2"
        >
          <span class="w-40 flex-shrink-0 text-right text-sm text-gray-600">
            <span v-if="detail.required" class="text-red-500">*&nbsp;</span>
            {{ detail.paramName || detail.paramCode
            }}{{
              detail.unitMeasureName ? `(${detail.unitMeasureName})` : ''
            }}
          </span>
          <a-input
            v-model:value="detail.paramValue"
            placeholder="请输入参数值"
            class="!w-full"
          />
        </div>
      </div>
      <a-form-item label="备注">
        <a-textarea
          v-model:value="formData.remark"
src/views/mes/workbench/components/tabs/FeedbackTab.vue
@@ -1,5 +1,5 @@
<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';
@@ -12,7 +12,7 @@
} 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';
@@ -29,9 +29,13 @@
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 batchActionType = ref<'' | 'submit' | 'approve'>('');
const pendingSubmitMode = ref<'saveAndSubmit' | 'submit'>('submit');
defineOptions({ name: 'WorkbenchFeedbackTab' });
@@ -73,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 */
@@ -117,6 +124,8 @@
  const code = await generateAutoCode(MesAutoCodeRuleCode.PRO_FEEDBACK_CODE);
  const checkFlag = await resolveCheckFlag(task.routeId, task.processId);
  // 加载任务绑定的工艺参数(必填参数校验 + 参数值回显)
  await loadParamDetails(task.id);
  await formApi.setValues({
    code,
@@ -133,6 +142,7 @@
    itemName: task.itemName,
    itemSpecification: task.itemSpecification,
    unitMeasureName: task.unitMeasureName,
    reportableQuantity: task.reportableQuantity,
    scheduledQuantity: task.quantity,
    feedbackApprove: task.feedbackApprove,
    checkFlag,
@@ -210,7 +220,7 @@
  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;
@@ -242,7 +252,7 @@
  }
  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('报工单已提交');
@@ -328,13 +338,15 @@
  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());
}
// 监听任务变更(仅当任务 ID 真正变化时才重置表单)
// 监听任务变更:任务对象变化(选择任务 / 列表刷新后同步的最新数据)时重置表单,
// 确保"可报工数量"等任务派生字段始终展示最新值
watch(
  () => props.task?.id,
  () => props.task,
  () => {
    if (props.task) {
      resetForTask(props.task);
@@ -364,6 +376,34 @@
      </div>
      <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
@@ -456,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>
src/views/mes/workbench/components/tabs/TaskDetailTab.vue
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { PendingTask } from '../../../types';
import type { PendingTask } from '../../types';
import { Descriptions, Progress, Tag, Timeline, TimelineItem } from 'ant-design-vue';