gaoluyang
11 小时以前 48632e6d9a04e28d2f1ddd22e6896b281b21b128
src/views/productionManagement/productionOrder/New.vue
@@ -1,68 +1,137 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增生产订单"
        width="800"
        @close="closeModal"
      v-model="isShow"
      title="新增生产订单"
      width="800"
      @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
      <el-form
        ref="formRef"
        :model="formState"
        label-width="140px"
        label-position="top"
      >
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
          label="产品名称"
          prop="productModelId"
          :rules="[
            {
              required: true,
              message: '请选择产品',
              trigger: 'change',
            },
          ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ formState.productName ? formState.productName : '选择产品' }}
            {{ formState.productName || "选择产品" }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
        <el-form-item label="规格" prop="productModelName">
          <el-input v-model="formState.productModelName" disabled />
        </el-form-item>
        <el-form-item label="单位" prop="unit">
          <el-input v-model="formState.unit" disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
          label="工艺路线"
          prop="routeId"
          :rules="[
            {
              required: true,
              message: '请选择工艺路线',
              trigger: 'change',
            },
          ]"
        >
          <el-input v-model="formState.unit"  disabled />
        </el-form-item>
        <el-form-item label="工艺路线">
          <el-select v-model="formState.routeId"
                     placeholder="请选择工艺路线"
                     style="width: 100%;"
                     :loading="bindRouteLoading">
            <el-option v-for="item in routeOptions"
                       :key="item.id"
                       :label="`${item.processRouteCode || ''}`"
                       :value="item.id" />
          <el-select
            v-model="formState.routeId"
            placeholder="请选择工艺路线"
            style="width: 100%"
            :loading="bindRouteLoading"
            @change="handleRouteChange"
          >
            <el-option
              v-for="item in routeOptions"
              :key="item.id"
              :label="item.processRouteCode || ''"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item
            label="需求数量"
            prop="quantity"
          v-if="processListData.length"
          label="工序报工人"
          prop="processUserList"
          :rules="[
            {
              validator: validateProcessUsers,
              trigger: 'change',
            },
          ]"
        >
          <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" />
          <div class="process-user-list">
            <div
              v-for="(item, index) in processListData"
              :key="item.id || `${item.processId}-${index}`"
              class="process-user-item"
            >
              <div class="process-user-header">
                <div class="process-user-name">
                  {{ item.name || item.processName || item.no || `工序${index + 1}` }}
                </div>
                <el-button
                  type="danger"
                  link
                  class="process-user-remove"
                  @click="removeProcessItem(index)"
                >
                  删除
                </el-button>
              </div>
              <el-select
                v-model="formState.processUserList[index].userIds"
                placeholder="请选择报工人"
                class="process-user-select"
                filterable
                clearable
                multiple
                collapse-tags
                collapse-tags-tooltip
                :max-collapse-tags="3"
                @change="handleProcessUserChange(index, $event)"
              >
                <el-option
                  v-for="user in userOptions"
                  :key="user.userId"
                  :label="user.nickName"
                  :value="user.userId"
                />
              </el-select>
            </div>
          </div>
        </el-form-item>
        <el-form-item label="需求数量" prop="quantity">
          <el-input-number
            v-model="formState.quantity"
            :step="1"
            :min="1"
            style="width: 100%"
          />
        </el-form-item>
      </el-form>
      <!-- 产品选择弹窗 -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
        v-model="showProductSelectDialog"
        single
        @confirm="handleProductSelect"
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
@@ -74,115 +143,238 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import { computed, getCurrentInstance, nextTick, ref } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {addProductOrder, listProcessRoute} from "@/api/productionManagement/productionOrder.js";
import { addProductOrder, listProcessRoute } from "@/api/productionManagement/productionOrder.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import {findProcessRouteItemList} from "@/api/productionManagement/processRouteItem.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
    default: "qualified",
  },
});
const emit = defineEmits(['update:visible', 'completed']);
const emit = defineEmits(["update:visible", "completed"]);
// 响应式数据(替代选项式的 data)
const formState = ref({
const createDefaultFormState = () => ({
  productId: undefined,
  productModelId: undefined,
  routeId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  quantity: 0,
  quantity: 1,
  processUserList: [],
});
const formState = ref(createDefaultFormState());
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
    emit("update:visible", val);
  },
});
const showProductSelectDialog = ref(false);
const routeOptions = ref([]);
const bindRouteLoading = ref(false);
const processListData = ref([]);
const userOptions = ref([]);
const userLoading = ref(false);
let { proxy } = getCurrentInstance()
const { proxy } = getCurrentInstance();
const formRef = ref();
const validateProcessUserField = async (shouldValidate = false) => {
  await nextTick();
  if (!formRef.value) return;
  if (processListData.value.length && shouldValidate) {
    formRef.value.validateField("processUserList");
    return;
  }
  formRef.value.clearValidate("processUserList");
};
const resetProcessUsers = () => {
  processListData.value = [];
  formState.value.processUserList = [];
};
const closeModal = () => {
  // 重置表单数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    routeId: undefined,
    productName: "",
    productModelName: "",
    quantity: '',
  };
  formState.value = createDefaultFormState();
  routeOptions.value = [];
  resetProcessUsers();
  isShow.value = false;
};
// 产品选择处理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    fetchRouteOptions( product.id);
    // 触发表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
