已添加5个文件
已修改8个文件
1247 ■■■■■ 文件已修改
src/api/productionManagement/productProcessParameter.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionRecords.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/New.vue 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 423 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionRecords/Edit.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionRecords/New.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionRecords/index.vue 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 159 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessParameter.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
import request from "@/utils/request";
// å·¥åºç»‘定生产记录分页查询
export function listPage(query) {
  return request({
    url: "/productProcessParameter/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žå·¥åºç”Ÿäº§è®°å½•关联(支持批量)
// processId æ”¾åœ¨è·¯å¾„里,body ç›´æŽ¥ä¼ é€‰ä¸­çš„生产记录数组
export function addProductProcessParameter(processId, data) {
  return request({
    url: `/productProcessParameter/addProductProcessParameter/${processId}`,
    method: "post",
    data,
  });
}
// åˆ é™¤ï¼ˆæ”¯æŒæ‰¹é‡ ids æ•°ç»„)
export function del(ids) {
  const idStr = Array.isArray(ids) ? ids.join(",") : ids;
  return request({
    url: `/productProcessParameter/${idStr}`,
    method: "delete",
  });
}
src/api/productionManagement/productionProcess.js
@@ -66,4 +66,32 @@
    method: "post",
    responseType: "blob",
  });
}
// èŽ·å–ç»‘å®šçš„è®¾å¤‡åˆ—è¡¨
export function getBindDevices(processId) {
  return request({
    url: "/productProcess/getBindDevices",
    method: "get",
    params: { processId },
  });
}
// ç»‘定设备
export function bindDevices(processId, deviceIds) {
  return request({
    url: "/productProcess/bindDevices",
    method: "post",
    params: { processId },
    data: deviceIds,
  });
}
// è§£é™¤è®¾å¤‡ç»‘定
export function unbindDevice(processId, deviceId) {
  return request({
    url: "/productProcess/unbindDevice",
    method: "delete",
    params: { processId, deviceId },
  });
}
src/api/productionManagement/productionRecords.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// ç”Ÿäº§è®°å½•(参数)接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productParameter/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/productParameter/addProductParameter",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹
export function update(data) {
  return request({
    url: "/productParameter/updProductParameter",
    method: "put",
    data: data,
  });
}
// åˆ é™¤ ids ä¸ºæ•°ç»„时会拼成 productParameter/1,2,3
export function del(ids) {
  const idStr = Array.isArray(ids) ? ids.join(",") : ids;
  return request({
    url: `/productParameter/${idStr}`,
    method: "delete",
  });
}
src/components/PIMTable/PIMTable.vue
@@ -26,6 +26,7 @@
      type="selection"
      width="55"
      v-if="isSelection"
      :selectable="rowSelectable"
    />
    <el-table-column align="center" label="序号" type="index" width="60" />
@@ -333,6 +334,12 @@
  },
});
// é€‰æ‹©æ¡†ç¦ç”¨æŽ§åˆ¶ï¼šè¡Œå¯¹è±¡ä¸Šå¦‚果有 disabled === true,则不允许选中
const rowSelectable = (row, index) => {
  return !row.disabled;
};
// Data
const uploadRefs = ref([]);
const currentFiles = ref({});
src/views/productionManagement/productionOrder/New.vue
@@ -24,17 +24,29 @@
        </el-form-item>
        <el-form-item
            label="批号"
            prop="batchNo"
            :rules="[
                {
                required: true,
                }
            ]"
        >
          <el-input v-model="formState.batchNo"/>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
          <el-input v-model="formState.productModelName" disabled/>
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
          <el-input v-model="formState.unit" disabled/>
        </el-form-item>
        <el-form-item label="工艺路线">
@@ -45,7 +57,7 @@
            <el-option v-for="item in routeOptions"
                       :key="item.id"
                       :label="`${item.processRouteCode || ''}`"
                       :value="item.id" />
                       :value="item.id"/>
          </el-select>
        </el-form-item>
@@ -53,7 +65,21 @@
            label="预计生产数量"
            prop="quantity"
        >
          <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%" />
          <el-input-number v-model="formState.quantity" :step="1" :min="1" style="width: 100%"/>
        </el-form-item>
        <el-form-item
            label="预计日程(天)"
            prop="expectedSchedule"
        >
          <el-input-number v-model="formState.expectedSchedule" :step="0.01" :min="0" style="width: 100%" />
        </el-form-item>
        <el-form-item
            label="制造班组"
            prop="manufacturingTeam"
        >
          <el-input v-model="formState.manufacturingTeam"  disabled />
        </el-form-item>
      </el-form>
