src/views/productionManagement/productionReporting/components/ReportingDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,588 @@
<template>
  <el-dialog v-model="localVisible"
             :title="dialogTitle"
             width="800px"
             @close="handleClose">
    <!-- æ­¥éª¤æ¡ -->
    <el-steps :active="activeStep"
              finish-status="success">
      <el-step title="选择生产订单" />
      <el-step title="填写基础信息" />
      <el-step title="查看工序参数" />
      <el-step title="填写产量信息" />
    </el-steps>
    <!-- ç¬¬ä¸€æ­¥ï¼šé€‰æ‹©ç”Ÿäº§è®¢å• -->
    <div v-if="activeStep === 0">
      <el-form :model="form"
               ref="formRef"
               label-width="120px">
        <el-form-item label="生产订单"
                      prop="orderId"
                      required>
          <el-select v-model="orderId"
                     placeholder="请选择生产订单"
                     clearable
                     filterable
                     style="width: 100%"
                     :loading="orderLoading"
                     @change="handleOrderChange">
            <el-option v-for="order in orderList"
                       :key="order.id"
                       :label="`${order.npsNo} - ${order.productName} ${order.model}`"
                       :value="order.id" />
          </el-select>
        </el-form-item>
      </el-form>
    </div>
    <!-- ç¬¬äºŒæ­¥ï¼šå¡«å†™åŸºç¡€ä¿¡æ¯ -->
    <div v-else-if="activeStep === 1">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="生产订单号"
                      prop="npsNo">
          <el-input disabled
                    v-model="form.npsNo" />
        </el-form-item>
        <el-form-item label="班组"
                      prop="teamName"
                      required>
          <el-select v-model="form.teamName"
                     placeholder="请选择班组"
                     style="width: 100%">
            <el-option label="白班"
                       value="白班" />
            <el-option label="夜班"
                       value="夜班" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品编码"
                      prop="materialCode">
          <el-input disabled
                    v-model="form.materialCode" />
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productName">
          <el-input disabled
                    v-model="form.productName" />
        </el-form-item>
        <el-form-item label="规格"
                      prop="specification">
          <el-input disabled
                    v-model="form.specification" />
        </el-form-item>
        <el-form-item label="创建人"
                      prop="createBy"
                      required>
          <el-input v-model="form.createBy"
                    placeholder="请输入创建人" />
        </el-form-item>
        <el-form-item label="创建时间"
                      prop="createTime">
          <el-date-picker disabled
                          v-model="form.createTime"
                          type="datetime"
                          placeholder="请选择创建时间"
                          style="width: 100%" />
        </el-form-item>
      </el-form>
    </div>
    <!-- ç¬¬ä¸‰æ­¥ï¼šæŸ¥çœ‹å·¥åºå‚æ•° -->
    <div v-else-if="activeStep === 2">
      <!-- å·¥åºTab页 -->
      <el-tabs v-model="activeProcessId"
               @tab-click="handleTabClick">
        <el-tab-pane v-for="process in processList"
                     :key="process.id"
                     :label="process.processName"
                     :name="process.id + ''">
          <div>
            <!-- å‚数组列表 -->
            <div v-for="(group, groupIndex) in form.paramGroups[process.id] || []"
                 :key="groupIndex"
                 class="param-group">
              <div class="group-header">
                <span>参数组 {{ groupIndex + 1 }}</span>
                <el-button type="danger"
                           size="small"
                           @click="removeParamGroup(process.id, groupIndex)"
                           v-if="(form.paramGroups[process.id] || []).length > 1">
                  åˆ é™¤
                </el-button>
              </div>
              <el-form :model="form"
                       ref="formRef"
                       label-width="120px">
                <!-- åŠ¨æ€å‚æ•° -->
                <el-form-item v-for="param in params"
                              :key="param.id"
                              :label="param.paramName">
                  <template v-if="param.paramType == '1'">
                    <!-- æ•°å­—类型 -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-input-number v-model="group[param.id]"
                                       controls-position="right"
                                       :precision="getPrecision(param.paramFormat)"
                                       style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else-if="param.paramType == '2'">
                    <!-- æ–‡æœ¬ç±»åž‹ -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-input v-model="group[param.id]"
                                style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else-if="param.paramType == '3'">
                    <!-- å­—典类型 -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-select v-model="group[param.id]"
                                 placeholder="请选择"
                                 style="flex: 1;width: 150px">
                        <el-option v-for="option in dictOptions[param.paramFormat] || []"
                                   :key="option.dictValue"
                                   :label="option.dictLabel"
                                   :value="option.dictValue" />
                      </el-select>
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else-if="param.paramType == '4'">
                    <!-- æ—¥æœŸç±»åž‹ -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-date-picker :value-format="param.paramFormat"
                                      :format="param.paramFormat"
                                      :type="param.paramFormat=='YYYY-MM-DD'?'daterange':'datetimerange'"
                                      v-model="group[param.id]"
                                      style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                  <template v-else>
                    <!-- å…¶ä»–类型 -->
                    <div style="display: flex; align-items: center; gap: 8px;">
                      <el-input v-model="group[param.id]"
                                style="flex: 1" />
                      <span v-if="param.unit && param.unit != '/'">
                        {{ param.unit }}
                      </span>
                    </div>
                  </template>
                </el-form-item>
              </el-form>
            </div>
            <!-- æ–°å¢žå‚数组按钮 -->
            <el-button type="primary"
                       size="small"
                       @click="addParamGroup(process.id)">
              æ–°å¢žå‚数组
            </el-button>
          </div>
        </el-tab-pane>
      </el-tabs>
    </div>
    <!-- ç¬¬å››æ­¥ï¼šå¡«å†™äº§é‡ä¿¡æ¯ -->
    <div v-else-if="activeStep === 3">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="120px">
        <el-form-item label="产出方量"
                      prop="outputVolume"
                      required>
          <el-input-number v-model="form.outputVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="不合格方量"
                      prop="unqualifiedVolume"
                      required>
          <el-input-number v-model="form.unqualifiedVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="完成方量"
                      prop="completedVolume"
                      required>
          <el-input-number v-model="form.completedVolume"
                           :min="0"
                           :precision="2"
                           style="width: 100%" />
        </el-form-item>
      </el-form>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="handleClose">取 æ¶ˆ</el-button>
        <el-button type="primary"
                   v-if="activeStep > 0"
                   @click="activeStep--">上一步</el-button>
        <el-button type="primary"
                   v-if="activeStep < 3"
                   @click="handleNextStep">下一步</el-button>
        <el-button type="primary"
                   v-if="activeStep === 3"
                   :loading="submitLoading"
                   @click="handleSubmit">ç¡® è®¤</el-button>
      </span>
    </template>
  </el-dialog>