const ensureUserOptions = () => {
  if (userOptions.value.length || userLoading.value) return;
  userLoading.value = true;
  userListNoPageByTenantId()
    .then(res => {
      userOptions.value = res.data || [];
    })
    .finally(() => {
      userLoading.value = false;
    });
};
const routeOptions = ref([]);
const bindRouteLoading = ref(false);
const fetchRouteOptions = (productModelId) => {
  formState.value.routeId = undefined;
  routeOptions.value = []
  bindRouteLoading.value = true;
  listProcessRoute({ productModelId: productModelId }).then(res => {
    routeOptions.value = res.data || [];
  }).finally(() => {
    bindRouteLoading.value = false;
const createProcessUserList = list =>
  list.map(item => ({
    processId: item.processId,
    processName: item.name || item.processName || item.no || "",
    userIds: [],
    userNames: "",
  }));
const fetchProcessList = routeId => {
  if (!routeId) {
    resetProcessUsers();
    return;
  }
  findProcessRouteItemList({
    routeId,
    productModelId: formState.value.productModelId,
  })
}
    .then(res => {
      processListData.value = res.data || [];
      formState.value.processUserList = createProcessUserList(processListData.value);
      ensureUserOptions();
      validateProcessUserField();
    })
    .catch(() => {
      resetProcessUsers();
    });
};
const handleRouteChange = routeId => {
  fetchProcessList(routeId);
};
const handleProcessUserChange = (index, userIds) => {
  const selectedUsers = userOptions.value.filter(user => Array.isArray(userIds) && userIds.includes(user.userId));
  formState.value.processUserList[index].userNames = selectedUsers.map(user => user.nickName).join(",");
  validateProcessUserField(true);
};
const removeProcessItem = index => {
  processListData.value.splice(index, 1);
  formState.value.processUserList.splice(index, 1);
  validateProcessUserField(true);
};
const validateProcessUsers = (_, value, callback) => {
  if (!formState.value.routeId) {
    callback();
    return;
  }
  if (!processListData.value.length) {
    callback(new Error("当前工艺路线下没有工序"));
    return;
  }
  if (!Array.isArray(value) || value.length !== processListData.value.length) {
    callback(new Error("请为每道工序选择报工人"));
    return;
  }
  const hasEmptyUser = value.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0);
  if (hasEmptyUser) {
    callback(new Error("请为每道工序选择报工人"));
    return;
  }
  callback();
};
const handleProductSelect = products => {
  if (!products?.length) return;
  const product = products[0];
  formState.value.productId = product.id;
  formState.value.productName = product.productName;
  formState.value.productModelName = product.model;
  formState.value.productModelId = product.id;
  formState.value.unit = product.unit;
  formState.value.routeId = undefined;
  routeOptions.value = [];
  resetProcessUsers();
  showProductSelectDialog.value = false;
  fetchRouteOptions(product.id);
  formRef.value?.validateField("productModelId");
};
const fetchRouteOptions = productModelId => {
  formState.value.routeId = undefined;
  routeOptions.value = [];
  resetProcessUsers();
  bindRouteLoading.value = true;
  listProcessRoute({ productModelId })
    .then(res => {
      routeOptions.value = res.data || [];
    })
    .finally(() => {
      bindRouteLoading.value = false;
    });
};
const buildProcessRouteItems = () =>
  processListData.value.map((item, index) => {
    const processUser = formState.value.processUserList[index] || {};
    return {
      productOrderId: undefined,
      productRouteId: formState.value.routeId,
      processId: item.processId,
      productModelId: formState.value.productModelId,
      dragSort: item.dragSort ?? index + 1,
      isQuality: item.isQuality ?? false,
      reportUserIds: Array.isArray(processUser.userIds) ? processUser.userIds.join(",") : "",
    };
  });
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // 验证是否选择了产品和规格
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
  formRef.value.validate(valid => {
    if (!valid) return;
      addProductOrder(formState.value).then(res => {
        // 关闭模态框
        isShow.value = false;
        // 告知父组件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    if (!formState.value.productModelId) {
      proxy.$modal.msgError("请选择产品");
      return;
    }
  })
};
    if (!formState.value.routeId) {
      proxy.$modal.msgError("请选择工艺路线");
      return;
    }
    if (!formState.value.processUserList.length) {
      proxy.$modal.msgError("当前工艺路线下没有工序");
      return;
    }
    if (formState.value.processUserList.some(item => !Array.isArray(item.userIds) || item.userIds.length === 0)) {
      proxy.$modal.msgError("请为每道工序选择报工人");
      return;
    }
    addProductOrder({
      ...formState.value,
      processRouteItems: buildProcessRouteItems(),
    }).then(() => {
      isShow.value = false;
      emit("completed");
      proxy.$modal.msgSuccess("提交成功");
    });
  });
};
defineExpose({
  closeModal,
@@ -190,3 +382,55 @@
  isShow,
});
</script>
<style scoped>
.process-user-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 14px;
  border-radius: 12px;
  background: #f7f9fc;
  border: 1px solid #e8eef5;
}
.process-user-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  padding: 12px 14px;
  border-radius: 10px;
  background: #fff;
  border: 1px solid #edf2f7;
}
.process-user-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.process-user-name {
  color: #1f2d3d;
  font-weight: 500;
  line-height: 1.4;
}
.process-user-remove {
  flex-shrink: 0;
  padding: 0;
}
.process-user-select {
  width: 100%;
}
@media (max-width: 768px) {
  .process-user-item {
    grid-template-columns: 1fr;
    gap: 10px;
  }
}
</style>