@@ -101,7 +127,10 @@
  productName: "",
  productModelName: "",
  unit: "",
  batchNo: "",
  quantity: 0,
  expectedSchedule: undefined,
  manufacturingTeam: undefined,
});
const isShow = computed({
@@ -115,7 +144,7 @@
const showProductSelectDialog = ref(false);
let { proxy } = getCurrentInstance()
let {proxy} = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
@@ -126,6 +155,7 @@
    productName: "",
    productModelName: "",
    quantity: '',
    batchNo: "",
  };
  isShow.value = false;
};
@@ -136,11 +166,16 @@
    const product = products[0];
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    const productNameArr = product.productName.split('-');
    if (productNameArr.length === 3 && productNameArr[0] && productNameArr[1] !== '') {
      formState.value.manufacturingTeam = productNameArr[1].charAt(0) + '类车间';
    }
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    fetchRouteOptions( product.id);
    fetchRouteOptions(product.id);
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
@@ -152,7 +187,7 @@
  formState.value.routeId = undefined;
  routeOptions.value = []
  bindRouteLoading.value = true;
  listProcessRoute({ productModelId: productModelId }).then(res => {
  listProcessRoute({productModelId: productModelId}).then(res => {
    routeOptions.value = res.data || [];
  }).finally(() => {
    bindRouteLoading.value = false;
src/views/productionManagement/productionOrder/index.vue
@@ -125,16 +125,6 @@
      width: '120px',
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: '150px',
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: '200px',
    },
    {
      label: "产品名称",
      prop: "productCategory",
      width: '120px',
@@ -184,6 +174,16 @@
      width: 120,
    },
    {
      label: "预计日程(天)",
      prop: "expectedSchedule",
      width: 120,
    },
    {
      label: "制造班组",
      prop: "manufacturingTeam",
      width: 120,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
src/views/productionManagement/productionProcess/index.vue
@@ -48,6 +48,90 @@
                @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <!-- å·¥åºç”Ÿäº§è®°å½•列表弹框 -->
    <el-dialog v-model="isShowProcessParamModal"
               title="生产记录"
               width="800px">
      <div style="text-align: right"
           class="mb10">
        <el-button type="primary"
                   @click="showSelectProductParamModal">新增</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDeleteProcessParam"
                   :disabled="processParamSelectedRows.length === 0">批量删除</el-button>
      </div>
      <PIMTable rowKey="id"
                :column="processParamColumns"
                :tableData="processParamTableData"
                :page="processParamPage"
                :isSelection="true"
                @selection-change="handleProcessParamSelectionChange"
                @pagination="processParamPagination"
                :total="processParamPage.total"></PIMTable>
    </el-dialog>
    <!-- é€‰æ‹©ç”Ÿäº§è®°å½•弹框 -->
    <el-dialog v-model="isShowSelectParamModal"
               title="选择生产记录"
               width="800px">
      <PIMTable rowKey="id"
                :column="productParamColumns"
                :tableData="productParamTableData"
                :page="productParamPage"
                :isSelection="true"
                @selection-change="handleProductParamSelectionChange"
                @pagination="productParamPagination"
                :total="productParamPage.total"></PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="handleAddProcessParam">确定</el-button>
          <el-button @click="isShowSelectParamModal = false">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æŸ¥çœ‹è®¾å¤‡å¼¹æ¡† -->
    <el-dialog v-model="isShowDeviceModal"
               title="设备列表"
               width="900px">
      <div style="text-align: right"
           class="mb10">
        <el-button type="primary"
                   @click="showAddDeviceModal">添加设备</el-button>
      </div>
      <PIMTable rowKey="id"
                :column="deviceColumns"
                :tableData="deviceTableData"
                :page="devicePage"
                :isSelection="false"
                @pagination="devicePagination"
                :total="devicePage.total"></PIMTable>
    </el-dialog>
    <!-- æ·»åŠ è®¾å¤‡å¼¹æ¡† -->
    <el-dialog v-model="isShowSelectDeviceModal"
               title="选择设备"
               width="900px">
      <PIMTable rowKey="id"
                :column="selectDeviceColumns"
                :tableData="selectDeviceTableData"
                :page="selectDevicePage"
                :isSelection="true"
                @selection-change="handleSelectDeviceChange"
                @pagination="selectDevicePagination"
                :total="selectDevicePage.total"></PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="handleBindDevices">确定</el-button>
          <el-button @click="isShowSelectDeviceModal = false">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <new-process v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 @completed="getList" />
@@ -79,7 +163,17 @@
    del,
    importData,
    downloadTemplate,
    getBindDevices,
    bindDevices,
    unbindDevice,
  } from "@/api/productionManagement/productionProcess.js";
  import {
    listPage as listProcessParamPage,
    del as delProcessParam,
    addProductProcessParameter,
  } from "@/api/productionManagement/productProcessParameter.js";
  import { listPage as listProductParamPage } from "@/api/productionManagement/productionRecords.js";
  import { getLedgerPage } from "@/api/equipmentManagement/ledger.js";
  import { getToken } from "@/utils/auth";
  const data = reactive({
@@ -126,8 +220,22 @@
      label: "操作",
      align: "center",
      fixed: "right",
      width: 280,
      width: 350,
      operation: [
        {
          name: "查看",
          type: "text",
          clickFun: row => {
            showProcessParamModal(row);
          },
        },
        {
          name: "查看设备",
          type: "text",
          clickFun: row => {
            showDeviceModal(row);
          },
        },
        {
          name: "编辑",
          type: "text",
@@ -138,15 +246,127 @@
      ],
    },
  ]);
  // å·¥åºå·²ç»‘定的生产记录列表列配置
  const processParamColumns = ref([
    { label: "参数编码", prop: "code" },
    { label: "参数项", prop: "parameterItem" },
    {
      label: "参数类型",
      prop: "type",
      formatData: val => {
        const dictList = (proxy.useDict && proxy.useDict("parameter_tyep")?.parameter_tyep) || [];
        const list = dictList.value || dictList;
        const hit = (list || []).find(d => d.value === val);
        return hit ? hit.label : val;
      },
    },
    { label: "单位", prop: "unit" },
    { label: "参数格式", prop: "parameterFormat" },
    {
      label: "是否必填",
      prop: "isRequired",
      formatData: v => (v === "1" || v === 1 ? "是" : "否"),
    },
  ]);
  // å¯é€‰æ‹©çš„生产记录列表列配置
  const productParamColumns = ref([
    { label: "参数编码", prop: "code" },
    { label: "参数项", prop: "parameterItem" },
    {
      label: "参数类型",
      prop: "type",
      formatData: val => {
        const dictList = (proxy.useDict && proxy.useDict("parameter_tyep")?.parameter_tyep) || [];
        const list = dictList.value || dictList;
        const hit = (list || []).find(d => d.value === val);
        return hit ? hit.label : val;
      },
    },
    { label: "单位", prop: "unit" },
    { label: "参数格式", prop: "parameterFormat" },
    {
      label: "是否必填",
      prop: "isRequired",
      formatData: v => (v === "1" || v === 1 ? "是" : "否"),
    },
  ]);
  // è®¾å¤‡åˆ—表列配置
  const deviceColumns = ref([
    { label: "设备名称", prop: "deviceName" },
    { label: "规格型号", prop: "deviceModel" },
    { label: "设备品牌", prop: "deviceBrand" },
    { label: "设备类型", prop: "type" },
    { label: "供应商", prop: "supplierName" },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      width: 100,
      operation: [
        {
          name: "删除",
          type: "text",
          typeAttrs: { danger: true },
          clickFun: row => {
            handleUnbindDevice(row);
          },
        },
      ],
    },
  ]);
  // å¯é€‰è®¾å¤‡åˆ—表列配置
  const selectDeviceColumns = ref([
    { label: "设备名称", prop: "deviceName" },
    { label: "规格型号", prop: "deviceModel" },
    { label: "设备品牌", prop: "deviceBrand" },
    { label: "设备类型", prop: "type" },
    { label: "供应商", prop: "supplierName" },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const isShowNewModal = ref(false);
  const isShowEditModal = ref(false);
  const isShowProcessParamModal = ref(false);
  const isShowSelectParamModal = ref(false);
  const record = ref({});
  const currentProcess = ref(null);
  const importDialogVisible = ref(false);
  const importDialogRef = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const processParamPage = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const productParamPage = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const processParamTableData = ref([]);
  const productParamTableData = ref([]);
  const processParamSelectedRows = ref([]);
  const productParamSelectedRows = ref([]);
  // è®¾å¤‡ç›¸å…³å˜é‡
  const isShowDeviceModal = ref(false);
  const isShowSelectDeviceModal = ref(false);
  const deviceTableData = ref([]);
  const selectDeviceTableData = ref([]);
  const selectedDeviceRows = ref([]);
  const devicePage = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const selectDevicePage = reactive({
    current: 1,
    size: 100,
    total: 0,
@@ -170,6 +390,16 @@
    page.size = obj.limit;
    getList();
  };
  const processParamPagination = obj => {
    processParamPage.current = obj.page;
    processParamPage.size = obj.limit;
    getProcessParamList();
  };
  const productParamPagination = obj => {
    productParamPage.current = obj.page;
    productParamPage.size = obj.limit;
    getProductParamList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
@@ -192,6 +422,14 @@
    selectedRows.value = selection;
  };
  const handleProcessParamSelectionChange = selection => {
    processParamSelectedRows.value = selection;
  };
  const handleProductParamSelectionChange = selection => {
    productParamSelectedRows.value = selection;
  };
  // æ‰“开新增弹框
  const showNewModal = () => {
    isShowNewModal.value = true;
@@ -202,6 +440,189 @@
    record.value = row;
  };
  // æŸ¥çœ‹å½“前工序的生产记录
  const showProcessParamModal = row => {
    currentProcess.value = row;
    isShowProcessParamModal.value = true;
    processParamPage.current = 1;
    getProcessParamList();
  };
  const getProcessParamList = () => {
    if (!currentProcess.value) return;
    const params = {
      processId: currentProcess.value.id,
      ...processParamPage,
    };
    listProcessParamPage(params).then(res => {
      processParamTableData.value = res.data.records || [];
      processParamPage.total = res.data.total || 0;
    });
  };
  // åˆ é™¤å½“前工序绑定的生产记录
  const handleDeleteProcessParam = () => {
    const ids = processParamSelectedRows.value.map(item => item.id);
    if (!ids.length) {
      proxy.$modal.msgWarning("请选择要删除的记录");
      return;
    }
    proxy.$modal
        .confirm("是否确认删除选中的生产记录?")
        .then(() => delProcessParam(ids))
        .then(() => {
          proxy.$modal.msgSuccess("删除成功");
          getProcessParamList();
        })
        .catch(() => {});
  };
  // æ‰“开选择生产记录弹框
  const showSelectProductParamModal = () => {
    isShowSelectParamModal.value = true;
    productParamPage.current = 1;
    getProductParamList();
  };
  const getProductParamList = () => {
    const params = {
      ...productParamPage,
    };
    listProductParamPage(params).then(res => {
      const records = res.data.records || [];
      // å·²ç»‘定到当前工序的生产记录,不允许再次选择 -> æ ¹æ®å‚数编码打上 disabled æ ‡è®°
      const boundCodeSet = new Set(
          (processParamTableData.value || [])
              .map(item => item.code ?? item.parameterCode)
              .filter(code => !!code)
      );
      productParamTableData.value = records.map(item => ({
        ...item,
        disabled: item.code ? boundCodeSet.has(item.code) : false,
      }));
      productParamPage.total = res.data.total || 0;
    });
  };
  // ç¡®è®¤ç»™å½“前工序新增生产记录
  const handleAddProcessParam = () => {
    if (!currentProcess.value) return;
    const selectedList = productParamSelectedRows.value || [];
    if (!selectedList.length) {
      proxy.$modal.msgWarning("请选择要绑定的生产记录");
      return;
    }
    // processId æ”¾åœ¨è·¯å¾„里,body ç›´æŽ¥ä¼ é€‰ä¸­çš„生产记录数组
    addProductProcessParameter(currentProcess.value.id, selectedList)
        .then(() => {
          proxy.$modal.msgSuccess("新增成功");
          isShowSelectParamModal.value = false;
          getProcessParamList();
        })
        .catch(() => {});
  };
  // æŸ¥çœ‹è®¾å¤‡å¼¹æ¡†
  const showDeviceModal = row => {
    currentProcess.value = row;
    isShowDeviceModal.value = true;
    devicePage.current = 1;
    getDeviceList();
  };
  // èŽ·å–å·²ç»‘å®šçš„è®¾å¤‡åˆ—è¡¨
  const getDeviceList = () => {
    if (!currentProcess.value) return;
    getBindDevices(currentProcess.value.id)
      .then(res => {
        deviceTableData.value = res.data || [];
        devicePage.total = res.data?.length || 0;
      })
      .catch(() => {});
  };
  // è®¾å¤‡åˆ—表分页
  const devicePagination = obj => {
    devicePage.current = obj.page;
    devicePage.size = obj.limit;
    getDeviceList();
  };
  // è§£ç»‘设备
  const handleUnbindDevice = row => {
    if (!currentProcess.value) return;
    proxy.$modal.confirm("是否确认解绑该设备?")
      .then(() => {
        return unbindDevice(currentProcess.value.id, row.id);
      })
      .then(() => {
        proxy.$modal.msgSuccess("解绑成功");
        getDeviceList();
      })
      .catch(() => {});
  };
  // æ‰“开添加设备弹框
  const showAddDeviceModal = () => {
    isShowSelectDeviceModal.value = true;
    selectDevicePage.current = 1;
    selectedDeviceRows.value = [];
    getSelectDeviceList();
  };
  // èŽ·å–å¯é€‰è®¾å¤‡åˆ—è¡¨
  const getSelectDeviceList = () => {
    if (!currentProcess.value) return;
    // å…ˆèŽ·å–å·²ç»‘å®šçš„è®¾å¤‡ID
    getBindDevices(currentProcess.value.id)
      .then(res => {
        const boundDeviceIds = new Set(
          (res.data || []).map(item => item.id).filter(id => !!id)
        );
        // èŽ·å–è®¾å¤‡å°è´¦åˆ†é¡µåˆ—è¡¨
        return getLedgerPage(selectDevicePage).then(deviceRes => {
          const allDevices = deviceRes.data?.records || [];
          // è¿‡æ»¤æŽ‰å·²ç»‘定的设备,并标记为disabled
          selectDeviceTableData.value = allDevices.map(item => ({
            ...item,
            disabled: boundDeviceIds.has(item.id),
          }));
          selectDevicePage.total = deviceRes.data?.total || 0;
        });
      })
      .catch(() => {});
  };
  // é€‰æ‹©è®¾å¤‡å˜åŒ–
  const handleSelectDeviceChange = selection => {
    selectedDeviceRows.value = selection;
  };
  // é€‰æ‹©è®¾å¤‡åˆ†é¡µ
  const selectDevicePagination = obj => {
    selectDevicePage.current = obj.page;
    selectDevicePage.size = obj.limit;
    getSelectDeviceList();
  };
  // ç»‘定设备
  const handleBindDevices = () => {
    if (!currentProcess.value) return;
    const selectedList = selectedDeviceRows.value || [];
    if (!selectedList.length) {
      proxy.$modal.msgWarning("请选择要绑定的设备");
      return;
    }
    const deviceIds = selectedList.map(item => item.id);
    bindDevices(currentProcess.value.id, deviceIds)
      .then(() => {
        proxy.$modal.msgSuccess("绑定成功");
        isShowSelectDeviceModal.value = false;
        getDeviceList();
      })
      .catch(() => {});
  };
  // åˆ é™¤
  function handleDelete() {
    const no = selectedRows.value.map(item => item.no);
src/views/productionManagement/productionRecords/Edit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
<template>
  <div>
    <el-dialog
      v-model="isShow"
      title="编辑生产记录"
      width="520px"
      @close="closeModal"
    >
      <el-form
        ref="formRef"
        label-width="120px"
        :model="formState"
        label-position="top"
      >
        <el-form-item
          label="参数项"
          prop="parameterItem"
          :rules="[{ required: true, message: '请输入参数项' }]"
        >
          <el-input v-model="formState.parameterItem" placeholder="请输入参数项" />
        </el-form-item>
        <el-form-item
          label="参数类型"
          prop="type"
          :rules="[{ required: true, message: '请选择参数类型' }]"
        >
          <el-select v-model="formState.type" placeholder="请选择参数类型" style="width: 100%;">
            <el-option
              v-for="dict in parameter_tyep"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item
          label="单位"
          prop="unit"
          :rules="[{ required: true, message: '请输入单位' }]"
        >
          <el-input v-model="formState.unit" placeholder="请输入单位" />
        </el-form-item>
        <el-form-item label="参数格式" prop="parameterFormat">
          <el-input v-model="formState.parameterFormat" placeholder="请输入参数格式(选填)" />
        </el-form-item>
        <el-form-item
          label="是否必填"
          prop="isRequired"
          :rules="[{ required: true, message: '请选择是否必填' }]"
        >
          <el-select v-model="formState.isRequired" placeholder="请选择" style="width: 100%;">
            <el-option label="是" value="1" />
            <el-option label="否" value="0" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, watch } from "vue";
import { update } from "@/api/productionManagement/productionRecords.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  record: {
    type: Object,
    default: () => ({}),
  },
});
const emit = defineEmits(["update:visible", "completed"]);
const formRef = ref(null);
const formState = ref({
  id: undefined,
  code: "",
  parameterItem: "",
  type: "",
  unit: "",
  parameterFormat: "",
  isRequired: undefined,
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit("update:visible", val);
  },
});
// å›žæ˜¾ï¼šç›‘听 record å˜åŒ–
watch(
  () => props.record,
  (newRecord) => {
    if (newRecord && Object.keys(newRecord).length) {
      formState.value = {
        id: newRecord.id,
        code: newRecord.code ?? "",
        parameterItem: newRecord.parameterItem ?? "",
        type: newRecord.type ?? "",
        unit: newRecord.unit ?? "",
        parameterFormat: newRecord.parameterFormat ?? "",
        isRequired: newRecord.isRequired,
      };
    }
  },
  { immediate: true, deep: true }
);
// å¼¹çª—打开时重新回显
watch(
  () => props.visible,
  (visible) => {
    if (visible && props.record && Object.keys(props.record).length) {
      formState.value = {
        id: props.record.id,
        code: props.record.code ?? "",
        parameterItem: props.record.parameterItem ?? "",
        type: props.record.type ?? "",
        unit: props.record.unit ?? "",
        parameterFormat: props.record.parameterFormat ?? "",
        isRequired: props.record.isRequired,
      };
    }
  }
);
const { proxy } = getCurrentInstance();
const { parameter_tyep } = proxy.useDict("parameter_tyep");
const closeModal = () => {
  isShow.value = false;
};
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      update(formState.value).then(() => {
        isShow.value = false;
        emit("completed");
        proxy.$modal.msgSuccess("修改成功");
      });
    }
  });
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/productionManagement/productionRecords/New.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,122 @@
<template>
  <div>
    <el-dialog
      v-model="isShow"
      title="新增生产记录"
      width="520px"
      @close="closeModal"
    >
      <el-form
        ref="formRef"
        label-width="120px"
        :model="formState"
        label-position="top"
      >
        <el-form-item
          label="参数项"
          prop="parameterItem"
          :rules="[{ required: true, message: '请输入参数项' }]"
        >
          <el-input v-model="formState.parameterItem" placeholder="请输入参数项" />
        </el-form-item>
        <el-form-item
          label="参数类型"
          prop="type"
          :rules="[{ required: true, message: '请选择参数类型' }]"
        >
          <el-select v-model="formState.type" placeholder="请选择参数类型" style="width: 100%;">
            <el-option
              v-for="dict in parameter_tyep"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item
          label="单位"
          prop="unit"
          :rules="[{ required: true, message: '请输入单位' }]"
        >
          <el-input v-model="formState.unit" placeholder="请输入单位" />
        </el-form-item>
        <el-form-item label="参数格式" prop="parameterFormat">
          <el-input v-model="formState.parameterFormat" placeholder="请输入参数格式(选填)" />
        </el-form-item>
        <el-form-item
          label="是否必填"
          prop="isRequired"
          :rules="[{ required: true, message: '请选择是否必填' }]"
        >
          <el-select v-model="formState.isRequired" placeholder="请选择" style="width: 100%;">
            <el-option label="是" value="1" />
            <el-option label="否" value="0" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance } from "vue";
