chenhj
2 天以前 0365c235877b5d09b3f02423681417da0a4a8b81
Merge branch 'dev_天津_君歌化工' of http://114.132.189.42:9002/r/product-inventory-management into dev_天津_君歌化工
已修改5个文件
553 ■■■■■ 文件已修改
src/components/Dialog/ImportDialog.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderEdit/index.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/ImportDialog.vue
@@ -39,8 +39,8 @@
    </el-upload>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="handleConfirm">确 定</el-button>
        <el-button @click="handleCancel">取 消</el-button>
        <el-button type="primary" :loading="loading" @click="handleConfirm">确 定</el-button>
        <el-button :disabled="loading" @click="handleCancel">取 消</el-button>
      </div>
    </template>
  </el-dialog>
@@ -118,6 +118,10 @@
  onChange: {
    type: Function,
    default: null
  },
  loading: {
    type: Boolean,
    default: false
  }
})
src/views/productionManagement/productionProcess/Edit.vue
@@ -3,28 +3,63 @@
    <el-dialog
        v-model="isShow"
        title="编辑部件"
        width="400"
        width="760"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-row :gutter="16">
          <el-col :span="12">
        <el-form-item
            label="部件:"
            prop="name"
            label="产品名称:"
            prop="productId"
            :rules="[
                {
                required: true,
                message: '请输入部件',
                message: '请选择产品名称',
              },
              {
                max: 100,
                message: '最多100个字符',
              }
            ]">
          <el-input v-model="formState.name" />
              <el-tree-select
            v-model="formState.productId"
            placeholder="请选择产品名称"
            clearable
            filterable
            check-strictly
            :data="productCategoryOptions"
            :render-after-expand="false"
            style="width: 100%"
            @change="handleProductChange"
              />
        </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
            label="产品规格:"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品规格',
              },
            ]">
              <el-select v-model="formState.productModelId"
                     placeholder="请选择产品规格"
                     clearable
                     filterable
                     :disabled="!formState.productId"
                     style="width: 100%">
                <el-option v-for="item in modelOptions"
                       :key="item.id"
                       :label="item.model"
                       :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
        <el-form-item label="部件编号" prop="no">
          <el-input v-model="formState.no"  />
        </el-form-item>
          </el-col>
          <el-col :span="12">
        <el-form-item
            label="部件类型"
            prop="type"