</template>
<script setup>
  import { ref, reactive, computed, watch } from "vue";
  import { ElMessage } from "element-plus";
  import { getDicts } from "@/api/system/dict/data";
  import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
  import {
    findProductProcessRouteItemList,
    findProcessParamListOrder,
  } from "@/api/productionManagement/productProcessRoute.js";
  const props = defineProps({
    visible: {
      type: Boolean,
      default: false,
    },
    data: {
      type: Object,
      default: () => ({}),
    },
  });
  const emit = defineEmits(["update:visible", "completed"]);
  const dialogTitle = computed(() => (props.data.id ? "编辑报工" : "新增报工"));
  const formRef = ref(null);
  const submitLoading = ref(false);
  const orderLoading = ref(false);
  const processLoading = ref(false);
  const localVisible = ref(props.visible);
  const activeStep = ref(0);
  const orderId = ref(props.data.orderId || "");
  const processId = ref(props.data.processId || "");
  const activeProcessId = ref("");
  const orderList = ref([]);
  const processList = ref([]);
  const params = ref([]);
  const dictOptions = ref({});
  const form = reactive({
    id: props.data.id || undefined,
    orderId: props.data.orderId || "",
    npsNo: props.data.npsNo || "",
    teamName: props.data.teamName || "",
    materialCode: props.data.materialCode || "",
    productName: props.data.productName || "",
    specification: props.data.specification || "",
    outputVolume: props.data.outputVolume || 0,
    unqualifiedVolume: props.data.unqualifiedVolume || 0,
    completedVolume: props.data.completedVolume || 0,
    createBy: props.data.createBy || "当前登录人",
    createTime: props.data.createTime || new Date(),
    paramGroups: props.data.paramGroups || {}, // å­˜å‚¨æ¯ä¸ªå·¥åºçš„参数组
  });
  const rules = {
    teamName: [{ required: true, message: "请选择班组", trigger: "blur" }],
    outputVolume: [
      { required: true, message: "请输入产出方量", trigger: "blur" },
    ],
    unqualifiedVolume: [
      { required: true, message: "请输入不合格方量", trigger: "blur" },
    ],
    completedVolume: [
      { required: true, message: "请输入完成方量", trigger: "blur" },
    ],
    createBy: [{ required: true, message: "请输入创建人", trigger: "blur" }],
  };
  // åŠ è½½ç”Ÿäº§è®¢å•åˆ—è¡¨
  const loadOrders = () => {
    orderLoading.value = true;
    productOrderListPage({ pageNum: 1, pageSize: 100 })
      .then(res => {
        orderList.value = res.data.records || [];
      })
      .finally(() => {
        orderLoading.value = false;
      });
  };
  // å¤„理生产订单选择
  const handleOrderChange = val => {
    if (val) {
      const order = orderList.value.find(item => item.id === val);
      if (order) {
        form.orderId = val;
        form.npsNo = order.npsNo;
        form.materialCode = order.materialCode;
        form.productName = order.productName;
        form.specification = order.model;
      }
      // åŠ è½½å·¥åºåˆ—è¡¨
      loadProcesses(val);
    } else {
      form.orderId = "";
      form.npsNo = "";
      form.materialCode = "";
      form.productName = "";
      form.specification = "";
      processId.value = "";
      activeProcessId.value = "";
      processList.value = [];
      params.value = [];
      form.params = {};
    }
  };
  // åŠ è½½å·¥åºåˆ—è¡¨
  const loadProcesses = orderId => {
    processLoading.value = true;
    findProductProcessRouteItemList({ orderId })
      .then(res => {
        processList.value = res.data || [];
        // å¦‚果有工序,默认选择第一个
        if (processList.value.length > 0) {
          const firstProcess = processList.value[0];
          activeProcessId.value = firstProcess.id + "";
          processId.value = firstProcess.id;
          form.processId = firstProcess.id;
          // åŠ è½½ç¬¬ä¸€ä¸ªå·¥åºçš„å‚æ•°
          loadParams(firstProcess.id, orderId);
        }
      })
      .finally(() => {
        processLoading.value = false;
      });
  };
  // å¤„理tab页点击
  const handleTabClick = tab => {
    const selectedProcessId = parseInt(tab.paneName);
    processId.value = selectedProcessId;
    form.processId = selectedProcessId;
    // åŠ è½½å‚æ•°åˆ—è¡¨
    loadParams(selectedProcessId, form.orderId);
  };
  // èŽ·å–å­—å…¸æ•°æ®
  const getDictOptions = async dictType => {
    if (!dictType) return [];
    if (dictOptions.value[dictType]) return dictOptions.value[dictType];
    try {
      const res = await getDicts(dictType);
      if (res.code === 200) {
        dictOptions.value[dictType] = res.data;
        return res.data;
      }
      return [];
    } catch (error) {
      console.error("获取字典数据失败:", error);
      return [];
    }
  };
  // åŠ è½½å‚æ•°åˆ—è¡¨
  const loadParams = (processId, orderId) => {
    findProcessParamListOrder({ orderId, routeItemId: processId }).then(
      async res => {
        params.value = res.data || [];
        // åˆå§‹åŒ–参数组
        if (!form.paramGroups[processId]) {
          form.paramGroups[processId] = [];
        }
        // å¦‚果没有参数组,添加一个默认参数组
        if (form.paramGroups[processId].length === 0) {
          const defaultGroup = {};
          for (const param of params.value) {
            defaultGroup[param.id] = param.standardValue || "";
            // å¦‚果是字典类型参数,获取字典数据
            if (param.paramType == "3" && param.paramFormat) {
              await getDictOptions(param.paramFormat);
            }
          }
          form.paramGroups[processId].push(defaultGroup);
        }
      }
    );
  };
  // èŽ·å–å°æ•°ç²¾åº¦
  const getPrecision = format => {
    if (!format) return 2;
    const match = format.match(/\.(\d+)/);
    return match ? parseInt(match[1].length) : 2;
  };
  // å¤„理下一步
  const handleNextStep = () => {
    if (activeStep.value === 0) {
      // ç¬¬ä¸€æ­¥ï¼šéªŒè¯ç”Ÿäº§è®¢å•选择
      if (!orderId.value) {
        ElMessage.error("请选择生产订单");
        return;
      }
      activeStep.value = 1;
    } else if (activeStep.value === 1) {
      // ç¬¬äºŒæ­¥ï¼šéªŒè¯åŸºç¡€ä¿¡æ¯
      formRef.value.validate(valid => {
        if (valid) {
          activeStep.value = 2;
        }
      });
    } else if (activeStep.value === 2) {
      // ç¬¬ä¸‰æ­¥ï¼šç›´æŽ¥è¿›å…¥ç¬¬å››æ­¥
      activeStep.value = 3;
    }
  };
  // å¤„理提交
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        submitLoading.value = true;
        // è¿™é‡Œå¯ä»¥è°ƒç”¨API进行提交
        setTimeout(() => {
          ElMessage.success(props.data.id ? "修改成功" : "新增成功");
          localVisible.value = false;
          emit("completed");
          submitLoading.value = false;
        }, 1000);
      }
    });
  };
  // å¤„理关闭
  const handleClose = () => {
    // é‡ç½®åˆ°åˆå§‹çŠ¶æ€
    activeStep.value = 0;
    orderId.value = "";
    processId.value = "";
    activeProcessId.value = "";
    processList.value = [];
    params.value = [];
    Object.assign(form, {
      id: undefined,
      orderId: "",
      npsNo: "",
      teamName: "",
      materialCode: "",
      productName: "",
      specification: "",
      outputVolume: 0,
      unqualifiedVolume: 0,
      completedVolume: 0,
      createBy: "当前登录人",
      createTime: new Date(),
      params: {},
    });
    localVisible.value = false;
  };
  // æ–°å¢žå‚数组
  const addParamGroup = processId => {
    if (!form.paramGroups[processId]) {
      form.paramGroups[processId] = [];
    }
    // åˆ›å»ºä¸€ä¸ªæ–°çš„参数组,使用默认值
    const newGroup = {};
    params.value.forEach(param => {
      newGroup[param.id] = param.standardValue || "";
    });
    form.paramGroups[processId].push(newGroup);
  };
  // åˆ é™¤å‚数组
  const removeParamGroup = (processId, index) => {
    if (form.paramGroups[processId] && form.paramGroups[processId].length > 1) {
      form.paramGroups[processId].splice(index, 1);
    }
  };
  // åˆå§‹åŒ–
  const init = () => {
    // æ— è®ºæ–°å¢žè¿˜æ˜¯ç¼–辑,都加载订单列表
    loadOrders();
    if (props.data.id) {
      // ç¼–辑时设置表单数据
      Object.assign(form, props.data);
      // è®¾ç½®orderId
      orderId.value = props.data.orderId || "";
      // å¦‚果有订单ID,加载工序和参数
      if (props.data.orderId) {
        // æ¨¡æ‹Ÿé€‰æ‹©è®¢å•的操作,触发数据加载
        setTimeout(() => {
          handleOrderChange(props.data.orderId);
        }, 100);
      }
    } else {
      // æ–°å¢žæ—¶è®¾ç½®é»˜è®¤å€¼
      form.createBy = "当前登录人";
      form.createTime = new Date();
    }
    // å§‹ç»ˆä»Žç¬¬ä¸€æ­¥å¼€å§‹
    activeStep.value = 0;
  };
  // ç›‘听props.visible变化
  watch(
    () => props.visible,
    newVal => {
      localVisible.value = newVal;
      if (newVal) {
        init();
      }
    }
  );
  // ç›‘听localVisible变化
  watch(localVisible, newVal => {
    emit("update:visible", newVal);
  });
</script>
<style scoped>
  .dialog-footer {
    text-align: right;
  }
  .param-group {
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    padding: 16px;
    margin-bottom: 16px;
    background-color: #f9f9f9;
  }
  .group-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    padding-bottom: 8px;
    border-bottom: 1px solid #e4e7ed;
  }
  .group-header span {
    font-weight: bold;
    font-size: 14px;
  }
</style>