import { add } from "@/api/productionManagement/productionRecords.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
});
const emit = defineEmits(["update:visible", "completed"]);
const formRef = ref(null);
const formState = ref({
  parameterItem: "",
  type: "",
  unit: "",
  parameterFormat: "",
  isRequired: undefined,
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit("update:visible", val);
  },
});
const { proxy } = getCurrentInstance();
const { parameter_tyep } = proxy.useDict("parameter_tyep");
const closeModal = () => {
  isShow.value = false;
};
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      add(formState.value).then(() => {
        isShow.value = false;
        emit("completed");
        proxy.$modal.msgSuccess("新增成功");
      });
    }
  });
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/productionManagement/productionRecords/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
<!-- ç”Ÿäº§è®°å½• -->
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="参数编码:">
          <el-input
            v-model="searchForm.code"
            placeholder="请输入"
            clearable
            style="width: 200px;"
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="参数项:">
          <el-input
            v-model="searchForm.parameterItem"
            placeholder="请输入"
            clearable
            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
              v-for="dict in parameter_tyep"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div style="text-align: right" class="mb10">
        <el-button type="primary" @click="showNewModal">新增</el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      />
    </div>
    <NewRecord v-if="isShowNewModal" v-model:visible="isShowNewModal" @completed="getList" />
    <EditRecord
      v-if="isShowEditModal"
      v-model:visible="isShowEditModal"
      :record="record"
      @completed="getList"
    />
  </div>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