@@ -44,15 +79,48 @@
            <el-option label="其他" :value="6" />
          </el-select>
        </el-form-item>
        <el-form-item label="工资定额" prop="salaryQuota">
          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
          </el-col>
          <el-col :span="12">
            <el-form-item
            label="计划工时(小时)"
            prop="salaryQuota"
            :rules="[
              { validator: validateNonNegativeSalaryQuota, trigger: ['blur', 'change'] }
            ]"
            >
              <el-input v-model="formState.salaryQuota" type="number" :step="0.001" :min="0" />
        </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="计划人员" prop="plannerId">
              <el-select
            v-model="formState.plannerId"
            placeholder="请选择计划人员"
            clearable
            filterable
            style="width: 100%"
            @change="handlePlannerChange"
              >
                <el-option
              v-for="item in plannerOptions"
              :key="item.userId"
              :label="item.nickName"
              :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
        <el-form-item label="是否质检" prop="isQuality">
          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
        </el-form-item>
          </el-col>
          <el-col :span="24">
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
@@ -65,8 +133,10 @@
</template>
<script setup>
import { ref, computed, getCurrentInstance, watch } from "vue";
import { ref, computed, getCurrentInstance, watch, onMounted } from "vue";
import {update} from "@/api/productionManagement/productionProcess.js";
import { modelListPage, productTreeList } from "@/api/basicData/product";
import { userListNoPageByTenantId } from "@/api/system/user.js";
const props = defineProps({
  visible: {
@@ -86,12 +156,19 @@
const formState = ref({
  id: props.record.id,
  name: props.record.name,
  productId: props.record.productId,
  productModelId: props.record.productModelId,
  type: props.record.type,
  no: props.record.no,
  remark: props.record.remark,
  salaryQuota: props.record.salaryQuota,
  plannerId: props.record.plannerId,
  plannerName: props.record.plannerName,
  isQuality: props.record.isQuality,
});
const productCategoryOptions = ref([]);
const modelOptions = ref([]);
const plannerOptions = ref([]);
const isShow = computed({
  get() {
@@ -108,10 +185,14 @@
    formState.value = {
      id: newRecord.id,
      name: newRecord.name || '',
      productId: newRecord.productId,
      productModelId: newRecord.productModelId,
      no: newRecord.no || '',
      type: newRecord.type,
      remark: newRecord.remark || '',
      salaryQuota: newRecord.salaryQuota || '',
      plannerId: newRecord.plannerId,
      plannerName: newRecord.plannerName || '',
      isQuality: props.record.isQuality,
    };
  }
@@ -123,16 +204,129 @@
    formState.value = {
      id: props.record.id,
      name: props.record.name || '',
      productId: props.record.productId,
      productModelId: props.record.productModelId,
      no: props.record.no || '',
      type: props.record.type,
      remark: props.record.remark || '',
      salaryQuota: props.record.salaryQuota || '',
      plannerId: props.record.plannerId,
      plannerName: props.record.plannerName || '',
      isQuality: props.record.isQuality,
    };
  }
});
let { proxy } = getCurrentInstance()
const validateNonNegativeSalaryQuota = (rule, value, callback) => {
  if (value === '' || value === null || value === undefined) {
    callback(new Error('请输入计划工时'));
    return;
  }
  const num = Number(value);
  if (Number.isNaN(num) || num < 0) {
    callback(new Error('计划工时不能小于0'));
    return;
  }
  callback();
};
const convertProductTree = list => {
  return (list || []).map(item => {
    const children = convertProductTree(item.children || item.childList || []);
    return {
      ...item,
      value: item.id,
      label: item.name || item.label,
      children,
      disabled: children.length > 0,
    };
  });
};
const findNodeById = (nodes, targetId) => {
  for (const node of nodes || []) {
    if (String(node.value) === String(targetId)) {
      return node;
    }
    if (node.children && node.children.length > 0) {
      const found = findNodeById(node.children, targetId);
      if (found) return found;
    }
  }
  return null;
};
const findNodeIdByLabel = (nodes, targetLabel) => {
  for (const node of nodes || []) {
    if (node.label === targetLabel) {
      return node.value;
    }
    if (node.children && node.children.length > 0) {
      const found = findNodeIdByLabel(node.children, targetLabel);
      if (found !== null && found !== undefined) return found;
    }
  }
  return undefined;
};
const getProductCategoryOptions = async () => {
  try {
    const res = await productTreeList();
    const list = Array.isArray(res) ? res : res?.data || [];
    productCategoryOptions.value = convertProductTree(list);
    if (!formState.value.productId && formState.value.name) {
      formState.value.productId = findNodeIdByLabel(productCategoryOptions.value, formState.value.name);
    }
    await getModelOptions(formState.value.productId);
  } catch (e) {
    productCategoryOptions.value = [];
  }
};
const getModelOptions = async productId => {
  if (!productId) {
    modelOptions.value = [];
    return;
  }
  try {
    const res = await modelListPage({
      id: productId,
      current: 1,
      size: 999,
    });
    const records = res?.records || res?.data?.records || [];
    modelOptions.value = records;
  } catch (e) {
    modelOptions.value = [];
  }
};
const getPlannerOptions = async () => {
  try {
    const res = await userListNoPageByTenantId();
    plannerOptions.value = res?.data || [];
    if (!formState.value.plannerId && formState.value.plannerName) {
      const selectedUser = plannerOptions.value.find(item => item.nickName === formState.value.plannerName);
      formState.value.plannerId = selectedUser?.userId;
    }
  } catch (e) {
    plannerOptions.value = [];
  }
};
const handlePlannerChange = value => {
  const selectedUser = plannerOptions.value.find(item => String(item.userId) === String(value));
  formState.value.plannerName = selectedUser?.nickName || '';
};
const handleProductChange = async value => {
  const selectedNode = findNodeById(productCategoryOptions.value, value);
  formState.value.name = selectedNode?.label || '';
  formState.value.productModelId = undefined;
  await getModelOptions(value);
};
const closeModal = () => {
  isShow.value = false;
@@ -152,6 +346,11 @@
  })
};
onMounted(() => {
  getProductCategoryOptions();
  getPlannerOptions();
});
defineExpose({
  closeModal,
  handleSubmit,
src/views/productionManagement/productionProcess/New.vue
@@ -3,28 +3,63 @@
    <el-dialog
        v-model="isShow"
        title="新增部件"
        width="400"
        width="760"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-row :gutter="16">
          <el-col :span="12">
        <el-form-item
            label="部件:"
            prop="name"
            label="产品名称:"
            prop="productId"
            :rules="[
                {
                required: true,
                message: '请输入部件',
                message: '请选择产品名称',
              },
              {
                max: 100,
                message: '最多100个字符',
              }
            ]">
          <el-input v-model="formState.name" />
              <el-tree-select
            v-model="formState.productId"
            placeholder="请选择产品名称"
            clearable
            filterable
            check-strictly
            :data="productCategoryOptions"
            :render-after-expand="false"
            style="width: 100%"
            @change="handleProductChange"
              />
        </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
            label="产品规格:"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品规格',
              },
            ]">
              <el-select v-model="formState.productModelId"
                     placeholder="请选择产品规格"
                     clearable
                     filterable
                     :disabled="!formState.productId"
                     style="width: 100%">
                <el-option v-for="item in modelOptions"
                       :key="item.id"
                       :label="item.model"
                       :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
        <el-form-item label="部件编号" prop="no">
          <el-input v-model="formState.no"  />
        </el-form-item>
          </el-col>
          <el-col :span="12">
        <el-form-item
            label="部件类型"
            prop="type"