import NewRecord from "./New.vue";
import EditRecord from "./Edit.vue";
import { listPage, del } from "@/api/productionManagement/productionRecords.js";
const data = reactive({
  searchForm: {
    code: "",
    parameterItem: "",
    type: "",
  },
});
const { searchForm } = toRefs(data);
const { proxy } = getCurrentInstance();
const { parameter_tyep } = proxy.useDict("parameter_tyep");
const tableColumn = ref([
  { label: "参数编码", prop: "code" },
  { label: "参数项", prop: "parameterItem" },
  {
    label: "参数类型",
    prop: "type",
    formatData: (params) =>
      (parameter_tyep?.value ?? parameter_tyep ?? []).find((d) => String(d.value) === String(params))?.label ?? params,
  },
  { label: "单位", prop: "unit" },
  { label: "参数格式", prop: "parameterFormat" },
  {
    label: "是否必填",
    prop: "isRequired",
    formatData: (params) => (params === '1' ? "是" : "否"),
  },
//   { label: "创建人", prop: "createUser" },
  { label: "创建日期", prop: "createTime" },
//   { label: "更新人", prop: "updateUser" },
  { label: "更新日期", prop: "updateTime" },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 160,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => showEditModal(row),
      },
      {
        name: "删除",
        type: "text",
        clickFun: (row) => handleDeleteRow(row),
      },
    ],
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const isShowNewModal = ref(false);
const isShowEditModal = ref(false);
const record = ref({});
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  listPage(params)
    .then((res) => {
      tableLoading.value = false;
      const data = res.data;
      tableData.value = data?.records ?? (Array.isArray(data) ? data : []);
      page.total = data?.total ?? 0;
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
const showNewModal = () => {
  isShowNewModal.value = true;
};
const showEditModal = (row) => {
  record.value = { ...row };
  isShowEditModal.value = true;
};
const handleDeleteRow = (row) => {
  proxy.$modal
    .confirm(`是否确认删除参数编码为"${row.code}"的数据?`)
    .then(() => del([row.id]))
    .then(() => {
      getList();
      proxy.$modal.msgSuccess("删除成功");
    })
    .catch(() => {});
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/productionManagement/productionReporting/index.vue
@@ -173,11 +173,6 @@
      width: 120,
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: 120,
    },
    {
      label: "产品名称",
      prop: "productName",
      width: 120,
src/views/productionManagement/workOrder/index.vue
@@ -176,6 +176,16 @@
                    readonly
                    style="width: 300px" />
        </el-form-item>
        <el-form-item label="投入总量(kg)"
                      prop="totalInvestment">
          <el-input v-model.number="reportForm.totalInvestment"
                    type="number"
                    min="1"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入投入总量"
                    @input="handleTotalInvestmentInput" />
        </el-form-item>
        <el-form-item label="本次生产数量"
                      prop="quantity">
          <el-input v-model.number="reportForm.quantity"
@@ -195,6 +205,16 @@
                    style="width: 300px"
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
        </el-form-item>
        <el-form-item label="检品数量"
                      prop="inspectedQuantity">
          <el-input v-model.number="reportForm.inspectedQuantity"
                    type="number"
                    min="0"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入检品数量"
                    @input="handleInspectedQuantity"/>
        </el-form-item>
        <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
@@ -270,6 +290,10 @@
    {
      label: "工序名称",
      prop: "processName",
    },
    {
      label: "投入总量(kg)",
      prop: "totalInvestment",
    },
    {
      label: "预计生产数量",
@@ -358,8 +382,10 @@
  const userOptions = ref([]);
  const reportForm = reactive({
    planQuantity: 0,
    totalInvestment: 0,
    quantity: null,
    scrapQty: null,
    inspectedQuantity: null,
    userName: "",
    workOrderId: "",
    reportWork: "",
@@ -368,6 +394,26 @@
    productMainId: null,
  });
  // æŠ•入总量验证规则
  const validateTotalInvestment = (rule, value, callback) => {
    if (value === null || value === undefined || value === "") {
      callback(new Error("请输入投入总量"));
      return;
    }
    const num = Number(value);
    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
      callback(new Error("投入总量必须大于等于1"));
      return;
    }
    if (reportForm.quantity !== null && reportForm.quantity !== undefined && reportForm.quantity !== "") {
      if (num < Number(reportForm.quantity)) {
        callback(new Error("投入总量必须大于本次生产数量"));
        return;
      }
    }
    callback();
  };
  // æœ¬æ¬¡ç”Ÿäº§æ•°é‡éªŒè¯è§„则
  const validateQuantity = (rule, value, callback) => {
    if (value === null || value === undefined || value === "") {
@@ -375,10 +421,15 @@
      return;
    }
    const num = Number(value);
    // æ•´æ•°ä¸”大于等于1
    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
      callback(new Error("本次生产数量必须大于等于1"));
      return;
    }
    if (reportForm.totalInvestment !== null && reportForm.totalInvestment !== undefined && reportForm.totalInvestment !== "") {
      if (num > Number(reportForm.totalInvestment)) {
        callback(new Error("本次生产数量必须小于投入总量"));
        return;
      }
    }
    callback();
  };
@@ -400,37 +451,87 @@
  // éªŒè¯è§„则
  const reportFormRules = {
    totalInvestment: [{ required: true, validator: validateTotalInvestment, trigger: "blur" }],
    quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }],
    scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
  };
  // å¤„理本次生产数量输入,限制必须大于等于1
  const handleQuantityInput = value => {
  // å¤„理投入总量输入
  const handleTotalInvestmentInput = value => {
    if (value === "" || value === null || value === undefined) {
      reportForm.quantity = null;
      reportForm.totalInvestment = null;
      reportForm.scrapQty = null;
      return;
    }
    const num = Number(value);
    if (isNaN(num)) {
      return;
    }
    // å¦‚果小于1,清除
    if (num < 1) {
      reportForm.quantity = null;
      reportForm.totalInvestment = null;
      reportForm.scrapQty = null;
      return;
    }
    // å¦‚果是小数取整数部分
    if (!Number.isInteger(num)) {
      const intValue = Math.floor(num);
      // å¦‚果取整后小于1,清除
      if (intValue < 1) {
        reportForm.totalInvestment = null;
        reportForm.scrapQty = null;
        return;
      }
      reportForm.totalInvestment = intValue;
    } else {
      reportForm.totalInvestment = num;
    }
    // å¦‚æžœ quantity æœ‰å€¼ï¼Œè‡ªåŠ¨è®¡ç®— scrapQty = totalInvestment - quantity
    if (reportForm.quantity !== null && reportForm.quantity !== undefined && reportForm.quantity !== "") {
      const total = Number(reportForm.totalInvestment);
      const qty = Number(reportForm.quantity);
      if (total > qty) {
        reportForm.scrapQty = total - qty;
      } else {
        reportForm.scrapQty = null;
      }
    }
  };
  // å¤„理本次生产数量输入,限制必须大于等于1
  const handleQuantityInput = value => {
    if (value === "" || value === null || value === undefined) {
      reportForm.quantity = null;
      reportForm.scrapQty = null;
      return;
    }
    const num = Number(value);
    if (isNaN(num)) {
      return;
    }
    if (num < 1) {
      reportForm.quantity = null;
      reportForm.scrapQty = null;
      return;
    }
    if (!Number.isInteger(num)) {
      const intValue = Math.floor(num);
      if (intValue < 1) {
        reportForm.quantity = null;
        reportForm.scrapQty = null;
        return;
      }
      reportForm.quantity = intValue;
      return;
    } else {
      reportForm.quantity = num;
    }
    reportForm.quantity = num;
    // å¦‚æžœ totalInvestment æœ‰å€¼ï¼Œè‡ªåŠ¨è®¡ç®— scrapQty = totalInvestment - quantity
    if (reportForm.totalInvestment !== null && reportForm.totalInvestment !== undefined && reportForm.totalInvestment !== "") {
      const total = Number(reportForm.totalInvestment);
      const qty = Number(reportForm.quantity);
      if (total > qty) {
        reportForm.scrapQty = total - qty;
      } else {
        reportForm.scrapQty = null;
      }
    }
  };
  // å¤„理报废数量
@@ -440,22 +541,38 @@
      return;
    }
    const num = Number(value);
    // å¦‚果是NaN,保持原值
    if (isNaN(num)) {
      return;
    }
    // å¦‚果是负数,清除输入
    if (num < 0) {
      reportForm.scrapQty = null;
      return;
    }
    // å¦‚果是小数,取整数部分
    if (!Number.isInteger(num)) {
      reportForm.scrapQty = Math.floor(num);
      return;
    }
    // æœ‰æ•ˆçš„非负整数(包括0)
    reportForm.scrapQty = num;
  };
  const handleInspectedQuantity = value => {
    if (value === "" || value === null || value === undefined) {
      reportForm.inspectedQuantity = null;
      return;
    }
    const num = Number(value);
    if (isNaN(num)) {
      return;
    }
    if (num < 0) {
      reportForm.inspectedQuantity = null;
      return;
    }
    if (!Number.isInteger(num)) {
      reportForm.inspectedQuantity = Math.floor(num);
      return;
    }
    reportForm.inspectedQuantity = num;
  };
  const currentReportRowData = ref(null);
  const page = reactive({
@@ -597,12 +714,14 @@
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.planQuantity = row.planQuantity;
    reportForm.totalInvestment = row.totalInvestment;
    reportForm.quantity =
      row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
    reportForm.productMainId = row.productMainId;
    reportForm.inspectedQuantity = row.inspectedQuantity;
    reportForm.scrapQty =
      row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
    nextTick(() => {
@@ -676,12 +795,12 @@
        return;
      }
      if (quantity > reportForm.planQuantity) {
        ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // if (quantity > reportForm.planQuantity) {
      //   ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
      //     confirmButtonText: "确定",
      //   });
      //   return;
      // }
      const submitData = {
        ...reportForm,
src/views/qualityManagement/finalInspection/index.vue
@@ -109,6 +109,18 @@
    prop: "productName",
  },
  {
    label: "批号",
    prop: "batchNo",
  },
  {
    label: "检品数量",
    prop: "inspectedQuantity",
  },
  {
    label: "请检部门",
    prop: "manufacturingTeam",
  },
  {
    label: "规格型号",
    prop: "model",
  },