@@ -44,17 +79,50 @@
            <el-option label="其他" :value="6" />
          </el-select>
        </el-form-item>
        <el-form-item label="工资定额" prop="salaryQuota">
          <el-input v-model="formState.salaryQuota" type="number" :step="0.001">
            <template #append>元</template>
          </el-col>
          <el-col :span="12">
            <el-form-item
            label="计划工时(小时)"
            prop="salaryQuota"
            :rules="[
              { validator: validateNonNegativeSalaryQuota, trigger: ['blur', 'change'] }
            ]"
            >
              <el-input v-model="formState.salaryQuota" type="number" :step="0.001" :min="0">
                <template #append>小时</template>
          </el-input>
        </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="计划人员" prop="plannerId">
              <el-select
            v-model="formState.plannerId"
            placeholder="请选择计划人员"
            clearable
            filterable
            style="width: 100%"
            @change="handlePlannerChange"
              >
                <el-option
              v-for="item in plannerOptions"
              :key="item.userId"
              :label="item.nickName"
              :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
        <el-form-item label="是否质检" prop="isQuality">
          <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
        </el-form-item>
          </el-col>
          <el-col :span="24">
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
@@ -67,8 +135,10 @@
</template>
<script setup>
import { ref, computed, getCurrentInstance } from "vue";
import { ref, computed, getCurrentInstance, onMounted } from "vue";
import {add} from "@/api/productionManagement/productionProcess.js";
import { modelListPage, productTreeList } from "@/api/basicData/product";
import { userListNoPageByTenantId } from "@/api/system/user.js";
const props = defineProps({
  visible: {
@@ -82,11 +152,18 @@
// 响应式数据(替代选项式的 data)
const formState = ref({
  name: '',
  productId: undefined,
  productModelId: undefined,
  type: undefined,
  remark: '',
  salaryQuota:  '',
  plannerId: undefined,
  plannerName: '',
  isQuality: false,
});
const productCategoryOptions = ref([]);
const modelOptions = ref([]);
const plannerOptions = ref([]);
const isShow = computed({
  get() {
@@ -98,6 +175,94 @@
});
let { proxy } = getCurrentInstance()
const validateNonNegativeSalaryQuota = (rule, value, callback) => {
  if (value === '' || value === null || value === undefined) {
    callback(new Error('请输入计划工时'));
    return;
  }
  const num = Number(value);
  if (Number.isNaN(num) || num < 0) {
    callback(new Error('计划工时不能小于0'));
    return;
  }
  callback();
};
const convertProductTree = list => {
  return (list || []).map(item => {
    const children = convertProductTree(item.children || item.childList || []);
    return {
      ...item,
      value: item.id,
      label: item.name || item.label,
      children,
      disabled: children.length > 0,
    };
  });
};
const findNodeById = (nodes, targetId) => {
  for (const node of nodes || []) {
    if (String(node.value) === String(targetId)) {
      return node;
    }
    if (node.children && node.children.length > 0) {
      const found = findNodeById(node.children, targetId);
      if (found) return found;
    }
  }
  return null;
};
const getProductCategoryOptions = async () => {
  try {
    const res = await productTreeList();
    const list = Array.isArray(res) ? res : res?.data || [];
    productCategoryOptions.value = convertProductTree(list);
  } catch (e) {
    productCategoryOptions.value = [];
  }
};
const getModelOptions = async productId => {
  if (!productId) {
    modelOptions.value = [];
    return;
  }
  try {
    const res = await modelListPage({
      id: productId,
      current: 1,
      size: 999,
    });
    const records = res?.records || res?.data?.records || [];
    modelOptions.value = records;
  } catch (e) {
    modelOptions.value = [];
  }
};
const getPlannerOptions = async () => {
  try {
    const res = await userListNoPageByTenantId();
    plannerOptions.value = res?.data || [];
  } catch (e) {
    plannerOptions.value = [];
  }
};
const handlePlannerChange = value => {
  const selectedUser = plannerOptions.value.find(item => String(item.userId) === String(value));
  formState.value.plannerName = selectedUser?.nickName || '';
};
const handleProductChange = async value => {
  const selectedNode = findNodeById(productCategoryOptions.value, value);
  formState.value.name = selectedNode?.label || '';
  formState.value.productModelId = undefined;
  await getModelOptions(value);
};
const closeModal = () => {
  isShow.value = false;
@@ -117,6 +282,11 @@
  })
};
onMounted(() => {
  getProductCategoryOptions();
  getPlannerOptions();
});
defineExpose({
  closeModal,
  handleSubmit,
src/views/productionManagement/productionProcess/index.vue
@@ -3,13 +3,33 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="部件:">
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.name"
                    placeholder="请输入"
                    placeholder="请输入产品名称"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="部件类型:">
          <el-select v-model="searchForm.type"
                     placeholder="请选择"
                     clearable
                     style="width: 200px;"
                     @change="handleQuery">
            <el-option label="加工"
                       :value="1" />
            <el-option label="刮板冷芯制作"
                       :value="2" />
            <el-option label="管路组对"
                       :value="3" />
            <el-option label="罐体连接及调试"
                       :value="4" />
            <el-option label="测试打压"
                       :value="5" />
            <el-option label="其他"
                       :value="6" />
          </el-select>
        </el-form-item>
        <el-form-item label="部件编号:">
          <el-input v-model="searchForm.no"
@@ -60,7 +80,10 @@
                  title="导入部件"
                  :action="importAction"
                  :headers="importHeaders"
                  :loading="importLoading"
                  :disabled="importLoading"
                  :auto-upload="false"
                  :on-progress="handleImportProgress"
                  :on-success="handleImportSuccess"
                  :on-error="handleImportError"
                  @confirm="handleImportConfirm"
@@ -85,26 +108,36 @@
  const data = reactive({
    searchForm: {
      name: "",
      type: undefined,
      no: "",
    },
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
    {
      label: "产品名称",
      prop: "name",
    },
    {
      label: "产品规格",
      prop: "productModel",
    },
    {
      label: "部件编号",
      prop: "no",
    },
    {
      label: "部件",
      prop: "name",
    },
    {
      label: "部件类型",
      prop: "typeText",
    },
    {
      label: "工资定额",
      label: "计划工时(小时)",
      prop: "salaryQuota",
    },
    {
      label: "计划人员",
      prop: "plannerName",
    },
    {
      label: "是否质检",
@@ -145,6 +178,7 @@
  const isShowEditModal = ref(false);
  const record = ref({});
  const importDialogVisible = ref(false);
  const importLoading = ref(false);
  const importDialogRef = ref(null);
  const page = reactive({
    current: 1,
@@ -152,6 +186,7 @@
    total: 0,
  });
  const { proxy } = getCurrentInstance();
  // 导入相关配置
  const importAction =
@@ -251,8 +286,14 @@
    }
  };
  // 导入中
  const handleImportProgress = () => {
    importLoading.value = true;
  };
  // 导入成功
  const handleImportSuccess = response => {
    importLoading.value = false;
    if (response.code === 200) {
      proxy.$modal.msgSuccess("导入成功");
      importDialogVisible.value = false;
@@ -267,11 +308,13 @@
  // 导入失败
  const handleImportError = error => {
    importLoading.value = false;
    proxy.$modal.msgError("导入失败:" + (error.message || "未知错误"));
  };
  // 关闭导入弹窗
  const handleImportClose = () => {
    importLoading.value = false;
    if (importDialogRef.value) {
      importDialogRef.value.clearFiles();
    }
src/views/productionManagement/workOrderEdit/index.vue
@@ -40,17 +40,33 @@
               label-width="120px">
        <el-form-item label="计划开始时间">
          <el-date-picker v-model="editrow.planStartTime"
                          type="date"
                          type="datetime"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          format="YYYY-MM-DD HH:mm:ss"
                          value-format="YYYY-MM-DD HH:mm:ss"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="计划结束时间">
          <el-date-picker v-model="editrow.planEndTime"
                          type="date"
                          type="datetime"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          format="YYYY-MM-DD HH:mm:ss"
                          value-format="YYYY-MM-DD HH:mm:ss"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="报工人">
          <el-select v-model="editrow.reportWorkUserIds"
                     multiple
                     filterable
                     collapse-tags
                     collapse-tags-tooltip
                     placeholder="请选择报工人"
                     style="width: 300px">
            <el-option v-for="user in userOptions"
                       :key="user.userId"
                       :label="user.nickName"
                       :value="user.userId" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
@@ -72,6 +88,7 @@
    productWorkOrderPage,
    updateProductWorkOrder,
  } from "@/api/productionManagement/workOrder.js";
  import { userListNoPageByTenantId } from "@/api/system/user.js";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  const { proxy } = getCurrentInstance();
@@ -163,6 +180,7 @@
  ]);
  
  const tableData = ref([]);
  const userOptions = ref([]);
  const tableLoading = ref(false);
  const editDialogVisible = ref(false);
  let editrow = ref(null);
@@ -210,7 +228,11 @@
    productWorkOrderPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        tableData.value = (res.data.records || []).map(item => ({
          ...item,
          planStartTime: formatDateTime(item.planStartTime),
          planEndTime: formatDateTime(item.planEndTime),
        }));
        page.total = res.data.total;
      })
      .catch(() => {
@@ -220,11 +242,45 @@
  const handleEdit = row => {
    editrow.value = JSON.parse(JSON.stringify(row));
    if (typeof editrow.value.reportWorkUserIds === "string") {
      editrow.value.reportWorkUserIds = editrow.value.reportWorkUserIds
        .split(",")
        .map(v => Number(v))
        .filter(v => Number.isFinite(v));
    } else if (!Array.isArray(editrow.value.reportWorkUserIds)) {
      editrow.value.reportWorkUserIds = [];
    }
    editDialogVisible.value = true;
  };
  const formatDateTime = value => {
    if (!value) return "";
    const date = dayjs(value);
    return date.isValid() ? date.format("YYYY-MM-DD HH:mm:ss") : value;
  };
  const getUserList = () => {
    userListNoPageByTenantId()
      .then(res => {
        if (res.code === 200) {
          userOptions.value = res.data || [];
        }
      })
      .catch(() => {
        userOptions.value = [];
      });
  };
  const handleUpdate = () => {
    updateProductWorkOrder(editrow.value)
    const selectedUsers = userOptions.value.filter(user =>
      (editrow.value.reportWorkUserIds || []).includes(user.userId)
    );
    const submitData = {
      ...editrow.value,
      reportWorkUserIds: editrow.value.reportWorkUserIds || [],
      reportWork: selectedUsers.map(user => user.nickName).join(","),
    };
    updateProductWorkOrder(submitData)
      .then(res => {
        proxy.$modal.msgSuccess("提交成功");
        editDialogVisible.value = false;
@@ -239,6 +295,7 @@
  onMounted(() => {
    getList();
    getUserList();
  });
</script>