湟水峡
1.采购模块不要项目名称
2.加一个有待回款登记的提示
3.回款登记、付款登记改成和销售订单价格关联,并且可以多个一起回款或付款
4.合同管理不要下载合同了,跟合同相关的字段可以去掉了
5.重构生产模块
6.测试流程并修改bug
已添加40个文件
已重命名1个文件
已修改82个文件
已删除1个文件
16238 ■■■■ 文件已修改
src/api/basicData/productModel.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productProcess.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/supplierManageFile.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/noticeManagement.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/rulesRegulationsManagementFile.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/vehicleManagement.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/calibration.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/measurementEquipment.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/upkeep.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockIn.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockManage.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockOut.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffContract.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffLeave.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffOnJob.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementInvoiceLedger.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementLedger.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRoute.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/processRouteItem.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productBom.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productProcessRoute.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productStructure.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProcess.js 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductInput.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductMain.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionProductOutput.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionReporting.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/publicApi/commonFile.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/metricMaintenance.js 87 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/qualityTestStandardBinding.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/receiptPayment.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/viewIndex.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/Pagination.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/index.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/index.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/summarizeTable.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/customerFile/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/fileList.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index.vue 131 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/enterpriseBook/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/knowledgeBase/index.vue 181 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/meetingManagement/index.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/noticeManagement/index.vue 546 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetDraft/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/summary/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/purchaseApproval/index.vue 1064 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 333 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/vehicleManagement/index.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/issueManagement/index.vue 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDia.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/index.vue 572 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/components/Modal.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/index.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/indexOld.vue 712 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentEntry/index.vue 312 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentHistory/index.vue 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productManagement/productIdentifier/index.vue 777 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/components/formDia.vue 110 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/index.vue 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/Edit.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/ItemsForm.vue 531 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/New.vue 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/index.vue 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/processRouteItem/index.vue 876 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/Detail/index.vue 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/StructureEdit.vue 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productStructure/index.vue 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/index.vue 468 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 600 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/New.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/Input.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/Output.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 378 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 645 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/filesDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 1135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index0.vue 415 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nearExpiryReturn/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentHistory/index.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/productModel.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import request from "@/utils/request.js";
export function productModelList(query) {
    return request({
        url: '/basic/product/pageModel',
        method: 'get',
        params: query
    })
}
src/api/basicData/productProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from '@/utils/request'
// å·¥åºåˆ—表分页查询
export function productProcessListPage(query) {
  return request({
    url: '/productProcess/listPage',
    method: 'get',
    params: query
  })
}
src/api/basicData/supplierManageFile.js
@@ -49,4 +49,27 @@
        data: ids
    })
}
// æŸ¥è¯¢é™„件列表
export function fileListPage(query) {
    return request({
        url: "/basic/supplierManageFile/listPage",
        method: "get",
        params: query,
    });
}
// ä¿å­˜é™„件列表
export function fileAdd(query) {
    return request({
        url: "/basic/supplierManageFile/add",
        method: "post",
        data: query,
    });
}
// åˆ é™¤é™„件列表
export function fileDel(query) {
    return request({
        url: "/basic/supplierManageFile/del",
        method: "delete",
        data: query,
    });
}
src/api/collaborativeApproval/noticeManagement.js
@@ -50,3 +50,29 @@
        method: 'get',
    })
}
// æŸ¥è¯¢å…¬å‘Šç±»åž‹åˆ—表
export function listNoticeType() {
    return request({
        url: '/noticeType/list',
        method: 'get'
    })
}
// æ–°å¢žå…¬å‘Šç±»åž‹
export function addNoticeType(data) {
    return request({
        url: '/noticeType/add',
        method: 'post',
        data: data
    })
}
// åˆ é™¤å…¬å‘Šç±»åž‹
export function delNoticeType(id) {
    return request({
        url: '/noticeType/del',
        method: 'delete',
        data: { id }
    })
}
src/api/collaborativeApproval/rulesRegulationsManagementFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// é™„件列表
export function listRuleFiles(query) {
  return request({
    url: "/rulesRegulationsManagementFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žé™„ä»¶
export function addRuleFile(data) {
  return request({
    url: "/rulesRegulationsManagementFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤é™„件(支持传递 id æ•°ç»„)
export function delRuleFile(ids) {
  return request({
    url: "/rulesRegulationsManagementFile/del",
    method: "delete",
    data: ids,
  });
}
src/api/collaborativeApproval/vehicleManagement.js
@@ -7,7 +7,7 @@
    method: "get",
    params: {
      ...page,
      ...query
      ...query,
    },
  });
}
@@ -17,7 +17,7 @@
  return request({
    url: "/vehicleManagement/add",
    method: "post",
    data: data,
    data,
  });
}
@@ -26,7 +26,7 @@
  return request({
    url: "/vehicleManagement/update",
    method: "post",
    data: data,
    data,
  });
}
@@ -39,11 +39,11 @@
  });
}
// æ ¹æ®id查询车辆详情
// æ ¹æ® id æŸ¥è¯¢è½¦è¾†
export function getVehicleById(id) {
  return request({
    url: "/vehicleManagement/getById/" + id,
    method: "get"
    url: `/vehicleManagement/getById/${id}`,
    method: "get",
  });
}
@@ -52,7 +52,7 @@
  return request({
    url: "/vehicleManagement/useVehicle",
    method: "post",
    data: data,
    data,
  });
}
@@ -61,18 +61,18 @@
  return request({
    url: "/vehicleManagement/returnVehicle",
    method: "post",
    data: data,
    data,
  });
}
// æŸ¥è¯¢è½¦è¾†ä½¿ç”¨è®°å½•
// ä½¿ç”¨è®°å½•
export function getVehicleUsageRecords(page, query) {
  return request({
    url: "/vehicleManagement/getUsageRecords",
    method: "get",
    params: {
      ...page,
      ...query
      ...query,
    },
  });
}
src/api/equipmentManagement/calibration.js
@@ -25,3 +25,11 @@
    data: query,
  });
}
// åˆ é™¤è®°å½•
export function ledgerRecordDelete(ids) {
  return request({
    url: "/measuringInstrumentLedgerRecord/delete",
    method: "delete",
    data: ids,
  });
}
src/api/equipmentManagement/measurementEquipment.js
@@ -33,3 +33,23 @@
    data: query,
  });
}
// è®¡é‡å™¨å…·å°è´¦-新增
// /measuringInstrumentLedger/add
export function addMeasuringInstrumentLedger(data){
    return request({
        url:"/measuringInstrumentLedger/add",
        method:"post",
        data
    })
}
// è®¡é‡å™¨å…·å°è´¦-编辑
// /measuringInstrumentLedger/update
export function updateMeasuringInstrumentLedger(data){
    return request({
        url:"/measuringInstrumentLedger/update",
        method:"post",
        data
    })
}
src/api/equipmentManagement/upkeep.js
@@ -70,3 +70,35 @@
    method: "delete",
  });
};
// æ·»åŠ è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡
export const deviceMaintenanceTaskAdd = (params) => {
  return request({
    url: '/deviceMaintenanceTask/add',
    method: "post",
    data: params,
  });
};
// ä¿®æ”¹è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡
export const deviceMaintenanceTaskEdit = (params) => {
  return request({
    url: '/deviceMaintenanceTask/update',
    method: "post",
    data: params,
  });
};
// è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡åˆ—è¡¨
export const deviceMaintenanceTaskList = (params) => {
  return request({
    url: '/deviceMaintenanceTask/listPage',
    method: "get",
    params: params,
  });
};
// è®¾å¤‡ä¿å…»å®šæ—¶ä»»åŠ¡åˆ—è¡¨
export const deviceMaintenanceTaskDel = (params) => {
  return request({
    url: '/deviceMaintenanceTask/delete',
    method: "delete",
    data: params,
  });
};
src/api/inventoryManagement/stockIn.js
@@ -18,6 +18,15 @@
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“信息列表
export const getStockInPageByProductProduction = (params) => {
    return request({
        url: "/stockin/listPageByProductProduction",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-查询自定义入库信息列表
export const getStockInPageByCustom = (params) => {
    return request({
@@ -61,6 +70,14 @@
        data,
    });
};
// ä¿®æ”¹ææ–™åº“存信息
export const updateManagementByCustom = (data) => {
    return request({
        url: "/stockin/updateManagementByCustom ",
        method: "post",
        data,
    });
};
// æ–°å¢žå•†å“å…¥åº“信息
export function addSutockIn(data) {
@@ -84,6 +101,14 @@
export function updateStockInCustom(data) {
    return request({
        url: '/stockin/updateCustom',
        method: 'post',
        data: data
    })
}
// ç¼–辑成品入库信息
export function updateProduct(data) {
    return request({
        url: '/stockin/update',
        method: 'post',
        data: data
    })
@@ -126,5 +151,11 @@
}
//
//查询库存图表数据
export function getStockInChartData() {
    return request({
        url: '/stockin/listReport',
        method: 'get'
    })
}
src/api/inventoryManagement/stockManage.js
@@ -17,7 +17,14 @@
        params,
    });
};
// æŸ¥è¯¢æˆå“åº“存信息列表
export const getStockManageProduction = (params) => {
    return request({
        url: "/stockin/listPageProductionStock",
        method: "get",
        params,
    });
};
// æŸ¥è¯¢è‡ªå®šä¹‰å…¥åº“库存信息列表
export const getStockManagePageByCustom = (params) => {
    return request({
src/api/inventoryManagement/stockOut.js
@@ -9,6 +9,15 @@
    });
};
// å‡ºåº“台账-生产出库查询出库列表
export const getStockOutSemiProductPage = (params) => {
    return request({
        url: "/stockmanagement/listPageBySemiProduct",
        method: "get",
        params,
    });
};
//新增出库信息
export const addStockOut = (data) => {
    return request({
src/api/personnelManagement/staffContract.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from "@/utils/request.js";
export function findStaffContractListPage(query) {
    return request({
        url: "/staff/staffContract/listPage",
        method: "get",
        params: query,
    });
}
src/api/personnelManagement/staffLeave.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
import request from "@/utils/request.js";
export function findStaffLeaveListPage(query) {
    return request({
        url: "/staff/staffLeave/listPage",
        method: "get",
        params: query,
    });
}
export function createStaffLeave(data) {
    return request({
        url: "/staff/staffLeave",
        method: "post",
        data: data,
    });
}
export function updateStaffLeave(id, data) {
    return request({
        url: "/staff/staffLeave/" + id,
        method: "put",
        data: data,
    });
}
export function batchDeleteStaffLeaves(data) {
    return request({
        url: "/staff/staffLeave/del",
        method: "delete",
        data: data,
    });
}
src/api/personnelManagement/staffOnJob.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
import request from '@/utils/request'
// æŸ¥è¯¢åœ¨èŒå‘˜å·¥å°è´¦
export function staffOnJobListPage(query) {
    return request({
        url: '/staff/staffOnJob/listPage',
        method: 'get',
        params: query,
    })
}
// æŸ¥è¯¢å‘˜å·¥å…¥èŒä¿¡æ¯
export function staffOnJobInfo(id, query) {
    return request({
        url: '/staff/staffOnJob/' + id,
        method: 'get',
        params: query,
    })
}
// æ–°å¢žå‘˜å·¥
export function createStaffOnJob(params) {
    return request({
        url: "/staff/staffOnJob",
        method: "post",
        data: params,
    });
}
// ä¿®æ”¹å‘˜å·¥
export function updateStaffOnJob(id, params) {
    return request({
        url: "/staff/staffOnJob/" + id,
        method: "put",
        data: params,
    });
}
// åˆ é™¤å‘˜å·¥
export function batchDeleteStaffOnJobs(query) {
    return request({
        url: "/staff/staffOnJob/del",
        method: "delete",
        data: query,
    });
}
// ç»­ç­¾åˆåŒ
export function renewContract(id, params) {
    return request({
        url: "/staff/staffOnJob/renewContract/" + id,
        method: "post",
        data: params,
    });
}
src/api/procurementManagement/procurementInvoiceLedger.js
@@ -61,7 +61,7 @@
// æŸ¥è¯¢åˆ—表
export function invoiceListPage(query) {
  return request({
    url: "/purchase/registration/listPage",
    url: "/sales/product/listPagePurchaseLedger",
    method: "get",
    params: query,
  });
src/api/procurementManagement/procurementLedger.js
@@ -57,7 +57,7 @@
    params: query,
  });
}
// æŸ¥è¯¢é‡‡è´­å°è´¦åˆ—表
export function purchaseListPage(query) {
  return request({
    url: "/purchase/ledger/listPage",
@@ -72,3 +72,30 @@
    method: "get",
  });
}
export function updateApprovalStatus(query) {
    return request({
        url: "/purchase/ledger/updateApprovalStatus",
        method: "post",
        data: query,
    });
}
// ä¿å­˜é‡‡è´­æ¨¡æ¿
// /purchase/ledger/addPurchaseTemplate
export function addPurchaseTemplate(data) {
    return request({
        url: "/purchase/ledger/addPurchaseTemplate",
        method: "post",
        data: data,
    });
}
// æŸ¥è¯¢é‡‡è´­æ¨¡æ¿
// /purchase/ledger/getPurchaseTemplateList
export function getPurchaseTemplateList(query) {
    return request({
        url: "/purchase/ledger/getPurchaseTemplateList",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/processRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
// å·¥è‰ºè·¯çº¿é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/processRoute/page",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/processRoute",
    method: "post",
    data: data,
  });
}
export function del(ids) {
  return request({
    url: '/processRoute/' + ids,
    method: 'delete',
  })
}
export function update(data) {
  return request({
    url: '/processRoute',
    method: 'put',
    data: data,
  })
}
// èŽ·å–è¯¦æƒ…
export function getById(id) {
  return request({
    url: `/processRoute/${id}`,
    method: 'get',
  })
}
src/api/productionManagement/processRouteItem.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProcessRouteItemList(query) {
  return request({
    url: "/processRouteItem/list",
    method: "get",
    params: query,
  });
}
export function addOrUpdateProcessRouteItem(data) {
  return request({
    url: "/processRouteItem",
    method: "post",
    data: data,
  });
}
// æŽ’序接口
export function sortProcessRouteItem(data) {
  return request({
    url: "/processRouteItem/sort",
    method: "post",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤æŽ¥å£
export function batchDeleteProcessRouteItem(ids) {
  // å°†id数组转换为逗号分隔的字符串,拼接到URL后面
  const idsStr = Array.isArray(ids) ? ids.join(",") : ids;
  return request({
    url: `/processRouteItem/batchDelete/${idsStr}`,
    method: "delete",
  });
}
src/api/productionManagement/productBom.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
// äº§å“BOM页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productBom/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢ž
export function add(data) {
  return request({
    url: "/productBom/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹
export function update(data) {
  return request({
    url: "/productBom/update",
    method: "put",
    data: data,
  });
}
// æ‰¹é‡åˆ é™¤
export function batchDelete(ids) {
  return request({
    url: "/productBom/batchDelete",
    method: "delete",
    data: ids,
  });
}
// æ ¹æ®äº§å“åž‹å·ID查询BOM
export function getByModel(productModelId) {
  return request({
    url: "/productBom/getByModel",
    method: "get",
    params: { productModelId },
  });
}
src/api/productionManagement/productProcessRoute.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
// å·¥è‰ºè·¯çº¿é¡¹ç›®é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ—表查询
export function findProductProcessRouteItemList(query) {
  return request({
    url: "/productProcessRoute/list",
    method: "get",
    params: query,
  });
}
export function addOrUpdateProductProcessRouteItem(data) {
  return request({
    url: "/productProcessRoute/updateRouteItem",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§è®¢å•下:新增工艺路线项目
export function addRouteItem(data) {
  return request({
    url: "/productProcessRoute/addRouteItem",
    method: "post",
    data,
  });
}
// èŽ·å–ç”Ÿäº§è®¢å•å…³è”çš„å·¥è‰ºè·¯çº¿ä¸»ä¿¡æ¯
export function listMain(orderId) {
  return request({
    url: "/productProcessRoute/listMain",
    method: "get",
    params: { orderId },
  });
}
// åˆ é™¤å·¥è‰ºè·¯çº¿é¡¹ç›®ï¼ˆè·¯ç”±åŽæ‹¼æŽ¥ id)
export function deleteRouteItem(id) {
  return request({
    url: `/productProcessRoute/deleteRouteItem/${id}`,
    method: "delete",
  });
}
// ç”Ÿäº§è®¢å•下:排序工艺路线项目
export function sortRouteItem(data) {
  return request({
    url: "/productProcessRoute/sortRouteItem",
    method: "post",
    data,
  });
}
src/api/productionManagement/productStructure.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
// äº§å“ç»“构页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function queryList(id) {
  return request({
    url: "/productStructure/listBybomId/" + id,
    method: "get",
  });
}
export function add(data) {
  return request({
    url: "/productStructure",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productionOrder.js
@@ -4,11 +4,74 @@
// åˆ†é¡µæŸ¥è¯¢
export function schedulingListPage(query) {
  return request({
    url: "/productionOrder/listPage",
    url: "/salesLedger/scheduling/listPage",
    method: "get",
    params: query,
  });
}
export function productOrderListPage(query) {
  return request({
    url: "/productOrder/page",
    method: "get",
    params: query,
  });
}
// ç”Ÿäº§è®¢å•-按产品型号查询可用工艺路线列表
export function listProcessRoute(query) {
  return request({
    url: "/productOrder/listProcessRoute",
    method: "get",
    params: query,
  });
}
// ç”Ÿäº§è®¢å•-绑定工艺路线
export function bindingRoute(data) {
  return request({
    url: "/productOrder/bindingRoute",
    method: "post",
    data,
  });
}
// ç”Ÿäº§è®¢å•-查询产品结构列表
export function listProcessBom(query) {
  return request({
    url: "/productOrder/listProcessBom",
    method: "get",
    params: query,
  });
}
// èŽ·å–ç‚’æœºæ­£åœ¨å·¥ä½œé‡æ•°æ®
export function schedulingList(query) {
  return request({
    url: "/salesLedger/scheduling/list",
    method: "get",
    params: query,
  });
}
// ä¿å­˜ç‚’机设置
export function addSpeculatTrading(data) {
  return request({
    url: "/salesLedger/scheduling/addSpeculatTrading",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹ç‚’机设置
export function updateSpeculatTrading(data) {
  return request({
    url: "/salesLedger/scheduling/updateSpeculatTrading",
    method: "post",
    data: data,
  });
}
// ç”Ÿäº§æ´¾å·¥
export function productionDispatch(query) {
  return request({
@@ -17,28 +80,37 @@
    data: query,
  });
}
// è‡ªåŠ¨æ´¾å·¥
export function productionDispatchList(query) {
  return request({
    url: "/salesLedger/scheduling/productionDispatchList",
    method: "post",
    data: query,
  });
}
// æ–°å¢žç”Ÿäº§è®¢å•
export function addProductionOrder(query) {
// æŸ¥è¯¢æŸè€—率
export function getLossRate() {
  return request({
    url: "/productionOrder/addProductionOrder",
    method: "post",
    data: query,
    url: "/salesLedger/scheduling/loss",
    method: "get",
  });
}
// ä¿®æ”¹ç”Ÿäº§è®¢å•
export function updateProductionOrder(query) {
// æ–°å¢žæŸè€—率
export function addLossRate(data) {
  return request({
    url: "/productionOrder/updateProductionOrder",
    url: "/salesLedger/scheduling/addLoss",
    method: "post",
    data: query,
    data: data,
  });
}
// åˆ é™¤ç”Ÿäº§è®¢å•
export function deleteProductionOrder(query) {
// ä¿®æ”¹æŸè€—率
export function updateLossRate(data) {
  return request({
    url: "/productionOrder/deleteProductionOrder",
    method: "delete",
    data: query,
    url: "/salesLedger/scheduling/updateLoss",
    method: "post",
    data: data,
  });
}
src/api/productionManagement/productionProcess.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
// å·¥åºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
  return request({
    url: "/productProcess/listPage",
    method: "get",
    params: query,
  });
}
export function processList(query) {
  return request({
    url: "/productProcess/list",
    method: "get",
    params: query,
  });
}
export function add(data) {
  return request({
    url: "/productProcess",
    method: "post",
    data: data,
  });
}
export function del(data) {
  return request({
    url: '/productProcess/batchDelete',
    method: 'delete',
    data: data,
  })
}
export function update(data) {
  return request({
    url: '/productProcess/update',
    method: 'put',
    data: data,
  })
}
// å·¥åºæŸ¥è¯¢
export function list() {
    return request({
        url: "/productProcess/list",
        method: "get",
    });
}
// å¯¼å…¥æ•°æ®
export function importData(data) {
  return request({
    url: "/productProcess/importData",
    method: "post",
    data: data,
  });
}
// ä¸‹è½½æ¨¡æ¿
export function downloadTemplate() {
  return request({
    url: "/productProcess/downloadTemplate",
    method: "post",
    responseType: "blob",
  });
}
src/api/productionManagement/productionProductInput.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§æŠ•入页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductInputListPage(query) {
    return request({
        url: "/productionProductInput/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionProductMain.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§æŠ¥å·¥é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductMainListPage(query) {
    return request({
        url: "/productionProductMain/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionProductOutput.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// ç”Ÿäº§äº§å‡ºé¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
export function productionProductOutputListPage(query) {
    return request({
        url: "/productionProductOutput/listPage",
        method: "get",
        params: query,
    });
}
src/api/productionManagement/productionReporting.js
@@ -33,3 +33,11 @@
    data: query,
  });
}
// ç”Ÿäº§æŠ¥å·¥-删除
export function productionReportDelete(query) {
  return request({
    url: "/productionProductMain/delete",
    method: "delete",
    data: query,
  });
}
src/api/productionManagement/workOrder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import request from "@/utils/request";
export function productWorkOrderPage(query) {
  return request({
    url: "/productWorkOrder/page",
    method: "get",
    params: query,
  });
}
export function updateProductWorkOrder(data) {
  return request({
    url: "/productWorkOrder/updateProductWorkOrder",
    method: "post",
    data: data,
  });
}
export function addProductMain(data) {
  return request({
    url: "/productionProductMain/addProductMain",
    method: "post",
    data: data,
  });
}
src/api/publicApi/commonFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
// å…¬å…±æ–‡ä»¶ç®¡ç†æŽ¥å£
import request from '@/utils/request'
// åˆ é™¤å…¬å…±æ–‡ä»¶
export function delCommonFile(ids) {
  return request({
    url: '/commonFile/delCommonFile',
    method: 'delete',
    data: ids
  })
}
// å¼€ç¥¨å°è´¦æ–‡ä»¶åˆ é™¤
export function delCommonFileInvoiceLedger(ids) {
  return request({
    url: '/invoiceLedger/delFile',
    method: 'delete',
    data: ids
  })
}
src/api/qualityManagement/metricMaintenance.js
@@ -1,45 +1,100 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æŸ¥è¯¢æŒ‡æ ‡åˆ—表
export function qualityTestStandardListPage(query) {
    return request({
        url: '/quality/qualityTestStandard/listPage',
        method: 'get',
    url: "/qualityTestStandard/listPage",
    method: "get",
        params: query,
    })
  });
}
// æ–°å¢žæŒ‡æ ‡åˆ—表
export function qualityTestStandardAdd(query) {
    return request({
        url: '/quality/qualityTestStandard/add',
        method: 'post',
    url: "/qualityTestStandard/add",
    method: "post",
        data: query,
    })
  });
}
// ä¿®æ”¹æŒ‡æ ‡åˆ—表
export function qualityTestStandardUpdate(query) {
    return request({
        url: '/quality/qualityTestStandard/update',
        method: 'post',
    url: "/qualityTestStandard/update",
    method: "post",
        data: query,
    })
  });
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityTestStandardDel(query) {
    return request({
        url: '/quality/qualityTestStandard/del',
        method: 'delete',
    url: "/qualityTestStandard/del",
    method: "delete",
        data: query,
    })
  });
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityInspectDetailByProductId(productId) {
    return request({
        url: '/quality/qualityTestStandard/product/' + productId,
        method: 'get',
    })
    url: "/qualityTestStandard/product/" + productId,
    method: "get",
  });
}
// å¤åˆ¶æ ‡å‡†å‚æ•°
export function qualityTestStandardCopyParam(id) {
  return request({
    url: "/qualityTestStandard/copyParam",
    method: "post",
    data: { id },
  });
}
// æ‰¹é‡å®¡æ ¸ï¼ˆçŠ¶æ€ï¼š1=通过/批准,2=撤销)
// ä¼ å‚:[{ id, state }]
export function qualityTestStandardAudit(data) {
  return request({
    url: "/qualityTestStandard/qualityTestStandardAudit",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:列表(不分页)
export function qualityTestStandardParamList(query) {
  return request({
    url: "/qualityTestStandardParam/list",
    method: "get",
    params: query,
  });
}
// æ ‡å‡†å‚数:新增
export function qualityTestStandardParamAdd(data) {
  return request({
    url: "/qualityTestStandardParam/add",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:修改
export function qualityTestStandardParamUpdate(data) {
  return request({
    url: "/qualityTestStandardParam/update",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:删除(传 id æ•°ç»„)
export function qualityTestStandardParamDel(ids) {
  return request({
    url: "/qualityTestStandardParam/del",
    method: "delete",
    data: ids,
  });
}
src/api/qualityManagement/qualityTestStandardBinding.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// ç»‘定列表(不分页)
export function qualityTestStandardBindingList(query) {
  return request({
    url: "/qualityTestStandardBinding/list",
    method: "get",
    params: query,
  });
}
// æ–°å¢žç»‘定(支持批量)
export function qualityTestStandardBindingAdd(data) {
  return request({
    url: "/qualityTestStandardBinding/add",
    method: "post",
    data,
  });
}
// åˆ é™¤ç»‘定(传 id æ•°ç»„)
export function qualityTestStandardBindingDel(ids) {
  return request({
    url: "/qualityTestStandardBinding/del",
    method: "delete",
    data: ids,
  });
}
src/api/salesManagement/receiptPayment.js
@@ -40,7 +40,7 @@
// æŸ¥è¯¢å·²ç»ç»‘定发票的开票台账
export function bindInvoiceNoRegPage(query) {
    return request({
        url: '/receiptPayment/bindInvoiceNoRegPage',
        url: '/sales/product/listPageSalesLedger',
        method: 'get',
        params: query
    })
src/api/system/post.js
@@ -9,6 +9,15 @@
  })
}
export function findPostOptions(query) {
  return request({
    url: '/system/post/optionselect',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å²—位详细
export function getPost(postId) {
  return request({
src/api/viewIndex.js
@@ -53,3 +53,20 @@
        method: 'get'
    })
}
// å„生产订单的完成进度统计
// /home/progressStatistics
export const getProgressStatistics = ()=>{
    return request({
        url: '/home/progressStatistics',
        method: 'get'
    })
}
//在制品周转情况
//home/workInProcessTurnover
export const getWorkInProcessTurnover= ()=>{
    return request({
        url: '/home/workInProcessTurnover',
        method: 'get'
    })
}
src/components/PIMTable/PIMTable.vue
@@ -40,12 +40,22 @@
      :fixed="item.fixed"
      :label="item.label"
      :prop="item.prop"
      show-overflow-tooltip
      :show-overflow-tooltip="item.dataType !== 'action' && item.dataType !== 'slot'"
      :align="item.align"
      :sortable="!!item.sortable"
      :type="item.type"
      :width="item.width"
    >
      <template #header="scope">
        <div class="pim-table-header-cell">
          <div class="pim-table-header-title">
            {{ item.label }}
          </div>
          <div v-if="item.headerSlot" class="pim-table-header-extra">
            <slot :name="item.headerSlot" :column="scope.column" />
          </div>
        </div>
      </template>
      <template
        v-if="item.hasOwnProperty('colunmTemplate')"
        #[item.colunmTemplate]="scope"
@@ -120,7 +130,7 @@
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'">
        <div v-else-if="item.dataType == 'action'" @click.stop>
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
@@ -135,7 +145,7 @@
                    : o.color,
              }"
              link
              @click="o.clickFun(scope.row)"
              @click.stop="o.clickFun(scope.row)"
              :key="key"
            >
              {{ o.name }}
@@ -204,7 +214,7 @@
    </el-table-column>
  </el-table>
  <pagination
    v-if="showPagination"
        v-if="isShowPagination"
    :total="page.total"
    :layout="page.layout"
    :page="page.current"
@@ -268,6 +278,10 @@
    type: Boolean,
    default: false,
  },
    isShowPagination: {
    type: Boolean,
    default: true,
  },
  isShowSummary: {
    type: Boolean,
    default: false,
@@ -316,10 +330,6 @@
  tableStyle: {
    type: [String, Object],
    default: () => ({ width: "100%" }),
  },
  showPagination: {
    type: Boolean,
    default: true,
  },
});
@@ -434,4 +444,9 @@
  padding-right: 0 !important;
  padding-left: 0 !important;
}
.pim-table-header-extra :deep(.el-input),
.pim-table-header-extra :deep(.el-select) {
  width: 100%;
}
</style>
src/components/PIMTable/Pagination.vue
@@ -2,10 +2,10 @@
  <div :class="{ hidden }" class="pagination-container">
    <el-pagination
      :background="background"
      v-model:currentPage="currentPage"
      v-model:pageSize="pageSize"
      v-model:current-page="currentPage"
      v-model:page-size="pageSize"
      :layout="layout"
      :page-size-options="pageSizes"
      :page-sizes="pageSizes"
      :pager-count="pagerCount"
      :total="total"
      v-bind="$attrs"
src/layout/components/Navbar.vue
@@ -6,24 +6,6 @@
      <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
    </div>
    <!--    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />-->
    <div class="center-menu">
      <span class="label">{{ userStore.currentFactoryName }}</span>
      <el-dropdown @command="handleFactoryChange" class="right-menu-item hover-effect" trigger="click">
        <div>
          <el-icon size="20">
            <Switch />
          </el-icon>
        </div>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item v-for="item in factoryList" :key="item.deptId" :command="item">
              {{ item.deptName }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div class="right-menu">
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
          <div class="avatar-wrapper">
@@ -46,7 +28,6 @@
        </el-dropdown>
      </div>
    </div>
  </div>
</template>
<script setup>
@@ -62,9 +43,6 @@
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import { userLoginFacotryList } from "@/api/system/user.js"
import Cookies from "js-cookie";
import { decrypt } from "@/utils/jsencrypt"
const appStore = useAppStore()
const userStore = useUserStore()
@@ -112,16 +90,16 @@
  settingsStore.toggleTheme()
}
function getUserLoginFacotryList() {
  if (userStore.id) {
    userLoginFacotryList({ userId: userStore.id }).then(res => {
      console.log('res', res)
      factoryList.value = res.data
    })
  } else {
    factoryList.value = []
  }
}
// function getUserLoginFacotryList() {
//   if (userStore.id) {
//     userLoginFacotryList({ userId: userStore.id }).then(res => {
//       console.log('res', res)
//       factoryList.value = res.data
//     })
//   } else {
//     factoryList.value = []
//   }
// }
function handleFactoryChange(command) {
  console.log('command', command)
@@ -145,8 +123,6 @@
  const timestamp = new Date().getTime();
  window.location.href = `${currentUrl}?reload=${timestamp}`;
}
getUserLoginFacotryList();
</script>
<style lang='scss' scoped>
@@ -156,22 +132,6 @@
  position: relative;
  background: var(--navbar-bg);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .center-menu {
    line-height: 50px;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    .label {
      font-weight: bold;
      font-size: 18px;
      color: #333333;
      margin-right: 10px;
    }
  }
  .hamburger-container {
    line-height: 46px;
@@ -241,6 +201,19 @@
      }
    }
    .notification-container {
      margin-right: 20px;
      display: flex;
      align-items: center;
      cursor: pointer;
      .notification-badge {
        :deep(.el-badge__content) {
          border: none;
        }
      }
    }
    .avatar-container {
      margin-right: 40px;
@@ -266,4 +239,22 @@
    }
  }
}
</style>
<style lang="scss">
.notification-popover {
  padding: 0 !important;
  .el-popover__title {
    display: none;
  }
  .el-popover__body {
    padding: 0 !important;
  }
}
.el-badge__content.is-fixed{
  top: 12px;
}
</style>
src/layout/components/index.js
@@ -2,3 +2,4 @@
export { default as Navbar } from './Navbar'
export { default as Settings } from './Settings'
export { default as TagsView } from './TagsView/index.vue'
// export { default as NotificationCenter } from './NotificationCenter/index.vue'
src/store/modules/user.js
@@ -99,9 +99,8 @@
      loginCheckFactory(userInfo) {
        const username = userInfo.username.trim()
        const password = userInfo.password
        const factoryId = userInfo.currentFatoryId
        return new Promise((resolve, reject) => {
          loginCheckFactory(username, password, factoryId).then(res => {
          loginCheckFactory(username, password).then(res => {
            setToken(res.token)
            this.token = res.token
            resolve()
src/utils/index.js
@@ -396,3 +396,16 @@
export function isEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}
/**
 * èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
 * @returns {string} æ ¼å¼åŒ–的日期字符串
 */
export function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, '0'); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}
src/utils/summarizeTable.js
@@ -42,8 +42,7 @@
};
// ä¸å«ç¨Žæ€»ä»·è®¡ç®—
const calculateTaxExclusiveTotalPrice = (taxInclusiveTotalPrice, taxRate) => {
  const taxRateNumber = taxRate?Number(taxRate):0;
  const taxRateDecimal = taxRateNumber / 100;
  const taxRateDecimal = taxRate / 100;
  return (taxInclusiveTotalPrice / (1 + taxRateDecimal)).toFixed(2);
};
// å«ç¨Žæ€»ä»·è®¡ç®—
src/views/basicData/customerFile/index.vue
@@ -71,9 +71,9 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="客户类型:" prop="type">
            <el-form-item label="客户类型:" prop="customerType">
              <el-select
                v-model="form.type"
                v-model="form.customerType"
                placeholder="请选择"
                clearable
                style="width: 100%"
@@ -200,16 +200,13 @@
  },
  {
    label: "客户类型",
    prop: "type",
    prop: "customerType",
    dataType: "tag",
    formatData: (val) => val || "--",
    formatType: (val) => {
      const map = {
        ä¼ä¸š: "primary",
        ä¸ªäºº: "success",
        æ”¿åºœ: "warning",
        äº‹ä¸šå•位: "info",
        å…¶ä»–: "default",
                '一批商': "primary",
        '终端商': "success",
      };
      return map[val] || "info";
    },
@@ -238,9 +235,6 @@
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
                disabled: (row) => {
                    return row.maintainer !== userStore.nickName
                }
      },
    ],
@@ -252,11 +246,8 @@
const tableLoading = ref(false);
// å®¢æˆ·ç±»åž‹é€‰é¡¹
const customerTypeOptions = ref([
  { label: "企业", value: "企业" },
  { label: "个人", value: "个人" },
  { label: "政府", value: "政府" },
  { label: "事业单位", value: "事业单位" },
  { label: "其他", value: "其他" },
  { label: "一批商", value: "一批商" },
  { label: "终端商", value: "终端商" },
]);
const page = reactive({
  current: 1,
@@ -274,14 +265,14 @@
  },
  form: {
    customerName: "",
    type: "",
        customerType: "",
    companyAddress: "",
    maintainer: "",
    maintenanceTime: "",
  },
  rules: {
    customerName: [{ required: true, message: "请输入", trigger: "blur" }],
    type: [{ required: false, message: "请输入", trigger: "blur" }],
        customerType: [{ required: false, message: "请输入", trigger: "blur" }],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    maintainer: [{ required: false, message: "请选择", trigger: "change" }],
    maintenanceTime: [
src/views/basicData/product/ProductSelectDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
<template>
  <el-dialog
      v-model="visible"
      title="选择产品"
      width="900px"
      destroy-on-close
      :close-on-click-modal="false"
  >
    <el-form :inline="true" :model="query" class="mb-2">
      <el-form-item label="产品大类">
        <el-input
            v-model="query.productName"
            placeholder="输入产品大类"
            clearable
            @keyup.enter="onSearch"
        />
      </el-form-item>
      <el-form-item label="型号名称">
        <el-input
            v-model="query.model"
            placeholder="输入型号名称"
            clearable
            @keyup.enter="onSearch"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSearch">搜索</el-button>
        <el-button @click="onReset">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- åˆ—表 -->
    <el-table
        v-loading="loading"
        :data="tableData"
        height="420"
        highlight-current-row
        row-key="id"
        @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" />
      <el-table-column type="index" label="#" width="60"/>
      <el-table-column prop="productName" label="产品大类" min-width="160"/>
      <el-table-column prop="model" label="型号名称" min-width="200"/>
      <el-table-column prop="unit" label="单位" min-width="160"/>
    </el-table>
    <div class="mt-3 flex justify-end">
      <el-pagination
          background
          layout="total, sizes, prev, pager, next, jumper"
          :total="total"
          v-model:page-size="page.pageSize"
          v-model:current-page="page.pageNum"
          :page-sizes="[10, 20, 50, 100]"
          @size-change="onPageChange"
          @current-change="onPageChange"
      />
    </div>
    <template #footer>
      <el-button @click="close()">取消</el-button>
      <el-button type="primary" :disabled="multipleSelection.length === 0" @click="onConfirm">
        ç¡®å®š
      </el-button>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import {computed, onMounted, reactive, ref, watch} from "vue";
import {ElMessage} from "element-plus";
import {productModelList} from '@/api/basicData/productModel'
export type ProductRow = {
  id: number;
  productName: string;
  model: string;
  unit?: string;
};
const props = defineProps<{
  modelValue: boolean;
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
const visible = computed({
  get: () => props.modelValue,
  set: (v) => emit("update:modelValue", v),
});
const query = reactive({
  productName: "",
  model: "",
});
const page = reactive({
  pageNum: 1,
  pageSize: 10,
});
const loading = ref(false);
const tableData = ref<ProductRow[]>([]);
const total = ref(0);
const multipleSelection = ref<ProductRow[]>([])
function close() {
  visible.value = false;
}
const handleSelectionChange = (val: ProductRow[]) => {
  multipleSelection.value = val
}
function onSearch() {
  page.pageNum = 1;
  loadData();
}
function onReset() {
  query.productName = "";
  query.model = "";
  page.pageNum = 1;
  loadData();
}
function onPageChange() {
  loadData();
}
function onConfirm() {
  if (multipleSelection.value.length === 0) {
    ElMessage.warning("请选择一条产品");
    return;
  }
  emit("confirm", multipleSelection.value);
  close();
}
async function loadData() {
  loading.value = true;
  try {
    multipleSelection.value = []; // ç¿»é¡µ/搜索后清空选择更符合预期
    const res = await productModelList({
      productName: query.productName.trim(),
      model: query.model.trim(),
      current: page.pageNum,
      size: page.pageSize,
    });
    tableData.value = res.records;
    total.value = res.total;
  } finally {
    loading.value = false;
  }
}
onMounted(() => {
  loadData()
})
</script>
src/views/basicData/product/index.vue
@@ -25,9 +25,7 @@
          :data="list"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          default-expand-all
          :default-expanded-keys="expandedKeys"
          :draggable="true"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'label' }"
          highlight-current
src/views/basicData/supplierManage/index.vue
@@ -204,9 +204,6 @@
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
                disabled: (row) => {
                    return row.maintainUserName !== userStore.nickName
                }
      },
    ],
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -32,7 +32,7 @@
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row>
                <el-row v-if="!isQuotationApproval">
                    <el-col :span="24">
                        <el-form-item label="审批事由:" prop="approveReason">
                            <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" disabled/>
@@ -73,6 +73,54 @@
                    </el-col>
                </el-row>
            </el-form>
      <!-- æŠ¥ä»·å®¡æ‰¹ï¼šå±•示报价详情(复用销售报价“查看详情对话框”内容结构) -->
      <div v-if="isQuotationApproval" style="margin: 10px 0 18px;">
        <el-divider content-position="left">报价详情</el-divider>
        <el-skeleton :loading="quotationLoading" animated>
          <template #template>
            <el-skeleton-item variant="h3" style="width: 30%" />
            <el-skeleton-item variant="text" style="width: 100%" />
            <el-skeleton-item variant="text" style="width: 100%" />
          </template>
          <template #default>
            <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo" description="未查询到对应报价详情" />
            <template v-else>
              <el-descriptions :column="2" border>
                <el-descriptions-item label="报价单号">{{ currentQuotation.quotationNo }}</el-descriptions-item>
                <el-descriptions-item label="客户名称">{{ currentQuotation.customer }}</el-descriptions-item>
                <el-descriptions-item label="业务员">{{ currentQuotation.salesperson }}</el-descriptions-item>
                <el-descriptions-item label="报价日期">{{ currentQuotation.quotationDate }}</el-descriptions-item>
                <el-descriptions-item label="有效期至">{{ currentQuotation.validDate }}</el-descriptions-item>
                <el-descriptions-item label="付款方式">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
                <el-descriptions-item label="报价总额" :span="2">
                  <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
                    Â¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }}
                  </span>
                </el-descriptions-item>
              </el-descriptions>
              <div style="margin-top: 20px;">
                <h4>产品明细</h4>
                <el-table :data="currentQuotation.products || []" border style="width: 100%">
                  <el-table-column prop="product" label="产品名称" />
                  <el-table-column prop="specification" label="规格型号" />
                  <el-table-column prop="unit" label="单位" />
                  <el-table-column prop="unitPrice" label="单价">
                    <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template>
                  </el-table-column>
                </el-table>
              </div>
              <div v-if="currentQuotation.remark" style="margin-top: 20px;">
                <h4>备注</h4>
                <p>{{ currentQuotation.remark }}</p>
              </div>
            </template>
          </template>
        </el-skeleton>
      </div>
      <el-form :model="{ activities }" ref="formRef" label-position="top">
        <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical">
          <el-step
@@ -121,33 +169,16 @@
      <template #footer v-if="operationType === 'approval'">
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm(2)">不通过</el-button>
          <el-button type="primary" @click="openSignatureDialog(1)">通过</el-button>
          <el-button type="primary" @click="submitForm(1)">通过</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç”µå­ç­¾åå¼¹çª—(vue3-signature-pad) -->
    <el-dialog v-model="signatureDialogVisible" title="电子签名" width="600px" append-to-body>
            <vueEsign
                ref="esign"
                class="mySign"
                :width="800"
                :height="300"
                :isCrop="isCrop"
                :lineWidth="lineWidth"
                :lineColor="lineColor"
            />
      <div style="margin-top:10px;">
        <el-button @click="clearSignature">清除</el-button>
        <el-button type="primary" @click="confirmSignature">确定</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import { getCurrentInstance, reactive, ref, toRefs } from "vue";
import vueEsign from "vue-esign";
import { computed, getCurrentInstance, reactive, ref, toRefs } from "vue";
import {
    approveProcessDetails,
    getDept,
@@ -156,9 +187,16 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue'
import { getToken } from "@/utils/auth";
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
const emit = defineEmits(['close'])
const { proxy } = getCurrentInstance()
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 0
  }
})
const dialogFormVisible = ref(false);
const operationType = ref('')
@@ -167,6 +205,10 @@
const userStore = useUserStore()
const productOptions = ref([]);
const userList = ref([])
const quotationLoading = ref(false)
const currentQuotation = ref({})
const isQuotationApproval = computed(() => Number(props.approveType) === 6)
const data = reactive({
    form: {
        approveTime: "",
@@ -178,21 +220,6 @@
    },
});
const { form } = toRefs(data);
const signatureDialogVisible = ref(false);
const signatureImg = ref('');
let submitStatus = null; // ä¸´æ—¶å­˜å‚¨é€šè¿‡/不通过状态
const isCrop = ref("");
const esign = ref(null);
const lineWidth = ref(0);
const lineColor = ref("#000000");
// ä¸Šä¼ é…ç½®
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
// èŠ‚ç‚¹æ ‡é¢˜
const getNodeTitle = (index, len) => {
@@ -219,11 +246,27 @@
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  currentQuotation.value = {}
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    form.value = {...row}
    getProductOptions()
  // æŠ¥ä»·å®¡æ‰¹ï¼šç”¨å®¡æ‰¹äº‹ç”±å­—段承载的“报价单号”去查报价列表
  if (isQuotationApproval.value) {
    const quotationNo = row?.approveReason;
    if (quotationNo) {
      quotationLoading.value = true
      getQuotationList({ quotationNo }).then((res) => {
        const records = res?.data?.records || []
        currentQuotation.value = records[0] || {}
      }).finally(() => {
        quotationLoading.value = false
      })
    }
  }
  approveProcessDetails(row.approveId).then((res) => {
    activities.value = res.data
    // å¢žåŠ isApproval字段
@@ -248,77 +291,10 @@
        productOptions.value = res.data;
    });
};
// æ‰“开签名弹窗
const openSignatureDialog = (status) => {
  submitStatus = status;
  signatureDialogVisible.value = true;
};
// æ¸…除签名
const clearSignature = () => {
    esign.value.reset();
};
// ç¡®è®¤ç­¾å
const confirmSignature = () => {
    esign.value.generate().then((res) => {
        console.log(res);
        // å°†base64转换为二进制
        const base64Data = res.split(',')[1]; // ç§»é™¤data:image/png;base64,前缀
        const binaryString = atob(base64Data);
        const bytes = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        signatureImg.value = bytes;
        // åˆ›å»ºæ–‡ä»¶å¯¹è±¡ç”¨äºŽä¸Šä¼ 
        const blob = new Blob([bytes], { type: 'image/png' });
        const file = new File([blob], 'signature.png', { type: 'image/png' });
        // åˆ›å»ºFormData
        const formData = new FormData();
        formData.append('file', file);
        // ä¸Šä¼ ç­¾åå›¾ç‰‡
        fetch(upload.url, {
            method: 'POST',
            headers: upload.headers,
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            if (data.code === 200) {
                console.log('data---', data)
                let tempFileIds = [];
                tempFileIds.push(data.data.tempId);
                signatureDialogVisible.value = false;
                clearSignature();
                // åªæœ‰é€šè¿‡æ—¶æ‰ä¼ é€’签名文件ID
                if (submitStatus === 1) {
                    submitForm(submitStatus, tempFileIds);
                } else {
                    submitForm(submitStatus);
                }
            } else {
                proxy.$modal.msgError("签名图片上传失败:" + data.msg);
            }
        })
        .catch(error => {
            console.error('上传失败:', error);
            proxy.$modal.msgError("签名图片上传失败");
        });
    }).catch((err) => {
        console.log(err);
        proxy.$modal.msgWarning("请先签名!");
    })
};
// æäº¤å®¡æ‰¹
const submitForm = (status, tempFileIds) => {
const submitForm = (status) => {
  const filteredActivities = activities.value.filter(activity => activity.isShen);
  filteredActivities[0].approveNodeStatus = status;
  // åªæœ‰é€šè¿‡æ—¶æ‰éœ€è¦ç­¾å
  if (status === 1 && tempFileIds) {
    filteredActivities[0].tempFileIds = tempFileIds;
  }
  // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
  const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
  updateApproveNode({ ...filteredActivities[0], isLast }).then(() => {
@@ -330,6 +306,8 @@
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  quotationLoading.value = false
  currentQuotation.value = {}
  emit('close')
};
defineExpose({
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -16,11 +16,12 @@
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="申请部门:" prop="approveDeptId">
            <el-form-item label="申请部门:" prop="approveDeptName">
<!--              <el-input v-model="form.approveDeptName" placeholder="请输入" clearable/>-->
                            <el-select
                                disabled
                                v-model="form.approveDeptId"
                                placeholder="选择部门"
                @change="handleDeptChange"
                            >
                                <el-option
                                    v-for="user in productOptions"
@@ -145,6 +146,9 @@
                            <el-select
                                v-model="form.approveUser"
                                placeholder="选择人员"
                filterable
                default-first-option
                :reserve-keyword="false"
                            >
                                <el-option
                                    v-for="user in userList"
@@ -212,6 +216,8 @@
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import log from "@/views/monitor/job/log.vue";
const userStore = useUserStore();
const dialogFormVisible = ref(false);
@@ -229,6 +235,7 @@
    approveId: "",
    approveUser: "",
        approveDeptId: "",
    approveDeptName: "",
    approveReason: "",
    checkResult: "",
    tempFileIds: [],
@@ -242,7 +249,7 @@
    approveTime: [{ required: false, message: "请输入", trigger: "change" },],
    approveId: [{ required: false, message: "请输入", trigger: "blur" }],
    approveUser: [{ required: false, message: "请输入", trigger: "blur" }],
        approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }],
    approveDeptName: [{ required: true, message: "请输入", trigger: "blur" }],
    approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
    startDate: [{ required: true, message: "请选择请假开始时间", trigger: "change" }],
@@ -273,7 +280,17 @@
function removeApproverNode(index) {
  approverNodes.value.splice(index, 1)
}
// å¤„理部门选择变化
const handleDeptChange = (deptId) => {
  if (deptId) {
    const selectedDept = productOptions.value.find(dept => dept.deptId === deptId);
    if (selectedDept) {
      form.value.approveDeptName = selectedDept.deptName;
    }
  } else {
    form.value.approveDeptName = '';
  }
};
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
@@ -395,14 +412,6 @@
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
src/views/collaborativeApproval/approvalProcess/fileList.vue
@@ -1,11 +1,12 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose" draggable>
    <el-table :data="tableData" border height="40vh">
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="100" align="center">
      <el-table-column fixed="right" label="操作" width="150" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">下载</el-button>
          <el-button link type="primary" size="small" @click="lookFile(scope.row)">预览</el-button>
          <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
@@ -16,6 +17,8 @@
<script setup>
import { ref } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import { delCommonFile } from '@/api/publicApi/commonFile.js'
const dialogVisible = ref(false)
const tableData = ref([])
@@ -35,6 +38,27 @@
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
// åˆ é™¤é™„ä»¶
const handleDelete = (row) => {
  ElMessageBox.confirm(`确认删除附件"${row.name}"吗?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    delCommonFile([row.id]).then(() => {
      ElMessage.success('删除成功')
      // ä»Žåˆ—表中移除已删除的附件
      const index = tableData.value.findIndex(item => item.id === row.id)
      if (index !== -1) {
        tableData.value.splice(index, 1)
      }
    }).catch(() => {
      ElMessage.error('删除失败')
    })
  }).catch(() => {
    ElMessage.info('已取消删除')
  })
}
defineExpose({
  open
})
src/views/collaborativeApproval/approvalProcess/index.vue
@@ -1,5 +1,16 @@
<template>
  <div class="app-container">
    <!-- æ ‡ç­¾é¡µåˆ‡æ¢ä¸åŒçš„审批类型 -->
    <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="approval-tabs">
      <el-tab-pane label="公出管理" name="1"></el-tab-pane>
      <el-tab-pane label="请假管理" name="2"></el-tab-pane>
      <el-tab-pane label="出差管理" name="3"></el-tab-pane>
      <el-tab-pane label="报销管理" name="4"></el-tab-pane>
      <el-tab-pane label="采购审批" name="5"></el-tab-pane>
      <el-tab-pane label="报价审批" name="6"></el-tab-pane>
      <el-tab-pane label="出库审批" name="7"></el-tab-pane>
    </el-tabs>
    <div class="search_form">
      <div>
        <span class="search_title">流程编号:</span>
@@ -24,7 +35,7 @@
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增</el-button>
        <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">新增</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
@@ -32,7 +43,7 @@
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :column="tableColumnCopy"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
@@ -42,8 +53,8 @@
          :total="page.total"
      ></PIMTable>
    </div>
    <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="approveType"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia>
    <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia>
    <FileList ref="fileListRef" />
  </div>
</template>
@@ -51,22 +62,33 @@
<script setup>
import FileList from "./fileList.vue";
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance} from "vue";
import {ElMessageBox} from "element-plus";
import { useRoute } from 'vue-router';
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
// å®šä¹‰ç»„件接收的props
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 0
  }
const userStore = useUserStore();
const route = useRoute();
// å½“前选中的标签页,默认为公出管理
const activeTab = ref('1');
// å½“前审批类型,根据选中的标签页计算
const currentApproveType = computed(() => {
  return Number(activeTab.value);
});
const userStore = useUserStore();
// æ ‡ç­¾é¡µåˆ‡æ¢å¤„理
const handleTabChange = (tabName) => {
  // åˆ‡æ¢æ ‡ç­¾é¡µæ—¶é‡ç½®æœç´¢æ¡ä»¶å’Œåˆ†é¡µï¼Œå¹¶é‡æ–°åŠ è½½æ•°æ®
  searchForm.value.approveId = '';
  searchForm.value.approveStatus = '';
  page.current = 1;
  getList();
};
const data = reactive({
@@ -76,7 +98,15 @@
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
// åŠ¨æ€è¡¨æ ¼åˆ—é…ç½®ï¼Œæ ¹æ®å®¡æ‰¹ç±»åž‹ç”Ÿæˆåˆ—
const tableColumnCopy = computed(() => {
  const isLeaveType = currentApproveType.value === 2; // è¯·å‡ç®¡ç†
  const isReimburseType = currentApproveType.value === 4; // æŠ¥é”€ç®¡ç†
  const isQuotationType = currentApproveType.value === 6; // æŠ¥ä»·å®¡æ‰¹
  // åŸºç¡€åˆ—配置
  const baseColumns = [
  {
    label: "审批状态",
    prop: "approveStatus",
@@ -103,7 +133,7 @@
      } else if (params == 2) {
        return "success";
      } else if (params == 4) {
        return "";
          return "info";
      } else {
        return 'danger';
      }
@@ -120,7 +150,7 @@
        width: 220
  },
  {
    label: "审批事由",
      label: isQuotationType ? "报价单号" : "审批事由",
    prop: "approveReason",
        width: 200
  },
@@ -128,23 +158,41 @@
    label: "申请人",
    prop: "approveUserName",
    width: 120
  },
    }
  ];
  // é‡‘额列(仅报销管理显示)
  if (isReimburseType) {
    baseColumns.push({
      label: "金额(元)",
      prop: "price",
      width: 120
    });
  }
  // æ—¥æœŸåˆ—(根据类型动态配置)
  baseColumns.push(
  {
    label: "申请日期",
    prop: "approveTime",
      label: isLeaveType ? "开始日期" : "申请日期",
      prop: isLeaveType ? "startDate" : "approveTime",
        width: 200
  },
  {
    label: "结束日期",
    prop: "approveOverTime",
      prop: isLeaveType ? "endDate" : "approveOverTime",
    width: 120
  },
  {
    }
  );
  // å½“前审批人列
  baseColumns.push({
    label: "当前审批人",
    prop: "approveUserCurrentName",
    width: 120
  },
  {
  });
  // æ“ä½œåˆ—
  baseColumns.push({
    dataType: "action",
    label: "操作",
    align: "center",
@@ -157,7 +205,7 @@
        clickFun: (row) => {
          openForm("edit", row);
        },
                disabled: (row) => row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
        disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
      },
      {
        name: "审核",
@@ -182,8 +230,10 @@
        },
      },
    ],
  },
]);
  });
  return baseColumns;
});
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
@@ -214,7 +264,7 @@
};
const getList = () => {
  tableLoading.value = true;
  approveProcessListPage({...page, ...searchForm.value,approveType:props.approveType}).then(res => {
  approveProcessListPage({...page, ...searchForm.value, approveType: currentApproveType.value}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
@@ -224,7 +274,7 @@
};
// å¯¼å‡º
const handleOut = () => {
  const type = Number(props.approveType || 0)
  const type = currentApproveType.value
  const urlMap = {
    0: "/approveProcess/exportZero",
    1: "/approveProcess/exportOne",
@@ -232,6 +282,8 @@
    3: "/approveProcess/exportThree",
    4: "/approveProcess/exportFour",
    5: "/approveProcess/exportFive",
    6: "/approveProcess/exportSix",
    7: "/approveProcess/exportSeven",
  }
  const url = urlMap[type] || urlMap[0]
  const nameMap = {
@@ -241,6 +293,8 @@
    3: "出差管理审批表",
    4: "报销管理审批表",
    5: "采购申请审批表",
    6: "报价审批表",
    7: "出库审批表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
@@ -288,8 +342,27 @@
      });
};
onMounted(() => {
  // æ ¹æ®URL参数设置标签页和查询条件
  const approveType = route.query.approveType;
  const approveId = route.query.approveId;
  if (approveType) {
    // è®¾ç½®æ ‡ç­¾é¡µï¼ˆapproveType å¯¹åº” activeTab çš„ name)
    activeTab.value = String(approveType);
  }
  if (approveId) {
    // è®¾ç½®æµç¨‹ç¼–号查询条件
    searchForm.value.approveId = String(approveId);
  }
  // æŸ¥è¯¢åˆ—表
  getList();
});
</script>
<style scoped></style>
<style scoped>
.approval-tabs {
  margin-bottom: 10px;
}
</style>
src/views/collaborativeApproval/enterpriseBook/index.vue
@@ -295,7 +295,6 @@
  getEmployeeDetail
} from '@/api/collaborativeApproval/enterpriseBook.js'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {
  changeUserStatus,
  listUser,
@@ -306,6 +305,7 @@
  addUser,
  deptTreeSelect,
} from "@/api/system/user";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ ‡ç­¾é¡µçŠ¶æ€
const activeTab = ref('personal')
@@ -395,7 +395,7 @@
}
  //获取员工列表
const getEmployeeList = async () => {
  staffJoinListPage(publicSearch.value).then(res => {
  staffOnJobListPage(Object.assign({current: -1, size: -1},publicSearch.value)).then(res => {
    console.log(res.data.records)
      EmployeeList.value = res.data.records
    }).catch(err => {})
@@ -403,7 +403,7 @@
// èŽ·å–å•ä½é€šè®¯å½•åˆ—è¡¨
const getCompanyContactsList = async () => {
  loading.value = true
    staffJoinListPage(companySearch.value).then(res => {
  staffOnJobListPage(Object.assign({current: -1, size: -1},companySearch.value)).then(res => {
    // console.log(res.data.records)
      companyContacts.value = res.data.records
    }).catch(err => {})
src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -13,11 +13,12 @@
        />
        <span class="search_title ml10">知识类型:</span>
        <el-select v-model="searchForm.type" clearable @change="handleQuery" style="width: 240px">
          <el-option label="合同特批" :value="'contract'" />
          <el-option label="审批案例" :value="'approval'" />
          <el-option label="解决方案" :value="'solution'" />
          <el-option label="经验总结" :value="'experience'" />
          <el-option label="操作指南" :value="'guide'" />
          <el-option
              v-for="item in knowledgeTypeOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
          />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          æœç´¢
@@ -61,11 +62,12 @@
          <el-col :span="12">
            <el-form-item label="知识类型" prop="type">
              <el-select v-model="form.type" placeholder="请选择知识类型" style="width: 100%">
                <el-option label="合同特批" value="contract" />
                <el-option label="审批案例" value="approval" />
                <el-option label="解决方案" value="solution" />
                <el-option label="经验总结" value="experience" />
                <el-option label="操作指南" value="guide" />
                <el-option
                    v-for="item in knowledgeTypeOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
@@ -231,7 +233,7 @@
<script setup>
import { Search } from "@element-plus/icons-vue";
import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
@@ -313,24 +315,10 @@
    prop: "type",
    dataType: "tag",
    formatData: (params) => {
      const typeMap = {
        contract: "合同特批",
        approval: "审批案例",
        solution: "解决方案",
        experience: "经验总结",
        guide: "操作指南"
      };
      return typeMap[params] || params;
      return getKnowledgeTypeLabel(params);
    },
    formatType: (params) => {
      const typeMap = {
        contract: "success",
        approval: "warning",
        solution: "primary",
        experience: "info",
        guide: "danger"
      };
      return typeMap[params] || "info";
      return getKnowledgeTypeTagType(params);
    }
  },
  {
@@ -401,111 +389,6 @@
  }
]);
// æ¨¡æ‹Ÿæ•°æ®
// let mockData = [
//   {
//     id: "1",
//     title: "特殊合同审批流程优化方案",
//     type: "contract",
//     scenario: "大额合同快速审批",
//     efficiency: "high",
//     problem: "大额合同审批流程复杂,审批时间长,影响业务进展",
//     solution: "建立绿色通道,对符合条件的合同采用简化审批流程,由部门负责人直接审批,平均审批时间从3天缩短至1天",
//     keyPoints: "绿色通道条件,简化流程,审批权限,时间控制",
//     creator: "张经理",
//     usageCount: 15,
//     createTime: "2024-01-15 10:30:00"
//   },
//   {
//     id: "2",
//     title: "跨部门协作审批经验总结",
//     type: "experience",
//     scenario: "多部门协作项目",
//     efficiency: "medium",
//     problem: "跨部门项目审批时,各部门意见不统一,审批进度缓慢",
//     solution: "建立项目协调机制,指定项目负责人,定期召开协调会议,统一各方意见后再进行审批",
//     keyPoints: "项目协调,定期会议,统一意见,负责人制度",
//     creator: "李主管",
//     usageCount: 8,
//     createTime: "2024-01-14 15:20:00"
//   },
//   {
//     id: "3",
//     title: "紧急采购审批操作指南",
//     type: "guide",
//     scenario: "紧急采购需求",
//     efficiency: "high",
//     problem: "紧急采购时审批流程复杂,无法满足紧急需求",
//     solution: "制定紧急采购审批标准,明确紧急程度分级,不同级别采用不同审批流程,确保紧急需求得到及时处理",
//     keyPoints: "紧急分级,标准制定,流程简化,及时处理",
//     creator: "王专员",
//     usageCount: 12,
//     createTime: "2024-01-13 09:15:00"
//   }
// ];
// çŸ¥è¯†æ ‡é¢˜æ¨¡æ¿
const titleTemplates = [
  "{type}审批流程优化方案",
  "{scenario}处理经验总结",
  "{type}特殊情况处理指南",
  "{scenario}快速审批方案",
  "{type}标准化操作流程",
  "{scenario}问题解决方案",
  "{type}最佳实践总结",
  "{scenario}效率提升方案"
];
// çŸ¥è¯†ç±»åž‹é…ç½®
const knowledgeTypes = [
  { type: "contract", label: "合同特批", efficiency: "high" },
  { type: "approval", label: "审批案例", efficiency: "medium" },
  { type: "solution", label: "解决方案", efficiency: "high" },
  { type: "experience", label: "经验总结", efficiency: "medium" },
  { type: "guide", label: "操作指南", efficiency: "low" }
];
// åœºæ™¯åˆ—表
const scenarios = ["大额合同审批", "跨部门协作", "紧急采购", "特殊申请", "流程优化", "问题处理", "标准化建设", "效率提升"];
// è‡ªåŠ¨ç”Ÿæˆæ–°æ•°æ®
const generateNewData = () => {
  const newId = (mockData.length + 1).toString();
  const now = new Date();
  const randomType = knowledgeTypes[Math.floor(Math.random() * knowledgeTypes.length)];
  const randomScenario = scenarios[Math.floor(Math.random() * scenarios.length)];
  // ç”Ÿæˆéšæœºæ ‡é¢˜
  let title = titleTemplates[Math.floor(Math.random() * titleTemplates.length)];
  title = title
    .replace('{type}', randomType.label)
    .replace('{scenario}', randomScenario);
  const newKnowledge = {
    id: newId,
    title: title,
    type: randomType.type,
    scenario: randomScenario,
    efficiency: randomType.efficiency,
    problem: `在${randomScenario}过程中遇到的问题描述...`,
    solution: `针对${randomScenario}的解决方案和操作步骤...`,
    keyPoints: "关键要点1,关键要点2,关键要点3,关键要点4",
    creator: ["张经理", "李主管", "王专员", "刘总监"][Math.floor(Math.random() * 4)],
    usageCount: Math.floor(Math.random() * 20) + 1,
    createTime: now.toLocaleString()
  };
  // æ·»åŠ åˆ°æ•°æ®å¼€å¤´
  mockData.unshift(newKnowledge);
  // ä¿æŒæ•°æ®é‡åœ¨åˆç†èŒƒå›´å†…(最多保留30条)
  if (mockData.length > 30) {
    mockData = mockData.slice(0, 30);
  }
  console.log(`[${new Date().toLocaleString()}] è‡ªåŠ¨ç”Ÿæˆæ–°çŸ¥è¯†: ${title}`);
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  getList();
@@ -515,7 +398,6 @@
// å¼€å§‹è‡ªåŠ¨åˆ·æ–°
const startAutoRefresh = () => {
  setInterval(() => {
    generateNewData();
    getList();
  }, 600000); // 10分钟刷新一次 (10 * 60 * 1000 = 600000ms)
};
@@ -605,14 +487,7 @@
// èŽ·å–ç±»åž‹æ ‡ç­¾æ–‡æœ¬
const getTypeLabel = (type) => {
  const typeMap = {
    contract: "合同特批",
    approval: "审批案例",
    solution: "解决方案",
    experience: "经验总结",
    guide: "操作指南"
  };
  return typeMap[type] || type;
  return getKnowledgeTypeLabel(type);
};
// èŽ·å–æ•ˆçŽ‡æ ‡ç­¾ç±»åž‹
@@ -675,18 +550,6 @@
  });
};
// æ”¶è—çŸ¥è¯†
const markAsFavorite = () => {
  // å¢žåŠ ä½¿ç”¨æ¬¡æ•°
  const index = mockData.findIndex(item => item.id === currentKnowledge.value.id);
  if (index !== -1) {
    mockData[index].usageCount += 1;
    currentKnowledge.value.usageCount += 1;
  }
  ElMessage.success("已收藏,使用次数+1");
};
// æäº¤çŸ¥è¯†è¡¨å•
const submitForm = async () => {
  try {
@@ -745,6 +608,18 @@
// å¯¼å‡º
const { proxy } = getCurrentInstance()
const { knowledge_type } = proxy.useDict("knowledge_type")
// å­—典工具
const knowledgeTypeOptions = computed(() => knowledge_type?.value || [])
const getKnowledgeTypeLabel = (val) => {
  const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val))
  return item ? item.label : val
}
const getKnowledgeTypeTagType = (val) => {
  const item = knowledgeTypeOptions.value.find(i => String(i.value) === String(val))
  return item?.elTagType || "info"
}
const handleExport = () => {
  proxy.download('/knowledgeBase/export', { ...searchForm.value }, '知识库.xlsx')
}
src/views/collaborativeApproval/meetingManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
<template>
  <div class="app-container">
    <div class="tabs-wrapper">
      <el-tabs
        v-model="activeTab"
        class="meeting-tabs"
        @tab-change="handleTabChange"
      >
        <el-tab-pane label="会议设置" name="setting" />
        <el-tab-pane label="会议列表" name="index" />
        <el-tab-pane label="会议申请" name="application" />
        <el-tab-pane label="会议审批" name="examine" />
        <el-tab-pane label="会议发布" name="publish" />
        <el-tab-pane label="会议总结" name="summary" />
      </el-tabs>
    </div>
    <div class="tab-content">
      <keep-alive>
        <component :is="currentComponent" />
      </keep-alive>
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'
import MeetSetting from '../notificationManagement/meetSetting/index.vue'
import MeetIndex from '../notificationManagement/meetIndex/index.vue'
import MeetApplication from '../notificationManagement/meetApplication/index.vue'
import MeetExamine from '../notificationManagement/meetExamine/index.vue'
import MeetPublish from '../notificationManagement/meetPublish/index.vue'
import MeetSummary from '../notificationManagement/summary/index.vue'
const activeTab = ref('setting')
const tabComponentMap = {
  setting: MeetSetting,
  index: MeetIndex,
  application: MeetApplication,
  examine: MeetExamine,
  publish: MeetPublish,
  summary: MeetSummary
}
const currentComponent = computed(() => tabComponentMap[activeTab.value] || MeetSetting)
function handleTabChange(name) {
  activeTab.value = name
}
</script>
<style scoped lang="scss">
.tabs-wrapper {
  margin-bottom: 10px;
}
.tab-content {
  min-height: 400px;
}
</style>
src/views/collaborativeApproval/noticeManagement/index.vue
@@ -4,35 +4,46 @@
    <div class="search_form">
      <div>
        <el-button type="primary" @click="openForm('add')">新增公告</el-button>
        <el-button type="danger" plain @click="handleDelete" :disabled="!selectedIds.length">删除</el-button>
        <el-button type="info" @click="openNoticeTypeDialog">公告类型配置</el-button>
      </div>
    </div>
    <!-- é€šçŸ¥å…¬å‘Šæ¿ -->
    <div class="notice-board">
      <!-- æ”¾å‡é€šçŸ¥åŒºåŸŸ -->
      <div class="notice-section" v-if="holidayNoticeCount > 0">
        <div class="section-header">
          <h3>📅 æ”¾å‡é€šçŸ¥</h3>
          <span class="section-count">{{ holidayNoticeCount }}条</span>
        </div>
      <el-tabs v-model="activeNoticeTypeTab" @tab-change="handleNoticeTypeTabChange">
        <el-tab-pane
            v-for="noticeType in noticeTypeList"
            :key="noticeType.id"
            :label="noticeType.noticeType"
            :name="String(noticeType.id)"
        >
          <template #label>
            <span>{{ noticeType.noticeType }}
              <span class="tab-count" v-if="getNoticeCountByType(noticeType.id) > 0">
                ({{ getNoticeCountByType(noticeType.id) }})
              </span>
            </span>
          </template>
          <div class="notice-section">
        <div class="notice-cards">
          <div
              v-for="notice in holidayNotices"
                  v-for="notice in getNoticesByType(noticeType.id)"
              :key="notice.id"
              class="notice-card holiday-card"
                  class="notice-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
            <div class="card-header">
              <div class="card-title">
                <el-icon class="holiday-icon">
                    <el-icon class="notice-icon">
                  <Calendar/>
                </el-icon>
                {{ notice.title }}
              </div>
              <div class="card-actions">
                <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)">编辑</el-button>
                <el-button link type="danger" @click="handleDelete(notice.id)">删除</el-button>
                    <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)" v-if="notice.status !== 1">编辑</el-button>
                    <el-button link type="success" @click="handlePublish(notice)" v-if="notice.status === 0">发布</el-button>
                    <el-button link type="danger" @click="handleDelete(notice.id)" v-if="notice.status !== 1">删除</el-button>
              </div>
            </div>
            <div class="card-content">
@@ -60,80 +71,22 @@
            </div>
          </div>
        </div>
      </div>
      <pagination
          v-if="holidayNoticePage.total > 0"
          :total="holidayNoticePage.total"
          :page="holidayNoticePage.current"
          :limit="holidayNoticePage.size"
          @pagination="handleHolidayNoticeCurrentChange"
      />
      <!-- è®¾å¤‡ç»´ä¿®é€šçŸ¥åŒºåŸŸ -->
      <div class="notice-section" v-if="maintenanceNoticeCount > 0">
        <div class="section-header">
          <h3>🔧 è®¾å¤‡ç»´ä¿®é€šçŸ¥</h3>
          <span class="section-count">{{ maintenanceNoticeCount }}条</span>
        </div>
        <div class="notice-cards">
          <div
              v-for="notice in maintenanceNotices"
              :key="notice.id"
              class="notice-card maintenance-card"
              :class="{ 'urgent': notice.priority === '3' }"
          >
            <div class="card-header">
              <div class="card-title">
                <el-icon class="maintenance-icon">
                  <Tools/>
                </el-icon>
                {{ notice.title }}
              </div>
              <div class="card-actions">
                <el-button link type="primary" @click="handleEdit(notice)" :disabled="isNoticeExpired(notice)">编辑</el-button>
                <el-button link type="danger" @click="handleDelete(notice.id)">删除</el-button>
              </div>
            </div>
            <div class="card-content">
              <p>{{ notice.content }}</p>
            </div>
            <div class="card-footer">
              <div class="card-meta">
                <span class="priority" :class="'priority-' + notice.priority">
                  {{ getPriorityText(notice.priority) }}
                </span>
                <span class="status" :class="'status-' + getNoticeStatus(notice)">
                  {{ getStatusText(getNoticeStatus(notice)) }}
                </span>
              </div>
              <div class="card-info">
                <span class="creator">{{ notice.createUserName }}</span>
              <span class="expiration" v-if="notice.expirationDate">截止日期:{{ notice.expirationDate }}</span>
              </div>
            </div>
            <div class="card-remark" v-if="notice.remark">
              <el-icon>
                <InfoFilled/>
              </el-icon>
              <span>{{ notice.remark }}</span>
            </div>
          </div>
        </div>
      </div>
      <pagination
          v-if="maintenanceNoticePage.total > 0"
          :total="maintenanceNoticePage.total"
          :page="maintenanceNoticePage.current"
          :limit="maintenanceNoticePage.size"
          @pagination="handleMaintenanceNoticeCurrentChange"
                v-if="getNoticePageByType(noticeType.id).total > 0"
                :total="getNoticePageByType(noticeType.id).total"
                :page="getNoticePageByType(noticeType.id).current"
                :limit="getNoticePageByType(noticeType.id).size"
                @pagination="(val) => handleNoticeCurrentChange(noticeType.id, val)"
      />
      <!-- ç©ºçŠ¶æ€ -->
      <div class="empty-state" v-if="holidayNotices.length === 0 && maintenanceNotices.length === 0">
            <div class="empty-state" v-if="getNoticesByType(noticeType.id).length === 0">
        <el-empty description="暂无通知公告"/>
      </div>
          </div>
        </el-tab-pane>
      </el-tabs>
    </div>
    <!-- æ–°å¢ž/编辑对话框 -->
@@ -154,8 +107,12 @@
          <el-col :span="12">
            <el-form-item label="公告类型" prop="type">
              <el-select v-model="form.type" placeholder="请选择公告类型" style="width: 100%">
                <el-option label="放假通知" :value="1"/>
                <el-option label="设备维修通知" :value="2"/>
                <el-option
                    v-for="item in noticeTypeList"
                    :key="item.id"
                    :label="item.noticeType"
                    :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
@@ -223,24 +180,96 @@
        </div>
      </template>
    </el-dialog>
    <!-- å…¬å‘Šç±»åž‹é…ç½®å¼¹æ¡† -->
    <el-dialog
        v-model="noticeTypeDialogVisible"
        title="公告类型配置"
        width="800px"
        @close="handleNoticeTypeDialogClose"
    >
      <div class="notice-type-container">
        <div class="notice-type-header">
          <el-button type="primary" @click="handleAddNoticeType">新增类型</el-button>
        </div>
        <el-table :data="noticeTypeList" border style="width: 100%">
          <el-table-column prop="id" label="ID" width="80" align="center"/>
          <el-table-column prop="noticeType" label="公告类型" align="center">
            <template #default="scope">
              <el-input
                  v-if="scope.row.editing"
                  v-model="scope.row.noticeType"
                  placeholder="请输入公告类型"
              />
              <span v-else>{{ scope.row.noticeType }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="200" align="center">
            <template #default="scope">
              <el-button
                  v-if="scope.row.editing"
                  link
                  type="primary"
                  size="small"
                  @click="handleSaveNoticeType(scope.row)"
              >
                ä¿å­˜
              </el-button>
              <el-button
                  v-if="scope.row.editing"
                  link
                  type="info"
                  size="small"
                  @click="handleCancelEdit(scope.row)"
              >
                å–消
              </el-button>
              <el-button
                  v-if="!scope.row.editing"
                  link
                  type="primary"
                  size="small"
                  @click="handleEditNoticeType(scope.row)"
              >
                ç¼–辑
              </el-button>
              <el-button
                  v-if="!scope.row.editing"
                  link
                  type="danger"
                  size="small"
                  @click="handleDeleteNoticeType(scope.row)"
              >
                åˆ é™¤
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import {Search, Calendar, Tools, InfoFilled} from "@element-plus/icons-vue";
import {Calendar, InfoFilled} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, computed} from "vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {useRoute} from "vue-router";
import useUserStore from "@/store/modules/user";
import {
  addNotice,
  delNotice,
  getCount,
  listNotice,
  updateNotice
  updateNotice,
  listNoticeType,
  addNoticeType,
  delNoticeType
} from "../../../api/collaborativeApproval/noticeManagement.js";
import pagination from "../../../components/PIMTable/Pagination.vue";
const userStore = useUserStore();
const route = useRoute();
// å“åº”式数据
const data = reactive({
@@ -280,8 +309,16 @@
// é¡µé¢çŠ¶æ€
const dialogVisible = ref(false);
const dialogTitle = ref("");
const selectedIds = ref([]);
const formRef = ref();
// å…¬å‘Šç±»åž‹é…ç½®ç›¸å…³
const noticeTypeDialogVisible = ref(false);
const noticeTypeList = ref([]);
const activeNoticeTypeTab = ref('');
// é€šçŸ¥æ•°æ® - ä½¿ç”¨ Map å­˜å‚¨ï¼Œkey ä¸ºç±»åž‹ id
const noticesMap = ref({});
const noticePagesMap = ref({});
// è®¡ç®—属性
@@ -398,6 +435,28 @@
  });
};
const handlePublish = (notice) => {
  ElMessageBox.confirm(
      "确认发布这条公告吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "info"
      }
  ).then(() => {
    updateNotice({
      ...notice,
      status: 1
    }).then(res => {
      if (res.code === 200) {
        ElMessage.success("发布成功");
        resetTable()
      }
    })
  });
};
const submitForm = () => {
  formRef.value.validate((valid) => {
    if (valid) {
@@ -419,78 +478,257 @@
  });
};
const holidayNoticeCount = ref()
const maintenanceNoticeCount = ref()
const fetchCount = () => {
  getCount().then(res => {
    holidayNoticeCount.value = res.data.filter(item => {
      return item.type === 1
    })[0].count;
    maintenanceNoticeCount.value = res.data.filter(item => {
      return item.type === 2
    })[0].count;
  });
// åˆå§‹åŒ–某个类型的分页数据
const initNoticePage = (typeId) => {
  if (!noticePagesMap.value[typeId]) {
    noticePagesMap.value[typeId] = {
      total: 0,
      current: 1,
      size: 10
    };
}
  if (!noticesMap.value[typeId]) {
    noticesMap.value[typeId] = [];
  }
};
const holidayNotices = ref([])
const maintenanceNotices = ref([])
const holidayNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
// èŽ·å–æŸä¸ªç±»åž‹çš„é€šçŸ¥åˆ—è¡¨
const getNoticesByType = (typeId) => {
  return noticesMap.value[typeId] || [];
};
const maintenanceNoticePage = ref({
  total: 0,
  current: 1,
  size: 9
})
// èŽ·å–æŸä¸ªç±»åž‹çš„åˆ†é¡µæ•°æ®
const getNoticePageByType = (typeId) => {
  return noticePagesMap.value[typeId] || { total: 0, current: 1, size: 10 };
};
const fetchHolidayNotices = () => {
  listNotice({...holidayNoticePage.value, type: 1}).then(res => {
    holidayNotices.value = res.data.records
    holidayNoticePage.value.total = res.data.total
// èŽ·å–æŸä¸ªç±»åž‹çš„æ•°é‡
const getNoticeCountByType = (typeId) => {
  return getNoticePageByType(typeId).total || 0;
};
// èŽ·å–æŸä¸ªç±»åž‹çš„é€šçŸ¥æ•°æ®
const fetchNoticesByType = (typeId) => {
  initNoticePage(typeId);
  const pageData = noticePagesMap.value[typeId];
  listNotice({...pageData, type: typeId}).then(res => {
    if (res.code === 200) {
      noticesMap.value[typeId] = res.data.records || [];
      noticePagesMap.value[typeId].total = res.data.total || 0;
    }
  });
};
const fetchMaintenanceNotices = () => {
  listNotice({...holidayNoticePage.value, type: 2}).then(res => {
    maintenanceNotices.value = res.data.records
    maintenanceNoticePage.value.total = res.data.total
  });
// å¤„理分页变化
const handleNoticeCurrentChange = (typeId, val) => {
  initNoticePage(typeId);
  noticePagesMap.value[typeId].size = val.limit;
  noticePagesMap.value[typeId].current = val.page;
  fetchNoticesByType(typeId);
};
const handleHolidayNoticeCurrentChange = (val) => {
  holidayNoticePage.value.size = val.limit
  holidayNoticePage.value.current = val.page
  fetchHolidayNotices()
};
const handleMaintenanceNoticeCurrentChange = (val) => {
  maintenanceNoticePage.value.size = val.limit
  maintenanceNoticePage.value.current = val.page
  fetchMaintenanceNotices()
// å¤„理 tab åˆ‡æ¢
const handleNoticeTypeTabChange = (tabName) => {
  activeNoticeTypeTab.value = tabName;
  const typeId = Number(tabName);
  fetchNoticesByType(typeId);
};
const resetTable = () => {
  holidayNoticePage.value.current = 1
  holidayNoticePage.value.size = 9
  maintenanceNoticePage.value.current = 1
  maintenanceNoticePage.value.size = 9
  fetchHolidayNotices()
  fetchMaintenanceNotices()
  fetchCount()
  // é‡ç½®æ‰€æœ‰ç±»åž‹çš„分页并重新获取数据
  noticeTypeList.value.forEach(type => {
    initNoticePage(type.id);
    noticePagesMap.value[type.id].current = 1;
    noticePagesMap.value[type.id].size = 10;
    fetchNoticesByType(type.id);
  });
};
const resetForm = () => {
  formRef.value?.resetFields();
};
// å…¬å‘Šç±»åž‹é…ç½®ç›¸å…³æ–¹æ³•
const openNoticeTypeDialog = () => {
  noticeTypeDialogVisible.value = true;
  fetchNoticeTypeList();
};
const fetchNoticeTypeList = () => {
  return listNoticeType().then(res => {
    if (res.code === 200) {
      noticeTypeList.value = res.data.map(item => ({
        ...item,
        editing: false
      }));
      // æ£€æŸ¥è·¯ç”±å‚数中的 type
      const routeType = route.query.type;
      let targetTypeId = null;
      if (routeType) {
        // å¦‚果路由参数中有 type,查找对应的类型
        const typeId = Number(routeType);
        const foundType = noticeTypeList.value.find(item => item.id === typeId);
        if (foundType) {
          targetTypeId = typeId;
        }
      }
      // å¦‚果有类型数据
      if (noticeTypeList.value.length > 0) {
        // å¦‚果路由参数指定了类型且存在,使用路由参数的类型
        // å¦åˆ™å¦‚果没有选中 tab,默认选中第一个
        if (targetTypeId !== null) {
          activeNoticeTypeTab.value = String(targetTypeId);
          fetchNoticesByType(targetTypeId);
        } else if (!activeNoticeTypeTab.value) {
          activeNoticeTypeTab.value = String(noticeTypeList.value[0].id);
          fetchNoticesByType(noticeTypeList.value[0].id);
        }
      }
    }
  });
};
const handleAddNoticeType = () => {
  const newItem = {
    id: undefined,
    noticeType: '',
    editing: true
  };
  noticeTypeList.value.push(newItem);
};
const handleEditNoticeType = (row) => {
  // ä¿å­˜åŽŸå§‹å€¼
  row.originalNoticeType = row.noticeType;
  row.editing = true;
};
const handleSaveNoticeType = (row) => {
  if (!row.noticeType || row.noticeType.trim() === '') {
    ElMessage.warning('公告类型不能为空');
    return;
  }
  const data = {
    noticeType: row.noticeType.trim()
  };
  if (row.id) {
    // ç¼–辑模式 - å…ˆåˆ é™¤å†æ·»åŠ ï¼ˆå› ä¸ºåªæœ‰ add å’Œ del æŽ¥å£ï¼‰
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        addNoticeType(data).then(addRes => {
          if (addRes.code === 200) {
            ElMessage.success('编辑成功');
            row.editing = false;
            delete row.originalNoticeType;
            fetchNoticeTypeList().then(() => {
              // å¦‚果当前选中的类型被编辑,需要重新获取数据
              if (activeNoticeTypeTab.value === String(row.id)) {
                fetchNoticesByType(addRes.data?.id || row.id);
              }
            });
          }
        });
      }
    });
  } else {
    // æ–°å¢žæ¨¡å¼
    addNoticeType(data).then(res => {
      if (res.code === 200) {
        ElMessage.success('新增成功');
        row.editing = false;
        fetchNoticeTypeList();
      }
    });
  }
};
const handleDeleteNoticeType = (row) => {
  // å¦‚果没有id,说明是新增但未保存的行,直接从前端删除
  if (!row.id) {
    const index = noticeTypeList.value.indexOf(row);
    if (index > -1) {
      noticeTypeList.value.splice(index, 1);
    }
    return;
  }
  // å¦‚果有id,调用后端接口删除
  ElMessageBox.confirm(
      "确认删除这个公告类型吗?",
      "提示",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }
  ).then(() => {
    delNoticeType(row.id).then(res => {
      if (res.code === 200) {
        ElMessage.success("删除成功");
        // å¦‚果删除的是当前选中的类型,切换到第一个类型
        if (activeNoticeTypeTab.value === String(row.id)) {
          fetchNoticeTypeList().then(() => {
            if (noticeTypeList.value.length > 0) {
              activeNoticeTypeTab.value = String(noticeTypeList.value[0].id);
              fetchNoticesByType(noticeTypeList.value[0].id);
            } else {
              activeNoticeTypeTab.value = '';
            }
          });
        } else {
          fetchNoticeTypeList();
        }
      }
    });
  });
};
const handleCancelEdit = (row) => {
  if (!row.id) {
    // å¦‚果是新增但未保存的行,移除它
    const index = noticeTypeList.value.indexOf(row);
    if (index > -1) {
      noticeTypeList.value.splice(index, 1);
    }
  } else {
    // å¦‚果是编辑中的行,取消编辑状态并恢复原值
    row.editing = false;
    if (row.originalNoticeType !== undefined) {
      row.noticeType = row.originalNoticeType;
      delete row.originalNoticeType;
    }
  }
};
const handleNoticeTypeDialogClose = () => {
  // å…³é—­å¼¹æ¡†æ—¶ï¼Œå–消所有编辑状态
  noticeTypeList.value.forEach(item => {
    if (item.editing && !item.id) {
      // å¦‚果是新增但未保存的行,移除它
      const index = noticeTypeList.value.indexOf(item);
      if (index > -1) {
        noticeTypeList.value.splice(index, 1);
      }
    } else if (item.editing) {
      // å¦‚果是编辑中的行,取消编辑状态并恢复原值
      item.editing = false;
      if (item.originalNoticeType !== undefined) {
        item.noticeType = item.originalNoticeType;
        delete item.originalNoticeType;
      }
    }
  });
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  fetchCount()
  fetchHolidayNotices()
  fetchMaintenanceNotices()
  // å…ˆèŽ·å–å…¬å‘Šç±»åž‹åˆ—è¡¨ï¼Œç„¶åŽæ ¹æ®ç±»åž‹èŽ·å–é€šçŸ¥æ•°æ®
  fetchNoticeTypeList();
});
</script>
@@ -569,12 +807,16 @@
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.holiday-card {
  border-left-color: #67c23a;
.notice-icon {
  color: #409eff;
  margin-right: 8px;
  font-size: 18px;
}
.maintenance-card {
  border-left-color: #e6a23c;
.tab-count {
  color: #909399;
  font-size: 12px;
  margin-left: 4px;
}
.urgent {
@@ -598,17 +840,6 @@
  flex: 1;
}
.holiday-icon {
  color: #67c23a;
  margin-right: 8px;
  font-size: 18px;
}
.maintenance-icon {
  color: #e6a23c;
  margin-right: 8px;
  font-size: 18px;
}
.card-actions {
  display: flex;
@@ -624,6 +855,9 @@
  color: #606266;
  line-height: 1.6;
  font-size: 14px;
  word-break: break-all;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}
.card-footer {
@@ -713,6 +947,16 @@
  text-align: right;
}
.notice-type-container {
  padding: 10px 0;
}
.notice-type-header {
  margin-bottom: 15px;
  display: flex;
  justify-content: flex-end;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .notice-cards {
src/views/collaborativeApproval/notificationManagement/index.vue
@@ -322,7 +322,7 @@
import { ElMessage, ElMessageBox } from "element-plus";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { staffOnJobListPage } from "@/api/personnelManagement/employeeRecord.js";
import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
import { listNotification, addNotification, updateNotification, delNotification,addOnlineMeeting,addFileSharing } from "@/api/collaborativeApproval/notificationManagement.js";
import { id } from "element-plus/es/locales.mjs";
@@ -549,7 +549,6 @@
        clickFun: (row) => {
          publishNotification(row);
        },
        // disabled: (row) => row.status === "published"
      },
      {
        name: "撤回",
@@ -557,7 +556,6 @@
        clickFun: (row) => {
          revokeNotification(row);
        },
        // disabled: (row) => row.status !== "published"
      }
    ]
  }
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue
@@ -1,9 +1,5 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议申请</h2>
    </div>
  <div>
    <!-- ç”³è¯·ç±»åž‹é€‰æ‹© -->
    <el-card class="type-card">
@@ -131,7 +127,7 @@
            <el-option
                v-for="person in employees"
                :key="person.id"
                :label="`${person.staffName} (${person.postJob})`"
                :label="`${person.staffName} (${person.postName})`"
                :value="person.id"
            />
          </el-select>
@@ -160,7 +156,7 @@
import {ElMessage} from 'element-plus'
import {Plus, Document, Promotion, Bell} from '@element-plus/icons-vue'
import {getRoomEnum, saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å½“前申请类型
const currentType = ref('department') // approval: å®¡æ‰¹æµç¨‹, department: éƒ¨é—¨çº§, notification: é€šçŸ¥å‘布
@@ -306,8 +302,12 @@
  getRoomEnum().then(res => {
    meetingRooms.value = res.data
  })
  getStaffOnJob().then(res => {
    employees.value = res.data.sort((a, b) => a.postJob.localeCompare(b.postJob))
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    employees.value = res.data.records.sort((a, b) => a.postName.localeCompare(b.postName))
  })
})
</script>
src/views/collaborativeApproval/notificationManagement/meetDraft/index.vue
@@ -1,5 +1,5 @@
<template>
  <div class="app-container">
  <div>
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议草稿</h2>
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue
@@ -1,12 +1,6 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议审批</h2>
    </div>
  <div>
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card">
      <el-form :model="searchForm" inline>
        <el-form-item label="会议主题">
          <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable/>
@@ -27,11 +21,10 @@
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ä¼šè®®å®¡æ‰¹åˆ—表 -->
    <el-card>
      <el-table v-loading="loading" :data="approvalList" border>
      <el-table v-loading="loading" :data="approvalList" border :height="tableHeight">
        <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip/>
        <el-table-column prop="applicant" label="申请人" align="center" width="120"/>
        <el-table-column prop="host" label="主理人" align="center" width="120"/>
@@ -195,14 +188,17 @@
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getExamineList,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
// æ€»æ¡æ•°
const total = ref(0)
// è¡¨æ ¼é«˜åº¦ï¼ˆæ ¹æ®çª—口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
const roomEnum = ref([])
const staffList = ref([])
// å®¡æ‰¹åˆ—表数据
@@ -244,7 +240,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -346,9 +342,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
@@ -1,12 +1,5 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议室使用查询</h2>
    </div>
    <!-- æŸ¥è¯¢æ¡ä»¶ -->
    <el-card class="search-card">
  <div>
      <el-form :model="queryForm" label-width="80px" inline>
        <el-form-item label="查询日期">
          <el-date-picker
@@ -23,7 +16,6 @@
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ä¼šè®®å®¤ä½¿ç”¨æƒ…况 -->
    <el-card class="table-container" :loading="loading">
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue
@@ -1,12 +1,6 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议发布</h2>
    </div>
  <div>
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card">
      <el-form :model="searchForm" inline>
        <el-form-item label="会议主题">
          <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable/>
@@ -25,11 +19,10 @@
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ä¼šè®®å‘布列表 -->
    <el-card>
      <el-table v-loading="loading" :data="approvalList" border>
      <el-table v-loading="loading" :data="approvalList" border :height="tableHeight">
        <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip/>
        <el-table-column prop="applicant" label="申请人" align="center" width="120"/>
        <el-table-column prop="host" label="主理人" align="center" width="120"/>
@@ -193,14 +186,17 @@
import {ElMessage, ElMessageBox} from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import {getRoomEnum, getMeetingPublish,saveMeetingApplication} from '@/api/collaborativeApproval/meeting.js'
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
// æ€»æ¡æ•°
const total = ref(0)
// è¡¨æ ¼é«˜åº¦ï¼ˆæ ¹æ®çª—口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
const roomEnum = ref([])
const staffList = ref([])
// å‘布列表数据
@@ -243,7 +239,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -344,9 +340,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2]= await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2]= await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/notificationManagement/meetSetting/index.vue
@@ -1,20 +1,7 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议室设置</h2>
      <div>
        <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
        <el-button type="primary" @click="handleAdd">
        <el-icon><Plus /></el-icon>
        æ–°å¢žä¼šè®®å®¤
        </el-button>
      </div>
    </div>
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card">
      <el-form :model="searchForm" label-width="100px" inline>
    <el-form :model="searchForm" label-width="100px" class="search-form">
        <el-form-item label="会议室名称">
          <el-input v-model="searchForm.name" placeholder="请输入会议室名称" clearable />
        </el-form-item>
@@ -25,12 +12,18 @@
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      <el-form-item class="search-actions">
        <el-button @click="handleExport">导出</el-button>
        <el-button type="primary" @click="handleAdd">
          <el-icon><Plus /></el-icon>
          æ–°å¢žä¼šè®®å®¤
        </el-button>
      </el-form-item>
      </el-form>
    </el-card>
    <!-- ä¼šè®®å®¤åˆ—表 -->
    <el-card>
      <el-table v-loading="loading" :data="meetingRoomList" border>
      <el-table v-loading="loading" :data="meetingRoomList" border :height="tableHeight">
        <el-table-column prop="name" label="会议室名称" align="center" />
        <el-table-column prop="location" label="位置" align="center" />
        <el-table-column prop="capacity" label="容纳人数" align="center" />
@@ -120,6 +113,9 @@
// æ€»æ¡æ•°
const total = ref(0)
// è¡¨æ ¼é«˜åº¦ï¼ˆæ ¹æ®çª—口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
// ä¼šè®®å®¤åˆ—表数据
const meetingRoomList = ref([])
@@ -291,6 +287,15 @@
  padding: 20px;
}
.search-form {
  display: flex;
  /* align-items: center; */
}
.search-actions {
  margin-left: auto;
}
.page-header {
  display: flex;
  justify-content: space-between;
src/views/collaborativeApproval/notificationManagement/summary/index.vue
@@ -1,12 +1,6 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>会议纪要</h2>
    </div>
  <div>
    <!-- æœç´¢åŒºåŸŸ -->
    <el-card class="search-card">
      <el-form :model="searchForm" inline>
        <el-form-item label="会议主题">
          <el-input v-model="searchForm.title" placeholder="请输入会议主题" clearable />
@@ -19,11 +13,10 @@
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ä¼šè®®åˆ—表 -->
    <el-card>
      <el-table v-loading="loading" :data="meetingList" border>
      <el-table v-loading="loading" :data="meetingList" border :height="tableHeight">
        <el-table-column prop="title" label="会议主题" align="center" min-width="200" show-overflow-tooltip />
        <el-table-column prop="applicant" label="申请人" align="center" width="120" />
        <el-table-column prop="host" label="主持人" align="center" width="120" />
@@ -167,14 +160,17 @@
import Pagination from '@/components/Pagination/index.vue'
import Editor from '@/components/Editor/index.vue'
import { getRoomEnum, getMeetingPublish ,getMeetingMinutesByMeetingId,saveMeetingMinutes} from '@/api/collaborativeApproval/meeting.js'
import { getStaffOnJob } from "@/api/personnelManagement/onboarding.js"
import dayjs from "dayjs"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// æ•°æ®åˆ—表加载状态
const loading = ref(false)
// æ€»æ¡æ•°
const total = ref(0)
// è¡¨æ ¼é«˜åº¦ï¼ˆæ ¹æ®çª—口高度自适应)
const tableHeight = ref(window.innerHeight - 380)
const roomEnum = ref([])
const staffList = ref([])
@@ -218,7 +214,7 @@
    it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => {
      return {
        id: staff.id,
        name: `${staff.staffName}(${staff.postJob})`
        name: `${staff.staffName}(${staff.postName})`
      }
    })
@@ -341,9 +337,9 @@
// é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
onMounted(async () => {
  const [resp1, resp2] = await Promise.all([getRoomEnum(), getStaffOnJob()])
  const [resp1, resp2] = await Promise.all([getRoomEnum(), staffOnJobListPage({current: -1, size: -1, staffState: 1})])
  roomEnum.value = resp1.data
  staffList.value = resp2.data
  staffList.value = resp2.data.records
  await getList()
})
src/views/collaborativeApproval/purchaseApproval/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1064 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <el-form :model="searchForm" :inline="true">
          <el-form-item label="供应商名称:">
            <el-input v-model="searchForm.supplierName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="采购合同号:">
            <el-input
                v-model="searchForm.purchaseContractNumber"
                style="width: 240px"
                placeholder="请输入"
                @change="handleQuery"
                clearable
                :prefix-icon="Search"
            />
          </el-form-item>
          <el-form-item label="销售合同号:">
            <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="项目名称:">
            <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
    <div class="table_list">
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys"
        :row-key="(row) => row.id"
        show-summary
        :summary-method="summarizeMainTable"
        @expand-change="expandChange"
        height="calc(100vh - 18.5em)"
        :row-class-name="tableRowClassName"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand">
          <template #default="props">
            <el-table
              :data="props.row.children"
              border
              show-summary
              :summary-method="summarizeChildrenTable"
            >
              <el-table-column
                align="center"
                label="序号"
                type="index"
                width="60"
              />
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column
                label="含税单价(元)"
                prop="taxInclusiveUnitPrice"
                :formatter="formattedNumber"
              />
              <el-table-column
                label="含税总价(元)"
                prop="taxInclusiveTotalPrice"
                :formatter="formattedNumber"
              />
              <el-table-column
                label="不含税总价(元)"
                prop="taxExclusiveTotalPrice"
                :formatter="formattedNumber"
              />
            </el-table>
          </template>
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
          label="采购合同号"
          prop="purchaseContractNumber"
          width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="销售合同号"
          prop="salesContractNo"
          width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="供应商名称"
          width="240"
          prop="supplierName"
          show-overflow-tooltip
        />
        <el-table-column label="订单状态" width="100" align="center">
          <template #default="scope">
            <el-tag v-if="scope.row.isInvalid" type="danger" size="small">失效</el-tag>
            <el-tag v-else type="success" size="small">正常</el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="项目名称"
          prop="projectName"
          width="420"
          show-overflow-tooltip
        />
        <el-table-column
            label="审批状态"
            prop="approvalStatus"
            width="200"
            show-overflow-tooltip
        >
          <template #default="scope">
            <el-tag
                size="small"
            >
              {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="付款方式"
          width="100"
          prop="paymentMethod"
          show-overflow-tooltip
        />
        <el-table-column
          label="合同金额(元)"
          prop="contractAmount"
           width="200"
          show-overflow-tooltip
          :formatter="formattedNumber"
        />
        <el-table-column
          label="录入人"
          prop="recorderName"
           width="100"
          show-overflow-tooltip
        />
        <el-table-column
          label="录入日期"
          prop="entryDate"
           width="100"
          show-overflow-tooltip
        />
        <el-table-column
          fixed="right"
          label="操作"
          min-width="150"
          align="center"
        >
          <template #default="scope">
            <el-button
              link
              type="primary"
              size="small"
              @click="approvePurchase(scope.row)"
              :disabled="scope.row.approvalStatus !== 0"
              >审批</el-button
            >
            <el-button
                link
                type="primary"
                size="small"
                @click="rejectPurchase(scope.row)"
                :disabled="scope.row.approvalStatus !== 0"
            >拒绝审批</el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
  </div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  getSalesLedgerWithProducts,
  addOrUpdateSalesLedgerProduct,
  delProduct,
  delLedgerFile,
  getProductInfoByContractNo,
} from "@/api/salesManagement/salesLedger.js";
import {
  addOrEditPurchase,
  delPurchase,
  getSalesNo,
  purchaseListPage,
  productList,
  getPurchaseById,
  getOptions,
  createPurchaseNo, updateApprovalStatus,
} from "@/api/procurementManagement/procurementLedger.js";
import useFormData from "@/hooks/useFormData.js";
import QRCode from "qrcode";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
const productSelectedRows = ref([]);
const modelOptions = ref([]);
const userList = ref([]);
const productOptions = ref([]);
const salesContractList = ref([]);
const supplierList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const fileList = ref([]);
import useUserStore from "@/store/modules/user";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import dayjs from "dayjs";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore();
// äºŒç»´ç ç›¸å…³å˜é‡
const qrCodeDialogVisible = ref(false);
const qrCodeUrl = ref("");
// è®¢å•审批状态显示文本
const approvalStatusText = {
  0: '待审批',
  1: '审批通过',
  2: '审批失败'
};
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "", // ä¾›åº”商名称
    purchaseContractNumber: "", // é‡‡è´­åˆåŒç¼–号
    salesContractNo: "", // é”€å”®åˆåŒç¼–号
    projectName: "", // é¡¹ç›®åç§°
    entryDate: null, // å½•入日期
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
  form: {
    purchaseContractNumber: "",
    salesLedgerId: "",
    projectName: "",
    recorderId: "",
    entryDate: "",
    productData: [],
    supplierName: "",
    supplierId: "",
    paymentMethod: "",
        executionDate: "",
    approvalStatus: "0",
  },
  rules: {
    purchaseContractNumber: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
        entryDate: [{ required: true, message: "请选择", trigger: "change" }],
        executionDate: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const {  form, rules } = toRefs(data);
const { form: searchForm } = useFormData(data.searchForm);
// äº§å“è¡¨å•弹框数据
const productFormVisible = ref(false);
const productOperationType = ref("");
const productOperationIndex = ref("");
const currentId = ref("");
const productFormData = reactive({
  productForm: {
    productId: "",
    productCategory: "",
    productModelId: "",
    specificationModel: "",
    unit: "",
    quantity: "",
    taxInclusiveUnitPrice: "",
    taxRate: "",
    taxInclusiveTotalPrice: "",
    taxExclusiveTotalPrice: "",
    invoiceType: "",
        warnNum: "",
  },
  productRules: {
    productId: [{ required: true, message: "请选择", trigger: "change" }],
    productModelId: [{ required: true, message: "请选择", trigger: "change" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    taxInclusiveUnitPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxRate: [{ required: true, message: "请选择", trigger: "change" }],
        warnNum: [{ required: false, message: "请选择", trigger: "change" }],
    taxInclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxExclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const { productForm, productRules } = toRefs(productFormData);
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
const changeDaterange = (value) => {
  if (value) {
    searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    searchForm.entryDateStart = undefined;
    searchForm.entryDateEnd = undefined;
  }
  handleQuery();
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(
    param,
    [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
      "ticketsNum",
      "ticketsAmount",
      "futureTickets",
      "futureTicketsAmount",
    ],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const { entryDate, ...rest } = searchForm;
  purchaseListPage({ ...rest, ...page })
    .then((res) => {
      tableLoading.value = false;
      // tableData.value = res.data.records;
      // å¤„理数据,添加失效状态标记
      tableData.value = res.data.records.map(record => ({
        ...record,
        isInvalid: record.isWhite === 1
      }));
      tableData.value.map((item) => {
        item.children = [];
      });
      total.value = res.data.total;
      expandedRowKeys.value = [];
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const productSelected = (selectedRows) => {
  productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      productList({ salesLedgerId: row.id, type: 2 }).then((res) => {
        const index = tableData.value.findIndex((item) => item.id === row.id);
        if (index > -1) {
          tableData.value[index].children = res.data;
        }
        expandedRowKeys.value.push(row.id);
      });
    } catch (error) {
      console.log(error);
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ["contractAmount"]);
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeProTable = (param) => {
  return proxy.summarizeTable(param, [
    "taxInclusiveUnitPrice",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  productData.value = [];
  fileList.value = [];
  if (operationType.value == "add") {
    createPurchaseNo().then((res) => {
      form.value.purchaseContractNumber = res.data;
    });
  }
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  getSalesNo().then((res) => {
    salesContractList.value = res;
  });
  getOptions().then((res) => {
    // ä¾›åº”商过滤出isWhite=0 çš„æ•°æ®
    supplierList.value = res.data.filter((item) => item.isWhite == 0);
  });
  form.value.recorderId = userStore.id;
  form.value.entryDate = getCurrentDate();
  if (type === "edit") {
    currentId.value = row.id;
    getPurchaseById({ id: row.id, type: 2 }).then((res) => {
      form.value = { ...res };
      productData.value = form.value.productData;
      if (form.value.salesLedgerFiles) {
        fileList.value = form.value.salesLedgerFiles;
      } else {
        fileList.value = [];
      }
    });
  }
  dialogFormVisible.value = true;
};
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (file.size > 1024 * 1024 * 10) {
    proxy.$modal.msgError("上传文件大小不能超过10MB!");
    return false;
  }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// ä¸Šä¼ å¤±è´¥
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    file.tempId = res.data.tempId;
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// ç§»é™¤æ–‡ä»¶
function handleRemove(file) {
  console.log("handleRemove", file.id);
  if (file.size > 1024 * 1024 * 10) {
    // ä»…前端清理,不调用删除接口和提示
    return;
  }
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.id);
    delLedgerFile(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
    });
  }
}
// æäº¤è¡¨å•
const submitForm = (n) => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (productData.value.length > 0) {
        form.value.productData = proxy.HaveJson(productData.value);
      } else {
        proxy.$modal.msgWarning("请添加产品信息");
        return;
      }
      let tempFileIds = [];
      if (fileList.value.length > 0) {
        tempFileIds = fileList.value.map((item) => item.tempId);
      }
      form.value.tempFileIds = tempFileIds;
      form.value.type = 2;
      form.value.approvalStatus = n;
      addOrEditPurchase(form.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
      });
    }
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// æ‰“开产品弹框
const openProductForm = (type, row, index) => {
  productOperationType.value = type;
  productOperationIndex.value = index;
  productForm.value = {};
  proxy.resetForm("productFormRef");
  if (type === "edit") {
    productForm.value = { ...row };
  }
  productFormVisible.value = true;
  getProductOptions();
};
const getProductOptions = () => {
  productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
  });
};
const getModels = (value) => {
  if (value) {
    productForm.value.productCategory = findNodeById(productOptions.value, value) || "";
    modelList({ id: value }).then((res) => {
      modelOptions.value = res;
    });
  } else {
    productForm.value.productCategory = "";
    modelOptions.value = [];
  }
};
const getProductModel = (value) => {
  const index = modelOptions.value.findIndex((item) => item.id === value);
  if (index !== -1) {
    productForm.value.specificationModel = modelOptions.value[index].model;
    productForm.value.unit = modelOptions.value[index].unit;
  } else {
    productForm.value.specificationModel = null;
    productForm.value.unit = null;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹çš„label
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œç›´æŽ¥è¿”å›žï¼ˆå·²ç»æ˜¯label字符串)
      }
    }
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
      ...rest,
      value: id, // å°† id æ”¹ä¸º value
    };
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// æäº¤äº§å“è¡¨å•
const submitProduct = () => {
  proxy.$refs["productFormRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitProductEdit();
      } else {
        if (productOperationType.value === "add") {
          productData.value.push({ ...productForm.value });
          console.log("productData.value---", productData.value);
        } else {
          productData.value[productOperationIndex.value] = {
            ...productForm.value,
          };
        }
        closeProductDia();
      }
    }
  });
};
const submitProductEdit = () => {
  productForm.value.salesLedgerId = currentId.value;
  productForm.value.type = 2;
  addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeProductDia();
    getPurchaseById({ id: currentId.value, type: 2 }).then((res) => {
      productData.value = res.productData;
    });
  });
};
// åˆ é™¤äº§å“
const deleteProduct = () => {
  if (productSelectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  if (operationType.value === "add") {
    productSelectedRows.value.forEach((selectedRow) => {
      const index = productData.value.findIndex(
        (product) => product.id === selectedRow.id
      );
      if (index !== -1) {
        productData.value.splice(index, 1);
      }
    });
  } else {
    let ids = [];
    if (productSelectedRows.value.length > 0) {
      ids = productSelectedRows.value.map((item) => item.id);
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delProduct(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          closeProductDia();
          getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
            (res) => {
              productData.value = res.productData;
            }
          );
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  }
};
// å…³é—­äº§å“å¼¹æ¡†
const closeProductDia = () => {
  proxy.resetForm("productFormRef");
  productFormVisible.value = false;
};
// å®¡æ‰¹é€šè¿‡æ–¹æ³•
const approvePurchase = (row) => {
  ElMessageBox.confirm(`确认通过采购合同号为 ${row.purchaseContractNumber} çš„审批?`, '审批确认', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    updateApprovalStatus({ id: row.id, approvalStatus: 1}).then((res)=>{
      proxy.$modal.msgSuccess('审批成功');
      getList();
    })
  }).catch(() => {
    proxy.$modal.msg('已取消审批');
  });
};
// å®¡æ‰¹æ‹’绝方法
const rejectPurchase = (row) => {
  ElMessageBox.confirm(`确认拒绝采购合同号为 ${row.purchaseContractNumber} çš„审批?`, '审批确认', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    updateApprovalStatus({ id: row.id, approvalStatus: 2}).then((res)=>{
      proxy.$modal.msgSuccess('审批成功');
      getList();
    })
  }).catch(() => {
    proxy.$modal.msg('已取消审批');
  });
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/purchase/ledger/export", {}, "采购台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
        }
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      delPurchase(ids).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
const mathNum = () => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  if (!productForm.value.taxInclusiveUnitPrice) {
    return;
  }
  if (!productForm.value.quantity) {
    return;
  }
  // å«ç¨Žæ€»ä»·è®¡ç®—
  productForm.value.taxInclusiveTotalPrice =
    proxy.calculateTaxIncludeTotalPrice(
      productForm.value.taxInclusiveUnitPrice,
      productForm.value.quantity
    );
  if (productForm.value.taxRate) {
    // ä¸å«ç¨Žæ€»ä»·è®¡ç®—
    productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
        productForm.value.taxInclusiveTotalPrice,
        productForm.value.taxRate
      );
  }
};
const reverseMathNum = (field) => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  const taxRate = Number(productForm.value.taxRate);
  if (!taxRate) return;
  if (field === 'taxInclusiveTotalPrice') {
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼Œåç®—含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
    }
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œå«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    }
    // åç®—不含税总价
    productForm.value.taxExclusiveTotalPrice =
      (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2);
  } else if (field === 'taxExclusiveTotalPrice') {
    // åç®—含税总价
    productForm.value.taxInclusiveTotalPrice =
      (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2);
    // å·²çŸ¥æ•°é‡ï¼Œåç®—含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
    }
    // å·²çŸ¥å«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    }
  }
};
// é”€å”®åˆåŒé€‰æ‹©æ”¹å˜æ–¹æ³•
const salesLedgerChange = async (row) => {
  console.log("row", row);
  var index = salesContractList.value.findIndex((item) => item.id == row);
  console.log("index", index);
  if (index > -1) {
    form.value.projectName = salesContractList.value[index].projectName;
    await querygProductInfoByContractNo();
  }
};
const querygProductInfoByContractNo = async () => {
  const { code, data } = await getProductInfoByContractNo({
    contractNo: form.value.salesLedgerId,
  });
  if (code == 200) {
    productData.value = data;
  }
};
// æ˜¾ç¤ºäºŒç»´ç 
const showQRCode = async (row) => {
  try {
    // æž„建二维码内容,只包含采购合同号(纯文本)
    const qrContent = row.purchaseContractNumber || '';
    // æ£€æŸ¥å†…容是否为空
    if (!qrContent || qrContent.trim() === '') {
      proxy.$modal.msgWarning("该行没有采购合同号,无法生成二维码");
      return;
    }
    qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
      width: 200,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
      }
    });
    qrCodeDialogVisible.value = true;
  } catch (error) {
    console.error('生成二维码失败:', error);
    proxy.$modal.msgError("生成二维码失败:" + error.message);
  }
};
// ä¸‹è½½äºŒç»´ç 
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    proxy.$modal.msgWarning("二维码未生成");
    return;
  }
  const a = document.createElement('a');
  a.href = qrCodeUrl.value;
  a.download = `采购合同号二维码_${new Date().getTime()}.png`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  proxy.$modal.msgSuccess("下载成功");
};
// æ‰«ç æ–°å¢žå¯¹è¯æ¡†ç›¸å…³å˜é‡
const scanAddDialogVisible = ref(false);
const scanAddForm = reactive({
  scanContent: "",
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  contractAmount: "",
  paymentMethod: "",
  recorderName: "",
  scanRemark: "",
});
const scanAddRules = {
  purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
  supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }],
  projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
};
// æ‰«ç ç™»è®°å¯¹è¯æ¡†ç›¸å…³å˜é‡
const scanDialogVisible = ref(false);
const scanForm = reactive({
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  scanTime: "",
  scannerName: "",
  scanStatus: "未扫码",
  scanRemark: "",
});
const scanRules = {
  scanRemark: [{ required: true, message: "请输入扫码备注", trigger: "blur" }],
};
const scanRecords = ref([]);
// æ‰“开扫码新增对话框
const openScanAddDialog = () => {
  scanAddForm.scanContent = "";
  scanAddForm.purchaseContractNumber = "";
  scanAddForm.supplierName = "";
  scanAddForm.projectName = "";
  scanAddForm.contractAmount = "";
  scanAddForm.paymentMethod = "";
  scanAddForm.recorderName = userStore.nickName;
  scanAddForm.scanRemark = "";
  scanAddDialogVisible.value = true;
};
// è§£æžæ‰«ç å†…容(模拟解析二维码数据)
const parseScanContent = (content) => {
  if (!content) return;
  // æ¨¡æ‹Ÿè§£æžäºŒç»´ç å†…容,这里可以根据实际需求调整解析逻辑
  // å‡è®¾æ‰«ç å†…容格式为:合同号|供应商|项目|金额|付款方式
  const parts = content.split('|');
  if (parts.length >= 3) {
    scanAddForm.purchaseContractNumber = parts[0] || "";
    scanAddForm.supplierName = parts[1] || "";
    scanAddForm.projectName = parts[2] || "";
    scanAddForm.contractAmount = parts[3] || "";
    scanAddForm.paymentMethod = parts[4] || "";
  }
};
// å…³é—­æ‰«ç æ–°å¢žå¯¹è¯æ¡†
const closeScanAddDialog = () => {
  scanAddDialogVisible.value = false;
  proxy.resetForm("scanAddFormRef");
};
// æäº¤æ‰«ç æ–°å¢ž
const submitScanAdd = () => {
  proxy.$refs["scanAddFormRef"].validate((valid) => {
    if (valid) {
      // æž„建新增数据
      const newData = {
        purchaseContractNumber: scanAddForm.purchaseContractNumber,
        supplierName: scanAddForm.supplierName,
        projectName: scanAddForm.projectName,
        contractAmount: scanAddForm.contractAmount,
        paymentMethod: scanAddForm.paymentMethod,
        recorderName: scanAddForm.recorderName,
        entryDate: getCurrentDate(),
        remark: scanAddForm.scanRemark,
        type: 2
      };
      // æ¨¡æ‹Ÿæ–°å¢žæˆåŠŸ
      proxy.$modal.msgSuccess("扫码新增成功!");
      closeScanAddDialog();
      // å¯ä»¥é€‰æ‹©æ˜¯å¦åˆ·æ–°åˆ—表
      // getList();
    }
  });
};
// æ‰“开扫码登记对话框
const openScanDialog = (row) => {
  scanForm.purchaseContractNumber = row.purchaseContractNumber;
  scanForm.supplierName = row.supplierName;
  scanForm.projectName = row.projectName;
  scanForm.scanTime = getCurrentDateTime();
  scanForm.scannerName = userStore.nickName;
  scanForm.scanStatus = "未扫码";
  scanForm.scanRemark = "";
  scanRecords.value = [];
  scanDialogVisible.value = true;
};
// å…³é—­æ‰«ç ç™»è®°å¯¹è¯æ¡†
const closeScanDialog = () => {
  scanDialogVisible.value = false;
  proxy.resetForm("scanFormRef");
};
// æäº¤æ‰«ç ç™»è®°
const submitScan = () => {
  proxy.$refs["scanFormRef"].validate((valid) => {
    if (valid) {
      // æ·»åŠ æ‰«ç è®°å½•
      scanRecords.value.push({
        ...scanForm,
        id: Date.now(), // æ¨¡æ‹ŸID
        scanTime: getCurrentDateTime(),
      });
      scanForm.scanStatus = "已扫码";
      scanForm.scanRemark = scanForm.scanRemark || "无";
      proxy.$modal.msgSuccess("扫码登记成功!");
      closeScanDialog();
    }
  });
};
// èŽ·å–å½“å‰æ—¥æœŸæ—¶é—´
function getCurrentDateTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, "0");
  const day = String(now.getDate()).padStart(2, "0");
  const hours = String(now.getHours()).padStart(2, "0");
  const minutes = String(now.getMinutes()).padStart(2, "0");
  const seconds = String(now.getSeconds()).padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// æ·»åŠ è¡Œç±»åæ–¹æ³•
const tableRowClassName = ({ row }) => {
  return row.isInvalid ? 'invalid-row' : '';
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.invalid-row {
  opacity: 0.6;
  background-color: #f5f7fa;
}
</style>
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -52,54 +52,21 @@
                  </template>
                </el-table-column>
                <el-table-column prop="readCount" label="已读人数" width="100" />
                <el-table-column label="操作" width="250" fixed="right">
                <el-table-column label="操作" width="320" fixed="right">
                  <template #default="scope">
                    <el-button link @click="viewRegulation(scope.row)">查看</el-button>
                    <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button link type="danger" @click="repealEdit(scope.row)">废弃</el-button>
                    <el-button link type="success" @click="viewVersionHistory(scope.row)">版本历史</el-button>
                    <el-button link type="warning" @click="viewReadStatus(scope.row)">阅读状态</el-button>
                    <el-button link type="primary" @click="openFileDialog(scope.row)">附件</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-card>
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡† -->
    <!-- <el-dialog v-model="showSealApplyDialog" title="申请用印" width="600px">
      <el-form :model="sealForm" :rules="sealRules" ref="sealFormRef" label-width="100px">
        <el-form-item label="申请编号" prop="applicationNum">
          <el-input v-model="sealForm.applicationNum" placeholder="请输入申请编号" />
        </el-form-item>
        <el-form-item label="申请标题" prop="title">
          <el-input v-model="sealForm.title" placeholder="请输入申请标题" />
        </el-form-item>
        <el-form-item label="用印类型" prop="sealType">
          <el-select v-model="sealForm.sealType" placeholder="请选择用印类型" style="width: 100%">
            <el-option label="公章" value="official" />
            <el-option label="合同专用章" value="contract" />
            <el-option label="财务专用章" value="finance" />
            <el-option label="法人章" value="legal" />
          </el-select>
        </el-form-item>
        <el-form-item label="申请原因" prop="reason">
          <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="请详细说明用印原因" />
        </el-form-item>
        <el-form-item label="紧急程度" prop="urgency">
          <el-radio-group v-model="sealForm.urgency">
            <el-radio label="normal">普通</el-radio>
            <el-radio label="urgent">紧急</el-radio>
            <el-radio label="very-urgent">特急</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showSealApplyDialog = false">取消</el-button>
          <el-button type="primary" @click="submitSealApplication">提交申请</el-button>
        </span>
      </template>
    </el-dialog> -->
    <!-- ç”¨å°ç”³è¯·å¯¹è¯æ¡†ï¼ˆå·²ç§»é™¤ï¼‰ -->
    <!-- è§„章制度发布对话框 -->
    <el-dialog v-model="showRegulationDialog" :title="operationType === 'add' ? '发布制度' : '编辑制度'" width="800px">
@@ -150,25 +117,7 @@
      </template>
    </el-dialog>
    <!-- ç”¨å°è¯¦æƒ…对话框 -->
    <!-- <el-dialog v-model="showSealDetailDialog" title="用印申请详情" width="700px">
      <div v-if="currentSealDetail" class="mb10">
        <el-descriptions :column="2" border>
          <el-descriptions-item label="申请编号">{{ currentSealDetail.id }}</el-descriptions-item>
          <el-descriptions-item label="申请标题">{{ currentSealDetail.title }}</el-descriptions-item>
          <el-descriptions-item label="申请人">{{ currentSealDetail.createUserName }}</el-descriptions-item>
          <el-descriptions-item label="所属部门">{{ currentSealDetail.department }}</el-descriptions-item>
          <el-descriptions-item label="用印类型">{{ getSealTypeText(currentSealDetail.sealType) }}</el-descriptions-item>
          <el-descriptions-item label="申请时间">{{ currentSealDetail.createTime }}</el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag :type="getStatusType(currentSealDetail.status)">
              {{ getStatusText(currentSealDetail.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="申请原因" :span="2">{{ currentSealDetail.reason }}</el-descriptions-item>
        </el-descriptions>
      </div>
    </el-dialog> -->
    <!-- ç”¨å°è¯¦æƒ…对话框(已移除) -->
    <!-- è§„章制度详情对话框 -->
    <el-dialog v-model="showRegulationDetailDialog" title="规章制度详情" width="800px">
@@ -224,54 +173,42 @@
        </el-table-column>
      </el-table>
    </el-dialog>
    <FileListDialog
      ref="fileListDialogRef"
      v-model="fileDialogVisible"
      :show-upload-button="true"
      :show-delete-button="true"
      :delete-method="handleAttachmentDelete"
      :rules-regulations-management-id="currentFileRuleId"
      :name-column-label="'附件名称'"
      @upload="handleAttachmentUpload"
    />
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
import { listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import FileListDialog from '@/components/Dialog/FileListDialog.vue'
import { listRuleFiles, delRuleFile, addRuleFile } from '@/api/collaborativeApproval/rulesRegulationsManagementFile.js'
// å“åº”式数据
const currentUser = ref(null)
const activeTab = ref('seal')
const operationType = ref('add')
const tableData = ref([])
// ç”¨å°ç”³è¯·ç›¸å…³
const userStore = useUserStore()
const showSealApplyDialog = ref(false)
const tableLoading = ref(false)
const showSealDetailDialog = ref(false)
const currentSealDetail = ref(null)
const sealFormRef = ref()
const sealForm = reactive({
  applicationNum: '',
  title: '',
  sealType: '',
  reason: '',
  urgency: 'normal',
  status: 'pending'
})
const sealRules = {
  applicationNum: [{ required: true, message: '请输入申请编号', trigger: 'blur' }],
  title: [{ required: true, message: '请输入申请标题', trigger: 'blur' }],
  sealType: [{ required: true, message: '请选择用印类型', trigger: 'change' }],
  reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
}
const sealSearchForm = reactive({
  title: '',
  status: ''
})
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// é™„件弹窗
const fileDialogVisible = ref(false)
const fileListDialogRef = ref(null)
const currentFileRuleId = ref(null)
const filePage = reactive({
  current: 1,
  size: 10,
  total: 0
@@ -320,9 +257,6 @@
  category: ''
})
// å‡æ•°æ®
const sealApplications = ref([])
const regulations = ref([])
const versionHistory = ref([])
@@ -332,34 +266,6 @@
  // { employee: '刘雅婷', department: '技术部', readTime: '2025-01-11 14:20:00', confirmTime: '', status: 'unconfirmed' },
  // { employee: '王建国', department: '财务部', readTime: '2025-01-12 09:15:00', confirmTime: '2025-01-12 09:20:00', status: 'confirmed' }
// ç”¨å°ç”³è¯·çŠ¶æ€
const getStatusType = (status) => {
  const statusMap = {
    pending: 'warning',
    approved: 'success',
    rejected: 'danger'
  }
  return statusMap[status] || 'info'
}
// åˆ¶åº¦çŠ¶æ€
const getStatusText = (status) => {
  const statusMap = {
    pending: '待审批',
    approved: '已通过',
    rejected: '已拒绝'
  }
  return statusMap[status] || '未知'
}
// ç”¨å°ç±»åž‹
const getSealTypeText = (sealType) => {
  const sealTypeMap = {
    official: '公章',
    contract: '合同专用章',
    finance: '财务专用章',
    tegal: '技术专用章'
  }
  return sealTypeMap[sealType] || '未知'
}
// åˆ¶åº¦åˆ†ç±»
const getCategoryText = (category) => {
  const categoryMap = {
@@ -369,19 +275,6 @@
    tech: '技术制度'
  }
  return categoryMap[category] || '未知'
}
// æœç´¢å°ç« ç”³è¯·
const searchSealApplications = () => {
  page.current=1
  getSealApplicationList()
  // ElMessage.success('搜索完成')
}
// é‡ç½®å°ç« ç”³è¯·æœç´¢
const resetSealSearch = () => {
  sealSearchForm.title = ''
  sealSearchForm.status = ''
  searchSealApplications()
}
// æœç´¢åˆ¶åº¦
const searchRegulations = () => {
@@ -393,32 +286,6 @@
  regulationSearchForm.title = ''
  regulationSearchForm.category = ''
  searchRegulations()
}
// æäº¤ç”¨å°ç”³è¯·
const submitSealApplication = async () => {
  try {
    await sealFormRef.value.validate()
    addSealApplication(sealForm).then(res => {
      if(res.code == 200){
        ElMessage.success('申请提交成功')
        showSealApplyDialog.value = false
        getSealApplicationList()
        Object.assign(sealForm, {
        applicationNum: '',
        title: '',
        sealType: '',
        reason: '',
        urgency: 'normal',
        status: 'pending'
      })
      }
    }).catch(err => {
      ElMessage.error(err.msg)
    })
  } catch (error) {
    ElMessage.error('请完善申请信息')
  }
}
// æ–°å¢ž
const handleAdd = () => {
@@ -501,72 +368,6 @@
}
// æŸ¥çœ‹ç”¨å°ç”³è¯·è¯¦æƒ…
const viewSealDetail = (row) => {
  currentSealDetail.value = row
  showSealDetailDialog.value = true
}
// å®¡æ‰¹ç”¨å°ç”³è¯·
const approveSeal = (row) => {
  console.log(row)
  ElMessageBox.confirm('确认通过该用印申请?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    row.status = 'approved'
    updateSealApplication(row).then(res => {
      if(res.code == 200){
        ElMessage.success('审批通过')
      }
    })
  })
}
// æ‹’绝用印申请
const rejectSeal = (row) => {
  ElMessageBox.prompt('请输入拒绝原因', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    inputPattern: /\S+/,
    inputErrorMessage: '拒绝原因不能为空'
  }).then(({ value }) => {
    row.status = 'rejected'
    updateSealApplication(row).then(res => {
      if(res.code == 200){
        ElMessage.success('审批拒绝')
      }
    })
    ElMessage.success('已拒绝申请')
  })
}
// èŽ·å–åœ¨èŒå‘˜å·¥åˆ—è¡¨
const getList = () => {
  tableLoading.value = true;
      //获取当前登录用户信息
  getUserProfile().then(res => {
    if(res.code == 200){
      console.log(res.data.userName)
      currentUser.value = res.data.userName
    }
  })
  staffJoinListPage({staffState: 1}).then(res => {
    tableLoading.value = false;
    // tableData.value = res.data.records
    // //筛选出和currentUser同名的人员
    tableData.value = res.data.records.filter(item => item.staffName === currentUser.value)
    console.log("tableData",tableData.value)
    page.total = res.data.total;
    if(tableData.value.length == 0){
    ElMessage.error('当前用户未加入任何部门')
    }
  }).catch(err => {
    tableLoading.value = false;
  })
};
// æŸ¥çœ‹åˆ¶åº¦ç‰ˆæœ¬åŽ†å²
const viewVersionHistory = (row) => {
  showVersionHistoryDialog.value = true
@@ -582,7 +383,6 @@
}
// æŸ¥çœ‹åˆ¶åº¦è¯¦æƒ…
const viewRegulation = (row) => {
  getList()
  currentRegulationDetail.value = row
  showRegulationDetailDialog.value = true
  getReadingStatusByRuleId(row.id).then(res => {
@@ -659,32 +459,64 @@
  proxy.download('/rulesRegulationsManagement/export', { ...regulationSearchForm }, '规章制度.xlsx')
}
// èŽ·å–å°ç« ç”³è¯·åˆ—è¡¨æ•°æ®
const getSealApplicationList = async () => {
  tableLoading.value = true
  listSealApplication(page,sealSearchForm)
  .then(res => {
    //获取当前登录的部门信息
// èŽ·å–å½“å‰ç™»å½•çš„éƒ¨é—¨ä¿¡æ¯å¹¶è¿‡æ»¤æ•°æ®
    const currentFactoryName = userStore.currentFactoryName
    if (currentFactoryName) {
      // æ ¹æ®currentFactoryName过滤出department相同的数据
      sealApplications.value = res.data.records.filter(item => item.department === currentFactoryName)
      // æ›´æ–°è¿‡æ»¤åŽçš„æ€»æ•°
      page.value.total = sealApplications.value.length
    } else {
      // å¦‚果没有currentFactoryName,则显示所有数据
      sealApplications.value = res.data.records
      page.value.total = res.data.total
// é™„件:查询
const fetchRuleFiles = async (rulesRegulationsManagementId) => {
  const params = {
    current: filePage.current,
    size: filePage.size,
    rulesRegulationsManagementId
    }
    // sealApplications.value = res.data.records
    // page.value.total = res.data.total;
    tableLoading.value = false;
  const res = await listRuleFiles(params)
  const records = res?.data?.records || []
  filePage.total = res?.data?.total || records.length
  const mapped = records.map(item => ({
    id: item.id,
    name: item.fileName || item.name,
    url: item.fileUrl || item.url,
    raw: item
  }))
  fileListDialogRef.value?.setList(mapped)
}
  }).catch(err => {
    tableLoading.value = false;
  })
// æ‰“开附件弹窗
const openFileDialog = async (row) => {
  currentFileRuleId.value = row.id
  fileDialogVisible.value = true
  await fetchRuleFiles(row.id)
}
// åˆ·æ–°é™„件列表
const refreshFileList = async () => {
  if (!currentFileRuleId.value) return
  await fetchRuleFiles(currentFileRuleId.value)
}
// ä¸Šä¼ é™„件(由子组件触发)
const handleAttachmentUpload = async (filePayload) => {
  if (!currentFileRuleId.value) return
  const payload = {
    name: filePayload?.fileName || filePayload?.name,
    url: filePayload?.fileUrl || filePayload?.url,
    rulesRegulationsManagementId: currentFileRuleId.value
  }
  await addRuleFile(payload)
  ElMessage.success('文件上传成功')
  await refreshFileList()
}
// åˆ é™¤é™„ä»¶
const handleAttachmentDelete = async (row) => {
  if (!row?.id) return false
  try {
    await ElMessageBox.confirm('确认删除该附件?', '提示', { type: 'warning' })
  } catch {
    return false
  }
  await delRuleFile([row.id])
  ElMessage.success('删除成功')
  await refreshFileList()
}
// èŽ·å–è§„ç« åˆ¶åº¦åˆ—è¡¨æ•°æ®
const getRegulationList = async () => {
  tableLoading.value = true
@@ -704,7 +536,6 @@
onMounted(() => {
  // åˆå§‹åŒ–
  getSealApplicationList()
  getRegulationList()
})
</script>
src/views/collaborativeApproval/sealManagement/index.vue
@@ -12,11 +12,15 @@
        <div class="tab-content">
            <el-row :gutter="20" class="mb-20 ">
              <span class="ml-10">用印标题:</span>
              <el-col :span="6">
              <el-col :span="4">
                <el-input v-model="sealSearchForm.title" placeholder="请输入申请标题" clearable />
              </el-col>
              <span class="ml-10">用印编号:</span>
              <el-col :span="4">
                <el-input v-model="sealSearchForm.applicationNum" placeholder="请输入用印编号" clearable />
              </el-col>
              <span class="search_title">审批状态:</span>
              <el-col :span="6">
              <el-col :span="4">
                <el-select v-model="sealSearchForm.status" placeholder="审批状态" clearable>
                  <el-option label="待审批" value="pending" />
                  <el-option label="已通过" value="approved" />
@@ -96,6 +100,16 @@
        </el-form-item>
        <el-form-item label="申请原因" prop="reason">
          <el-input v-model="sealForm.reason" type="textarea" :rows="4" placeholder="请详细说明用印原因" />
        </el-form-item>
        <el-form-item label="审批人" prop="approveUserId">
          <el-select v-model="sealForm.approveUserId" placeholder="请选择审批人" style="width: 100%" filterable>
            <el-option
                v-for="user in userList"
                :key="user.userId"
                :label="user.nickName"
                :value="user.userId"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="紧急程度" prop="urgency">
          <el-radio-group v-model="sealForm.urgency">
@@ -240,15 +254,16 @@
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ref, reactive, onMounted, getCurrentInstance, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { listSealApplication, addSealApplication, updateSealApplication,listRuleManagement,addRuleManagement,updateRuleManagement,delRuleManagement,getReadingStatusByRuleId,getReadingStatusList,addReadingStatus,updateReadingStatus  } from '@/api/collaborativeApproval/sealManagement.js'
import { el } from 'element-plus/es/locales.mjs'
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import { getUserProfile, userListNoPageByTenantId } from '@/api/system/user.js'
import useUserStore from '@/store/modules/user'
import { userLoginFacotryList } from "@/api/system/user.js"
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å“åº”式数据
const currentUser = ref(null)
@@ -257,16 +272,19 @@
const tableData = ref([])
// ç”¨å°ç”³è¯·ç›¸å…³
const userStore = useUserStore()
const route = useRoute()
const showSealApplyDialog = ref(false)
const tableLoading = ref(false)
const showSealDetailDialog = ref(false)
const currentSealDetail = ref(null)
const sealFormRef = ref()
const userList = ref([])
const sealForm = reactive({
  applicationNum: '',
  title: '',
  sealType: '',
  reason: '',
  approveUserId: '',
  urgency: 'normal',
  status: 'pending'
})
@@ -275,12 +293,14 @@
  applicationNum: [{ required: true, message: '请输入申请编号', trigger: 'blur' }],
  title: [{ required: true, message: '请输入申请标题', trigger: 'blur' }],
  sealType: [{ required: true, message: '请选择用印类型', trigger: 'change' }],
  reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
  reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }],
  approveUserId: [{ required: true, message: '请选择审批人', trigger: 'change' }]
}
const sealSearchForm = reactive({
  title: '',
  status: ''
  status: '',
  applicationNum: ''
})
// åˆ†é¡µå‚æ•°
const page = reactive({
@@ -393,6 +413,7 @@
const resetSealSearch = () => {
  sealSearchForm.title = ''
  sealSearchForm.status = ''
  sealSearchForm.applicationNum = ''
  searchSealApplications()
}
// æœç´¢åˆ¶åº¦
@@ -420,6 +441,7 @@
        title: '',
        sealType: '',
        reason: '',
        approveUserId: '',
        urgency: 'normal',
        status: 'pending'
      })
@@ -561,7 +583,7 @@
      currentUser.value = res.data.userName
    }
  })
  staffJoinListPage({staffState: 1, ...page}).then(res => {
  staffOnJobListPage({staffState: 1, ...page}).then(res => {
    tableLoading.value = false;
    // tableData.value = res.data.records
    // //筛选出和currentUser同名的人员
@@ -713,9 +735,24 @@
  })
}
// ç›‘听对话框打开,获取用户列表
watch(showSealApplyDialog, (newVal) => {
  if (newVal) {
    userListNoPageByTenantId().then((res) => {
      userList.value = res.data;
    });
  }
});
onMounted(() => {
  // åˆå§‹åŒ–
  // è·¯ç”±æºå¸¦ applicationNum æ—¶ï¼Œé¢„填并查询
  if (route.query.applicationNum) {
    sealSearchForm.applicationNum = String(route.query.applicationNum)
    page.current = 1
  getSealApplicationList()
  } else {
    getSealApplicationList()
  }
  getRegulationList()
})
</script>
src/views/collaborativeApproval/vehicleManagement/index.vue
@@ -43,7 +43,7 @@
        :is-selection="true"
        :border="true"
        :table-loading="tableLoading"
        :table-style="{ width: '100%', height: 'calc(100vh - 21.5em)' }"
        :table-style="{ width: '100%', height: 'calc(100vh - 18.5em)' }"
        :page="{
          current: page.current,
          size: page.size,
@@ -248,7 +248,6 @@
<script setup>
import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import pagination from "@/components/PIMTable/Pagination.vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
@@ -352,6 +351,16 @@
  size: 100,
});
const usageRecordsTotal = ref(0);
const usageRecordsColumns = ref([
  { label: "车牌号", prop: "plateNumber", width: "120" },
  { label: "使用人", prop: "userName", width: "120" },
  { label: "目的地", prop: "destination", width: "150" },
  { label: "使用时间", prop: "useTime", width: "180" },
  { label: "还车时间", prop: "returnTime", width: "180" },
  { label: "使用前里程(km)", prop: "odometerBefore", width: "130" },
  { label: "还车时里程(km)", prop: "odometerAfter", width: "130" },
  { label: "本次行驶里程(km)", prop: "travelDistance", width: "140" },
]);
const currentVehicleId = ref(null);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
@@ -393,18 +402,6 @@
    fixed: "right",
    width: "250",
  },
]);
// ä½¿ç”¨è®°å½•表格列配置
const usageRecordsColumns = ref([
  { label: "车牌号", prop: "plateNumber", width: "120" },
  { label: "使用人", prop: "userName", width: "120" },
  { label: "目的地", prop: "destination", width: "150" },
  { label: "使用时间", prop: "useTime", width: "180" },
  { label: "还车时间", prop: "returnTime", width: "180" },
  { label: "使用前里程(km)", prop: "odometerBefore", width: "130" },
  { label: "还车时里程(km)", prop: "odometerAfter", width: "130" },
  { label: "本次行驶里程(km)", prop: "travelDistance", width: "140" },
]);
// æŸ¥è¯¢åˆ—表
@@ -449,13 +446,13 @@
    remark: "",
  };
  
  let userLists = await userListNoPage();
  const userLists = await userListNoPage();
  userList.value = userLists.data || [];
  if (type !== "add") {
    currentId.value = row.id;
    getVehicleById(row.id).then((res) => {
      form.value = { ...res.data || res };
      form.value = { ...(res.data || res) };
    });
  }
  dialogFormVisible.value = true;
@@ -466,14 +463,14 @@
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "add") {
        addVehicle(form.value).then((res) => {
        addVehicle(form.value).then(() => {
          proxy.$modal.msgSuccess("新增成功");
          closeDia();
          getList();
        });
      } else {
        form.value.id = currentId.value;
        updateVehicle(form.value).then((res) => {
        updateVehicle(form.value).then(() => {
          proxy.$modal.msgSuccess("修改成功");
          closeDia();
          getList();
@@ -489,7 +486,7 @@
  dialogFormVisible.value = false;
};
// æ‰“开发货弹框
// æ‰“开使用车辆弹框
const openUseForm = async (row) => {
  currentUseRow.value = row;
  useForm.value = {
@@ -501,7 +498,7 @@
    remark: "",
  };
  
  let userLists = await userListNoPage();
  const userLists = await userListNoPage();
  userList.value = userLists.data || [];
  
  useFormVisible.value = true;
@@ -511,7 +508,7 @@
const submitUseForm = () => {
  proxy.$refs["useFormRef"].validate((valid) => {
    if (valid) {
      useVehicle(useForm.value).then((res) => {
      useVehicle(useForm.value).then(() => {
        proxy.$modal.msgSuccess("使用车辆成功");
        closeUseDia();
        getList();
@@ -538,7 +535,7 @@
    remark: "",
  };
  
  let userLists = await userListNoPage();
  const userLists = await userListNoPage();
  userList.value = userLists.data || [];
  
  returnFormVisible.value = true;
@@ -548,7 +545,7 @@
const submitReturnForm = () => {
  proxy.$refs["returnFormRef"].validate((valid) => {
    if (valid) {
      returnVehicle(returnForm.value).then((res) => {
      returnVehicle(returnForm.value).then(() => {
        proxy.$modal.msgSuccess("还车成功");
        closeReturnDia();
        getList();
@@ -630,7 +627,7 @@
    type: "warning",
  })
    .then(() => {
      delVehicle(ids).then((res) => {
      delVehicle(ids).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
src/views/inventoryManagement/dispatchLog/index.vue
@@ -21,9 +21,12 @@
                    clearable
                    @change="handleQuery"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <!-- <el-button type="primary" @click="openForm('add')">新增</el-button> -->
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
                <el-button type="primary" plain @click="handlePrint">打印</el-button>
@@ -160,7 +163,7 @@
                    <div v-for="(item, index) in printData" :key="index" class="print-page">
                        <div class="delivery-note">
                            <div class="header">
                                <div class="company-name">青海湟水峡农业发展有限公司</div>
                                <div class="company-name">鼎诚瑞实业有限责任公司</div>
                                <div class="document-title">零售发货单</div>
                            </div>
                            
@@ -255,9 +258,10 @@
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
import { ref } from "vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import {
    getStockOutPage,
    delStockOut,
@@ -319,13 +323,7 @@
};
const getList = () => {
    tableLoading.value = true;
    const params = {
        ...page,
        supplierName: searchForm.value.supplierName,
        timeStr: searchForm.value.timeStr
    }
    getStockOutPage(params)
    getStockOutPage({ ...searchForm.value, ...page })
        .then((res) => {
            tableLoading.value = false;
            tableData.value = res.data.records;
@@ -338,7 +336,6 @@
            tableLoading.value = false;
        });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
@@ -558,7 +555,7 @@
      <div class="print-page">
        <div class="delivery-note">
          <div class="header">
            <div class="company-name">青海湟水峡农业发展有限公司</div>
            <div class="company-name">鼎诚瑞实业有限责任公司</div>
            <div class="document-title">零售发货单</div>
          </div>
          
@@ -686,14 +683,6 @@
    const seconds = String(date.getSeconds()).padStart(2, "0");
    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
onMounted(() => {
    getList();
});
src/views/inventoryManagement/issueManagement/index.vue
@@ -18,7 +18,9 @@
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
        <!-- <el-button type="primary" @click="openForm('add')">新增出库</el-button> -->
        <el-button @click="handleOut">导出</el-button>
        <!-- <el-button type="danger" plain @click="handleDelete">删除</el-button> -->
      </div>
    </div>
    <div class="table_list">
@@ -34,6 +36,7 @@
        <el-table-column label="规格型号" prop="specificationModel" width="200" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="70" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="inboundNum" width="90" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="inboundNum0" width="90" show-overflow-tooltip />
        <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
        <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="税率(%)" prop="taxRate" width="80" show-overflow-tooltip />
@@ -75,14 +78,16 @@
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ref } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import {
    getStockInPage,
  getStockInPage
} from "@/api/inventoryManagement/stockIn.js";
import {
  getStockManagePage,
    delStockManage,
  stockOut,
} from "@/api/inventoryManagement/stockManage.js";
@@ -97,12 +102,17 @@
  size: 100,
})
const total = ref(0)
const fileList = ref([])
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const dialogFormVisible = ref(false)
const data = reactive({
  searchForm: {
    supplierName: '',
    inboundQuantity:'',
    inboundTime:'',
    nickName: '',
    userId: '',
    timeStr: '',
  },
  form: {
@@ -129,24 +139,35 @@
}
const getList = () => {
  tableLoading.value = true
  const params = {
    ...page,
    supplierName: searchForm.value.supplierName,
    timeStr: searchForm.value.timeStr
  }
  getStockInPage(params).then(res => {
  getStockInPage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    console.log('res', res.data.records)
  }).catch(() => {
    tableLoading.value = false
  })
}
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode.label; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
      }
    }
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
@@ -154,7 +175,8 @@
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
};
const currentRowId = ref(null)
const currentRowId = ref(null) // æ–°å¢žï¼šå­˜å‚¨å½“前操作的行ID
const currentRowNum = ref(0)
const salesLedgerProductId = ref(null);
@@ -165,12 +187,14 @@
  currentRowNum.value = row.inboundNum0
  salesLedgerProductId.value = row.salesLedgerProductId
  form.value = {}
  // åˆå§‹åŒ–表单数据
  form.value = {
    productrecordId: '',
    inboundQuantity: '',
    inboundTime: getCurrentDate(),
    nickName: '',
    inboundQuantity: '', // å‡ºåº“数量清空
    inboundTime: getCurrentDate(), // é»˜è®¤å½“前日期
    nickName: '', // é»˜è®¤å½“前用户
  }
  console.log('form',form.value)
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  try {
    const userLists = await userListNoPageByTenantId()
@@ -189,13 +213,13 @@
  proxy.$refs["formRef"].validate(valid => {
    if (valid && currentRowId.value) {
      const outData = {
        id: currentRowId.value,
        id: currentRowId.value, // åŽŸå§‹è®°å½•ID
        salesLedgerProductId: salesLedgerProductId.value,
        quantity: form.value.inboundQuantity,
        time: form.value.inboundTime,
        userId: form.value.nickName,
        type: 1 // é‡‡è´­å‡ºåº“
        quantity: form.value.inboundQuantity, // å‡ºåº“数量
        time: form.value.inboundTime, // å‡ºåº“æ—¶é—´
        userId: form.value.nickName // æ“ä½œäºº
      }
      console.log(outData)
      stockOut(outData).then(res => {
        proxy.$modal.msgSuccess("提交成功")
@@ -207,7 +231,6 @@
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
@@ -229,14 +252,30 @@
    proxy.$modal.msg("已取消")
  })
}
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, '0');
  const day = String(today.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
    '选中的内容将被删除,是否确认删除?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    delStockManage(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
src/views/inventoryManagement/receiptManagement/components/formDia.vue
@@ -8,8 +8,6 @@
          placeholder="请选择采购订单号"
          clearable
          filterable
          remote
          :remote-method="loadPurchaseOptions"
          :loading="loadingPurchaseOptions"
          @change="handlePurchaseChange"
          :disabled="operationType === 'edit'"
@@ -39,33 +37,27 @@
        <el-table-column label="产品大类" prop="productCategory" />
        <el-table-column label="规格型号" prop="specificationModel" />
        <el-table-column label="单位" prop="unit" width="70" />
        <el-table-column label="供应商" prop="supplierName" width="100" />
        <!-- <el-table-column label="供应商" prop="supplierName" width="100" /> -->
        <el-table-column label="采购数量" prop="quantity" width="100" />
        <el-table-column label="待入库数量" prop="quantity0" width="100" />
        <el-table-column label="本次入库数量" prop="quantityStock" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.quantityStock" />
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.quantityStock" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column label="税率(%)" prop="taxRate" width="120" />
        <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150">
                    <template #default="scope">
                        <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" :disabled="operationType === 'edit'"/>
                    </template>
                </el-table-column>
        <el-table-column
          label="含税单价(元)"
          prop="taxInclusiveUnitPrice"
          label="总价(元)"
          :formatter="formattedNumber"
          width="150"
        />
        <el-table-column
          label="含税总价(元)"
          prop="taxInclusiveTotalPrice"
          :formatter="formattedNumber"
          width="150"
        />
        <el-table-column
          label="不含税总价(元)"
          prop="taxExclusiveTotalPrice"
          :formatter="formattedNumber"
          width="150"
        />
        >
        </el-table-column>
      </el-table>
    </el-form>
    <template #footer>
@@ -199,6 +191,18 @@
  return parseFloat(cellValue).toFixed(2);
};
// è®¡ç®—总价
const calculateTotalPrice = (row) => {
  const quantityStock = Number(row?.quantityStock ?? 0);
  const taxInclusiveUnitPrice = Number(row?.taxInclusiveUnitPrice ?? 0);
  if (Number.isFinite(quantityStock) && Number.isFinite(taxInclusiveUnitPrice)) {
    row.taxInclusiveTotalPrice = quantityStock * taxInclusiveUnitPrice;
  } else {
    row.taxInclusiveTotalPrice = 0;
  }
};
const fetchProductsByContract = async () => {
  if (!form.value.purchaseContractNumber) {
    proxy.$modal.msgWarning('请选择合同号')
@@ -217,6 +221,8 @@
    productList.value = productRes.data.map(item => ({
      ...item,
      quantityStock: 0,
      taxInclusiveUnitPrice: Number(item?.taxInclusiveUnitPrice ?? 0),
      taxInclusiveTotalPrice: 0,
      originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? 0),
    }))
  } catch (error) {
@@ -283,7 +289,10 @@
      nickName: userStore.nickName,
      details: selectedRows.value.map(product => ({
        id: product.id,
        inboundQuantity: Number(product.quantityStock)
        inboundQuantity: Number(product.quantityStock),
                unitPrice: Number(product.taxInclusiveUnitPrice),
        taxRate: Number(product.taxRate),
                taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice)
      })),
    };
    loading.value = true
@@ -365,6 +374,8 @@
      productList.value = res.data.map(item => ({
        ...item,
        quantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
        taxInclusiveUnitPrice: Number(item?.taxInclusiveUnitPrice ?? 0),
        taxInclusiveTotalPrice: Number(item?.quantityStock ?? 0) * Number(item?.taxInclusiveUnitPrice ?? 0),
        originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
      }))
      selectedRows.value = productList.value
@@ -385,3 +396,5 @@
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
ÎļþÃû´Ó src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue ÐÞ¸Ä
@@ -31,23 +31,6 @@
            <el-input v-model="scope.row.unit" placeholder="请输入单位" />
          </template>
        </el-table-column>
        <el-table-column label="供应商" prop="supplierName" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.supplierName" placeholder="请输入供应商" />
          </template>
        </el-table-column>
        <el-table-column label="物品类型" prop="itemType" width="140">
          <template #default="scope">
            <el-select v-model="scope.row.itemType" placeholder="请选择物品类型" style="width: 100%">
              <el-option
                v-for="item in itemTypeOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="入库数量" prop="inboundNum" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" />
@@ -65,44 +48,16 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="税率(%)" prop="taxRate" width="150">
        <el-table-column label="单价(元)" prop="unitPrice" width="150">
          <template #default="scope">
            <el-select v-model="scope.row.taxRate" placeholder="请选择税率" style="width: 100%" @change="() => calculateExclusivePrice(scope.row)">
              <el-option
                v-for="item in taxRateOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.unitPrice" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column
          label="含税单价(元)"
          prop="taxInclusiveUnitPrice"
          width="180"
           label="总价(元)"
           prop="totalPrice"
           width="150"
        >
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column
          label="含税总价(元)"
          prop="taxInclusiveTotalPrice"
          width="180"
        >
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveTotalPrice" @change="calculateExclusivePrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column
          label="不含税总价(元)"
          prop="taxExclusiveTotalPrice"
          width="180"
        >
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxExclusiveTotalPrice" />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="80" v-if="operationType === 'add'">
          <template #default="scope">
@@ -124,8 +79,7 @@
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
  addStockInCustom,
  updateStockInCustom,
    addStockInCustom, updateProduct
} from "@/api/inventoryManagement/stockIn.js";
const userStore = useUserStore()
@@ -199,9 +153,10 @@
    itemType: '',
    inboundNum: 0,
    inboundDate: '',
    quantityStock: 0,
    unitPrice: 0,
    totalPrice: 0,
    taxRate: null,
    taxInclusiveUnitPrice: 0,
    taxInclusiveTotalPrice: 0,
    taxExclusiveTotalPrice: 0,
  });
};
@@ -211,17 +166,18 @@
  productList.value.splice(index, 1);
};
// è®¡ç®—含税总价(根据单价和数量)
// è®¡ç®—总价(根据数量、单价和含税单价)
const calculateTotalPrice = (row) => {
  const unitPrice = Number(row.taxInclusiveUnitPrice || 0);
  // è®¡ç®—普通总价:inboundNum * unitPrice
  const quantity = Number(row.inboundNum || 0);
  row.taxInclusiveTotalPrice = unitPrice * quantity;
  const unitPrice = Number(row.unitPrice || 0);
  row.totalPrice = quantity * unitPrice;
  calculateExclusivePrice(row);
};
// è®¡ç®—不含税总价(根据含税总价和税率)
const calculateExclusivePrice = (row) => {
  const totalPrice = Number(row.taxInclusiveTotalPrice || 0);
  const totalPrice = Number(row.totalPrice || 0);
  const taxRate = Number(row.taxRate || 0);
  row.taxExclusiveTotalPrice = totalPrice / (1 + taxRate / 100);
};
@@ -240,10 +196,6 @@
      const product = productList.value[i];
      if (!product.productCategory || !product.specificationModel || !product.unit) {
        proxy.$modal.msgError(`第${i + 1}行产品数据未填写完整(产品大类、规格型号、单位为必填)`)
        return
      }
      if (!product.itemType) {
        proxy.$modal.msgError(`第${i + 1}行请选择物品类型`)
        return
      }
      if (!product.inboundDate) {
@@ -267,14 +219,13 @@
      itemType: product.itemType,
      inboundDate: formatDateTime(product.inboundDate, false),
      taxRate: Number(product.taxRate || 0),
      taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice || 0),
      taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice || 0),
      taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
            unitPrice: Number(product.unitPrice || 0),
    }));
    loading.value = true
    if (operationType.value === 'edit') {
      const editPayload = payloadList[0]
      await updateStockInCustom(editPayload)
      await updateProduct(editPayload)
    } else {
      await addStockInCustom(payloadList)
    }
@@ -336,8 +287,7 @@
      inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
      inboundDate: row?.inboundDate ?? row?.createTime ?? '',
      taxRate: Number(row?.taxRate ?? 0),
      taxInclusiveUnitPrice: Number(row?.taxInclusiveUnitPrice ?? 0),
      taxInclusiveTotalPrice: Number(row?.taxInclusiveTotalPrice ?? 0),
      unitPrice: Number(row?.unitPrice ?? 0),
      taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
    }]
  }
src/views/inventoryManagement/receiptManagement/index.vue
@@ -1,188 +1,538 @@
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab"
             @tab-change="handleTabChange">
      <el-tab-pane label="成品入库"
                   name="production">
    <div class="search_form">
      <div>
        <span class="search_title">供应商名称:</span>
        <el-input v-model="searchForm.supplierName" style="width: 240px" placeholder="请输入" @change="handleQuery"
          clearable prefix-icon="Search" />
        <span class="search_title ml10">入库日期:</span>
        <el-date-picker
          v-model="searchForm.timeStr"
            <el-date-picker v-model="searchForm.timeStr"
          type="date"
          placeholder="请选择日期"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          clearable
          @change="handleQuery"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
                            @change="handleQuery"/>
            <span class="search_title ml10">产品大类:</span>
            <el-input v-model="searchForm.productCategory"
                      style="width: 240px"
                      placeholder="请输入"
                      clearable/>
            <el-button type="primary"
                       @click="handleQuery"
                       style="margin-left: 10px">搜索
            </el-button>
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增入库</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="row => row.id" show-summary style="width: 100%"
        :summary-method="summarizeMainTable" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="入库时间" prop="createTime" width="100" show-overflow-tooltip />
        <el-table-column label="入库批次" prop="inboundBatches" width="160" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="240" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" width="100" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="200" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="70" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="inboundNum" width="90" show-overflow-tooltip />
        <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
        <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="税率(%)" prop="taxRate" width="80" show-overflow-tooltip />
        <el-table-column label="不含税总价" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="入库人" prop="createBy" width="80" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    @selection-change="handleSelectionChange"
                    :expand-row-keys="expandedRowKeys"
                    :row-key="row => row.id"
                    show-summary
                    style="width: 100%"
                    :summary-method="summarizeMainTable"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             type="selection"
                             width="55"/>
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60"/>
            <el-table-column label="入库时间"
                             prop="createTime"
                             show-overflow-tooltip/>
            <el-table-column label="销售合同号"
                             prop="salesContractNo"
                             width="180"
                             show-overflow-tooltip/>
            <el-table-column label="产品大类"
                             prop="productCategory"
                             show-overflow-tooltip/>
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip/>
            <el-table-column label="单位"
                             prop="unit"
                             width="70"
                             show-overflow-tooltip/>
            <el-table-column label="入库数量"
                             prop="inboundNum"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="单价(元)"
                             prop="unitPrice"
                             width="150"></el-table-column>
            <el-table-column label="总价(元)"
                             prop="totalPrice"
                             width="150"></el-table-column>
            <!-- <el-table-column fixed="right"
                             label="操作"
                             min-width="60"
                             align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);" :disabled="scope.row.createUser !== userStore.id">编辑</el-button>
                <el-button link
                           type="primary"
                           size="small"
                           @click="openForm('edit', scope.row, 'production');">编辑</el-button>
              </template>
            </el-table-column> -->
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="paginationChange"/>
        </div>
      </el-tab-pane>
      <el-tab-pane label="原料入库"
                   name="purchase">
        <div class="search_form">
          <div>
            <span class="search_title ml10">入库日期:</span>
            <el-date-picker v-model="searchForm.timeStr"
                            type="date"
                            placeholder="请选择日期"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                            @change="handleQuery"/>
            <span class="search_title ml10">产品大类:</span>
            <el-input v-model="searchForm.productCategory"
                      style="width: 240px"
                      placeholder="请输入"
                      clearable/>
            <el-button type="primary"
                       @click="handleQuery"
                       style="margin-left: 10px">搜索
            </el-button>
          </div>
          <div>
            <el-button type="primary"
                       @click="openForm('add', 'purchase')">新增入库
            </el-button>
            <el-button @click="handleOut">导出</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
          </div>
        </div>
        <div class="table_list">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    @selection-change="handleSelectionChange"
                    :expand-row-keys="expandedRowKeys"
                    :row-key="row => row.id"
                    show-summary
                    style="width: 100%"
                    :summary-method="summarizeMainTable"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             type="selection"
                             width="55"/>
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60"/>
            <el-table-column label="入库时间"
                             prop="createTime"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="采购合同号"
                             prop="purchaseContractNumber"
                             width="180"
                             show-overflow-tooltip/>
            <el-table-column label="供应商名称"
                             prop="supplierName"
                             width="240"
                             show-overflow-tooltip/>
            <el-table-column label="产品大类"
                             prop="productCategory"
                             show-overflow-tooltip/>
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip/>
            <el-table-column label="单位"
                             prop="unit"
                             width="70"
                             show-overflow-tooltip/>
            <el-table-column label="入库数量"
                             prop="inboundNum"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="含税单价(元)"
                             prop="taxInclusiveUnitPrice"
                             width="150"></el-table-column>
            <el-table-column label="含税总价(元)"
                             prop="taxInclusiveTotalPrice"
                             width="150"></el-table-column>
            <el-table-column label="入库人"
                             prop="createBy"
                             width="80"
                             show-overflow-tooltip/>
            <el-table-column fixed="right"
                             label="操作"
                             min-width="60"
                             align="center">
              <template #default="scope">
                <el-button link
                           :disabled="scope.row.type == 1"
                           type="primary"
                           size="small"
                           @click="openForm('edit', scope.row, 'purchase');">编辑
                </el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="paginationChange"/>
    </div>
    <form-dia ref="formDia" @close="handleQuery" @success="handleQuery"></form-dia>
      </el-tab-pane>
      <el-tab-pane label="生产入库"
                   name="product">
        <div class="search_form">
          <div>
            <span class="search_title ml10">入库日期:</span>
            <el-date-picker v-model="searchForm.timeStr"
                            type="date"
                            placeholder="请选择日期"
                            value-format="YYYY-MM-DD"
                            format="YYYY-MM-DD"
                            clearable
                            @change="handleQuery"/>
            <span class="search_title ml10">产品大类:</span>
            <el-input v-model="searchForm.productCategory"
                      style="width: 240px"
                      placeholder="请输入"
                      clearable/>
            <el-button type="primary"
                       @click="handleQuery"
                       style="margin-left: 10px">搜索
            </el-button>
          </div>
          <div>
            <el-button type="primary"
                       @click="openForm('add', 'purchase')">新增入库
            </el-button>
            <el-button @click="handleOut">导出</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
          </div>
        </div>
        <div class="table_list">
          <el-table :data="tableData"
                    border
                    v-loading="tableLoading"
                    @selection-change="handleSelectionChange"
                    :expand-row-keys="expandedRowKeys"
                    :row-key="row => row.id"
                    show-summary
                    style="width: 100%"
                    :summary-method="summarizeMainTable"
                    height="calc(100vh - 18.5em)">
            <el-table-column align="center"
                             type="selection"
                             width="55"/>
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60"/>
            <el-table-column label="入库时间"
                             prop="createTime"
                             width="100"
                             show-overflow-tooltip/>
            <el-table-column label="产品大类"
                             prop="productCategory"
                             show-overflow-tooltip/>
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip/>
            <el-table-column label="单位"
                             prop="unit"
                             width="220"
                             show-overflow-tooltip/>
            <el-table-column label="入库数量"
                             prop="inboundNum"
                             width="220"
                             show-overflow-tooltip/>
            <el-table-column label="入库人"
                             prop="createBy"
                             width="220"
                             show-overflow-tooltip/>
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="page.current"
                      :limit="page.size"
                      @pagination="pageProductChange"/>
        </div>
      </el-tab-pane>
    </el-tabs>
    <form-dia ref="formDia"
              @close="handleQuery"
              @success="handleQuery"></form-dia>
    <form-dia-product ref="formDiaProduct"
                      @close="handleQuery"
                      @success="handleQuery"></form-dia-product>
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue'
import pagination from "@/components/PIMTable/Pagination.vue";
import {
  ref,
  reactive,
  toRefs,
  onMounted,
  getCurrentInstance,
  nextTick,
} from "vue";
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import {
    getStockInPage,
  getStockInPageByProduction,
  getStockInPageByProductProduction,
    delStockIn,
} from "@/api/inventoryManagement/stockIn.js";
import FormDia from './components/formDia.vue'
import FormDia from "./components/formDia.vue";
import FormDiaProduct from "./components/formDiaProduct.vue";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
// èŽ·å–å½“å‰æ—¥æœŸ
function getCurrentDate() {
  return dayjs().format("YYYY-MM-DD");
}
const tableData = ref([])
const selectedRows = ref([])
const tableLoading = ref(false)
const formDia = ref()
const {proxy} = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const formDia = ref();
const formDiaProduct = ref();
const activeTab = ref("production"); // å½“前激活的 tab
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
});
const total = ref(0);
const data = reactive({
  searchForm: {
    supplierName: '',
    timeStr: '',
    supplierName: "",
    customerName: "",
    productCategory: "",
    timeStr: "",
  },
})
const { searchForm } = toRefs(data)
});
const {searchForm} = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = (obj) => {
  page.current = 1;
  getList();
};
const paginationChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
  getList();
};
const pageProductChange = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true
  const params = {
    ...page,
    supplierName: searchForm.value.supplierName,
    timeStr: searchForm.value.timeStr
  tableLoading.value = true;
  const params = {...page};
  // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹ä¼ é€’不同的查询参数
  if (activeTab.value === "production") {
    params.customerName = searchForm.value.customerName;
    params.timeStr = searchForm.value.timeStr;
  } else {
    params.supplierName = searchForm.value.supplierName;
    params.timeStr = searchForm.value.timeStr;
  }
  params.productCategory = searchForm.value.productCategory;
  if (activeTab.value === "product") {
    getStockInPageByProductProduction(params)
        .then(res => {
          tableLoading.value = false;
          tableData.value = res.data.records;
        });
  }else {
    // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„æŽ¥å£
    const apiCall =
        activeTab.value === "production"
            ? getStockInPageByProduction(params)
            : getStockInPage(params);
    apiCall
        .then(res => {
          tableLoading.value = false;
          tableData.value = res.data.records;
          // å‰ç«¯è®¡ç®—总价:总价 = unitPrice * inboundNum
          tableData.value = tableData.value.map(item => {
            // ä½¿ç”¨å…¥åº“数量计算总价
            const inboundNum = Number(item.inboundNum) || 0;
            const unitPrice = Number(item.unitPrice) || 0;
            const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0;
            // æ ¹æ®æ ‡ç­¾é¡µç±»åž‹è®¡ç®—不同的总价
            if (activeTab.value === "production") {
              // æˆå“åº“存:总价 = unitPrice * å…¥åº“数量
              item.totalPrice = (unitPrice * inboundNum).toFixed(2);
            } else {
              // åŽŸæ–™å’Œææ–™åº“å­˜ï¼šå«ç¨Žæ€»ä»· = taxInclusiveUnitPrice * å…¥åº“数量
              item.taxInclusiveTotalPrice = (
                  taxInclusiveUnitPrice * inboundNum
              ).toFixed(2);
  }
  
  getStockInPage(params).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
  }).catch(() => {
    tableLoading.value = false
            return item;
          });
          total.value = res.data.total;
  })
        .catch(() => {
          tableLoading.value = false;
        });
}
};
// åˆ‡æ¢ tab
const handleTabChange = tabName => {
  page.current = 1;
  // åˆ‡æ¢ tab æ—¶æ¸…空搜索条件
  searchForm.value.supplierName = "";
  searchForm.value.customerName = "";
  searchForm.value.timeStr = "";
  searchForm.value.productCategory = "";
  getList();
};
// æ‰“开弹框
const openForm = async (type, row) => {
const openForm = async (type, row, tabType) => {
  const currentTab = tabType || activeTab.value;
  await nextTick(() => {
    formDia.value?.openDialog(type, row)
  })
    if (currentTab === "production") {
      formDiaProduct.value?.openDialog(type, row);
    } else {
      formDia.value?.openDialog(type, row);
}
  });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection.filter(item => item.id)
}
const handleSelectionChange = selection => {
  selectedRows.value = selection.filter(item => item.id);
};
const expandedRowKeys = ref([])
const expandedRowKeys = ref([]);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice'])
}
const summarizeMainTable = param => {
  return proxy.summarizeTable(param, [
    "contractAmount",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm('是否确认导出?', '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    proxy.download("/stockin/export", {}, '入库台账.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  ElMessageBox.confirm("是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„导出接口
        let exportUrl = "/stockin/export";
        if (activeTab.value === "production") {
          exportUrl = "/stockin/exportOne";
}
        proxy.download(exportUrl, {}, "入库台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning('请选择数据')
    return
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
  const unauthorizedData = selectedRows.value.filter(item => item.createBy !== userStore.nickName)
  if (unauthorizedData.length > 0) {
    proxy.$modal.msgWarning("不可删除他人维护的数据")
    return
  }
  const ids = selectedRows.value.map(item => item.id)
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    delStockIn({ ids }).then(() => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    }).catch(() => {
      proxy.$modal.msgError("删除失败")
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
      .then(() => {
        // æ ¹æ®å½“前 tab ç±»åž‹é€‰æ‹©ä¸åŒçš„删除接口和type参数
        let deleteApi, deleteParams;
        if (activeTab.value === "production") {
          // æˆå“åˆ é™¤ï¼Œtypeä¼ 2
          deleteApi = delStockIn;
          deleteParams = {ids, type: 2};
        } else {
          // åŽŸæ–™åˆ é™¤ï¼Œtypeä¼ 1
          deleteApi = delStockIn;
          deleteParams = {ids, type: 1};
}
        deleteApi(deleteParams)
            .then(() => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("删除失败");
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList()
})
  getList();
});
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/stockManagement/index.vue
@@ -18,6 +18,7 @@
            <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
          </div>
          <div>
        <!-- <el-button type="primary" @click="openForm('add')">新增</el-button> -->
            <el-button @click="handleOut">导出</el-button>
            <el-button type="danger" plain @click="handleDelete">删除</el-button>
          </div>
@@ -34,9 +35,7 @@
            <el-table-column label="产品大类" prop="productCategory" width="100" show-overflow-tooltip />
            <el-table-column label="规格型号" prop="specificationModel" width="200" show-overflow-tooltip />
            <el-table-column label="单位" prop="unit" width="80" show-overflow-tooltip />
                        <el-table-column label="库存数量" prop="inboundNum" width="100" show-overflow-tooltip />
                        <el-table-column label="已出库数量" prop="totalInboundNum" width="100" show-overflow-tooltip />
                        <el-table-column label="待出库数量" prop="inboundNum0" width="100" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="inboundNum0" width="100" show-overflow-tooltip />
            <el-table-column label="库存预警数量" prop="warnNum" width="130" show-overflow-tooltip />
            <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
            <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
@@ -45,7 +44,7 @@
            <el-table-column label="入库人" prop="createBy" width="80" show-overflow-tooltip />
            <el-table-column fixed="right" label="操作" min-width="60" align="center">
              <template #default="scope">
                <el-button link type="primary" size="small" @click="openForm('edit', scope.row);" :disabled="scope.row.createUser !== userStore.id">编辑</el-button>
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">编辑</el-button>
              </template>
            </el-table-column>
          </el-table>
@@ -157,6 +156,7 @@
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { productTreeList,modelList } from "@/api/basicData/product.js"
import { getCurrentDate } from "@/utils/index.js";
import {
  getStockManagePage,
  delStockManage,
@@ -243,21 +243,14 @@
  page.size = obj.limit;
  getList()
}
const buildQueryParams = () => {
  return {
    ...page,
    supplierName: searchForm.value.supplierName,
    timeStr: searchForm.value.timeStr,
  }
}
const getList = () => {
  tableLoading.value = true
  const params = buildQueryParams()
  getStockManagePage(params).then(res => {
  getStockManagePage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ£€æŸ¥åº“å­˜
    // checkStockAndCreatePurchase();
  }).catch(() => {
    tableLoading.value = false
  })
@@ -365,8 +358,7 @@
    type: 'warning',
  }
  ).then(() => {
    const exportParams = buildQueryParams()
    proxy.download("/stockin/exportCopy", exportParams, '库存信息.xlsx')
    proxy.download("/stockin/exportCopy", {}, '库存信息.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
@@ -376,7 +368,7 @@
  let ids = []
  if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.createBy !== userStore.nickName);
        const unauthorizedData = selectedRows.value.filter(item => item.createUser !== userStore.id);
        if (unauthorizedData.length > 0) {
            proxy.$modal.msgWarning("不可删除他人维护的数据");
            return;
@@ -401,14 +393,6 @@
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, '0'); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}
onMounted(() => {
  getList()
src/views/procurementManagement/invoiceEntry/components/Modal.vue
@@ -1,5 +1,5 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" width="70%">
    <el-dialog :title="modalOptions.title" v-model="visible" width="70%" draggable>
    <el-form
      ref="formRef"
      :model="form"
@@ -14,6 +14,16 @@
          </el-form-item>
        </el-col>
        <el-col :span="12">
                    <el-form-item label="销售合同号:" prop="salesContractNo">
                        <el-input
                            v-model="form.salesContractNo"
                            placeholder="自动填充"
                            clearable
                            disabled
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
          <el-form-item label="供应商名称:" prop="supplierName">
            <el-input
              v-model="form.supplierName"
@@ -23,6 +33,16 @@
            />
          </el-form-item>
        </el-col>
<!--                <el-col :span="12">-->
<!--                    <el-form-item label="项目名称:" prop="projectName">-->
<!--                        <el-input-->
<!--                            v-model="form.projectName"-->
<!--                            placeholder="自动填充"-->
<!--                            clearable-->
<!--                            disabled-->
<!--                        />-->
<!--                    </el-form-item>-->
<!--                </el-col>-->
        <el-col :span="12">
          <el-form-item label="发票号:" prop="invoiceNumber">
            <el-input
@@ -148,6 +168,26 @@
            />
          </template>
        </el-table-column>
                <el-table-column
                    label="未来票数"
                    prop="futureTickets"
                    :formatter="formattedNumber"
                />
                <el-table-column
                    label="本次来票金额(元)"
                    prop="ticketsAmount"
                    :formatter="formattedNumber"
                />
                <el-table-column
                    label="未来票数"
                    prop="futureTickets"
                    :formatter="formattedNumber"
                />
                <el-table-column
                    label="未来票金额(元)"
                    prop="futureTicketsAmount"
                    :formatter="formattedNumber"
                />
      </el-table>
    </el-form>
    <template #footer>
@@ -187,6 +227,7 @@
  purchaseLedgerNo: undefined, // é‡‡è´­åˆåŒå·
  salesContractNo: undefined, // é”€å”®åˆåŒå·
  supplierName: undefined, // ä¾›åº”商名称
    projectName: undefined, // é¡¹ç›®åç§°
  invoiceNumber: undefined, // å‘票号
  invoiceAmount: undefined, // å‘票金额(元)
  issUerId: userStore.id, // å½•入人
@@ -359,6 +400,7 @@
              id: contractId, // æ˜Žç¡®è®¾ç½®åˆåŒID
              purchaseLedgerNo: contract.purchaseContractNumber, // æ·»åŠ é‡‡è´­åˆåŒå·
              supplierName: contract.supplierName, // æ·»åŠ ä¾›åº”å•†åç§°
                            projectName: contract.projectName // æ·»åŠ é¡¹ç›®åç§°
            });
          });
        }
@@ -366,17 +408,37 @@
      
      // è®¾ç½®è¡¨å•数据(使用第一个合同的基本信息,采购合同号留空)
      form.purchaseLedgerNo = ""; // é‡‡è´­åˆåŒå·ç•™ç©ºï¼Œå› ä¸ºä¼šåœ¨äº§å“è¡¨æ ¼ä¸­åˆ†åˆ«æ˜¾ç¤º
      form.invoiceAmount = 0;
      form.invoiceNumber = "";
      form.entryDate = dayjs().format("YYYY-MM-DD");
      form.enterDate = dayjs().format("YYYY-MM-DD");
      form.salesContractNo = results[0].data.salesContractNo;
            form.projectName = results[0].data.projectName;
      form.supplierName = results[0].data.supplierName;
      // ä¿ç•™å½•入人信息
      form.issUerId = userStore.id;
      form.issUer = userStore.nickName;
      
            // è®¾ç½®äº§å“æ•°æ®ï¼Œå¹¶åˆå§‹åŒ–开票数量和金额
            allProductData.forEach(item => {
                // æœ¬æ¬¡å¼€ç¥¨æ•°é»˜è®¤ä¸ºæ€»æ•°é‡
                item.ticketsNum = Number(item.quantity || 0);
                // æœ¬æ¬¡å¼€ç¥¨é‡‘额默认为含税总价
                item.ticketsAmount = Number(item.taxInclusiveTotalPrice || 0);
                // ä¿å­˜åŽŸå§‹æœªæ¥ç¥¨æ•°å’Œé‡‘é¢ï¼ˆç”¨äºŽè®¡ç®—ï¼‰
                item.tempFutureTickets = Number(item.quantity || 0);
                item.tempFutureTicketsAmount = Number(item.taxInclusiveTotalPrice || 0);
                // æœªæ¥ç¥¨æ•°å’Œé‡‘额初始为0(因为全部开票)
                item.futureTickets = 0;
                item.futureTicketsAmount = 0;
            });
      form.productData = allProductData;
            // è®¡ç®—发票金额:所有产品的含税总价之和
            const totalAmount = allProductData.reduce((sum, item) => {
                return sum + (Number(item.taxInclusiveTotalPrice) || 0);
            }, 0);
            form.invoiceAmount = totalAmount.toFixed(2);
      
      // å­˜å‚¨é€‰ä¸­çš„合同数据
      selectedContracts.value = selectedRows;
@@ -464,7 +526,7 @@
  if (Array.isArray(selectedRows) && selectedRows.length > 1) {
    modalOptions.title = `批量新增 (${selectedRows.length}条)`;
  } else {
    modalOptions.title = type == "add" ? "新增" : "编辑";
        modalOptions.title = type === "add" ? "新增" : "编辑";
  }
  
  // å¦‚果是单个操作,获取id
@@ -494,8 +556,10 @@
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      // ç»Ÿä¸€å°†æ‰€æœ‰åˆåŒçš„æ•°æ®æ”¾åœ¨ä¸€ä¸ªæ•°ç»„里,单个和批量都使用数组格式
      const submitData = selectedContracts.value.map(contract => {
            // å¦‚果是批量操作,将所有合同的数据放在一个数组里,只调用一次接口
            if (selectedContracts.value.length > 1) {
                // åˆ›å»ºåŒ…含所有合同数据的数组
                const batchData = selectedContracts.value.map(contract => {
        // ç­›é€‰å‡ºå±žäºŽå½“前合同的产品数据
        const contractProductData = form.productData.filter(item => 
          item.id === contract.id
@@ -517,28 +581,69 @@
          purchaseContractNumber: contract.purchaseContractNumber, // ä½¿ç”¨å®žé™…的采购合同号
          salesContractNo: contract.salesContractNo, // ä½¿ç”¨å®žé™…的销售合同号
          supplierName: contract.supplierName, // ä½¿ç”¨å®žé™…的供应商名称
                        projectName: contract.projectName, // ä½¿ç”¨å®žé™…的项目名称
          
          // äº§å“æ•°æ®
          productData: proxy.HaveJson(contractProductData),
          
          // æ‰¹é‡æ ‡è¯†
          isBatch: selectedContracts.value.length > 1,
                        isBatch: true,
          type: 4
        };
      });
      
      // ç»Ÿä¸€è°ƒç”¨æŽ¥å£ï¼Œä¼ é€’数组格式的数据
                // åªè°ƒç”¨ä¸€æ¬¡æŽ¥å£ï¼Œä¼ é€’包含所有合同数据的数组
      modalLoading.value = true;
      addOrUpdateRegistration(submitData).then((res) => {
                addOrUpdateRegistration(batchData).then((res) => {
        modalLoading.value = false;
        if (res.code === 200) {
          proxy.$modal.msgSuccess(selectedContracts.value.length > 1 ? "批量登记成功" : "登记成功");
                        proxy.$modal.msgSuccess("批量登记成功");
          closeAndRefresh();
        }
      }).catch(() => {
        modalLoading.value = false;
        proxy.$modal.msgError(selectedContracts.value.length > 1 ? "批量登记失败" : "登记失败");
                    proxy.$modal.msgError("批量登记失败");
      });
            } else {
                    // å•个合同提交逻辑 - ä»¥æ•°ç»„格式传递
                    const singleContract = selectedContracts.value[0];
                    const singleFormArray = [{
                        // åŸºç¡€è¡¨å•数据
                        invoiceNumber: form.invoiceNumber,
                        invoiceAmount: form.invoiceAmount,
                        entryDate: form.entryDate,
                        enterDate: form.enterDate,
                        issUerId: form.issUerId, // å½•入人id
                        issUer: form.issUer, // å½•入人
                        tempFileIds: form.tempFileIds,
                        // åˆåŒå®žé™…信息
                        purchaseLedgerId: singleContract.id, // ä½¿ç”¨id作为字段名,值为purchaseLedgerId
                        purchaseContractNumber: singleContract.purchaseContractNumber, // ä½¿ç”¨å®žé™…的采购合同号
                        salesContractNo: singleContract.salesContractNo, // ä½¿ç”¨å®žé™…的销售合同号
                        supplierName: singleContract.supplierName, // ä½¿ç”¨å®žé™…的供应商名称
                        projectName: singleContract.projectName, // ä½¿ç”¨å®žé™…的项目名称
                        // äº§å“æ•°æ®
                        productData: proxy.HaveJson(form.productData),
                        // æ‰¹é‡æ ‡è¯†
                        isBatch: false,
                        type: 4
                    }];
                    modalLoading.value = true;
                    addOrUpdateRegistration(singleFormArray).then((res) => {
                        modalLoading.value = false;
                        if (res.code === 200) {
                            proxy.$modal.msgSuccess("登记成功");
                            closeAndRefresh();
                        }
                    }).catch(() => {
                        modalLoading.value = false;
                        proxy.$modal.msgError("登记失败");
                    });
                }
    }
  });
};
src/views/procurementManagement/invoiceEntry/index.vue
@@ -40,7 +40,7 @@
        <div>
          <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
          <el-button type="primary" @click="handleAdd('add')">
            æ–°å¢žç™»è®°
            æ¥ç¥¨ç™»è®°
          </el-button>
<!--          <el-button type="danger" plain @click="handleDelete">删除</el-button>-->
        </div>
@@ -113,7 +113,6 @@
    gePurchaseListPage,
  {
    purchaseContractNumber: undefined,
    isInvoice:1,
  },
  [
    {
src/views/procurementManagement/invoiceEntry/indexOld.vue
ÎļþÒÑɾ³ý
src/views/procurementManagement/paymentEntry/index.vue
@@ -27,10 +27,10 @@
          </el-col>
          <el-col :span="4">
            <el-form-item style="float: right; margin-right: unset">
              <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
              <el-button type="primary" @click="openForm()">
              <el-button type="primary" @click="openForm('add')">
                æ–°å¢žä»˜æ¬¾
              </el-button>
              <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
<!--              <el-button type="danger" plain @click="handleDelete">-->
<!--                åˆ é™¤-->
<!--              </el-button>-->
@@ -104,7 +104,6 @@
                                    size="small"
                                    @click="changeEditType(scope.row)"
                                    v-if="!scope.row.editType"
                                    :disabled="scope.row.registrant !== userStore.nickName"
                                >编辑</el-button
                                >
                                <el-button
@@ -113,7 +112,6 @@
                                    size="small"
                                    @click="saveReceiptPayment(scope.row)"
                                    v-if="scope.row.editType"
                                    :disabled="scope.row.registrant !== userStore.nickName"
                                >保存</el-button
                                >
                                <el-button
@@ -121,7 +119,6 @@
                                    type="primary"
                                    size="small"
                                    @click="handleDelete(scope.row)"
                                    :disabled="scope.row.registrant !== userStore.nickName"
                                >删除</el-button
                                >
                            </template>
@@ -130,87 +127,110 @@
                </template>
            </PIMTable>
    </div>
    <el-dialog
    <FormDialog
      v-model="dialogFormVisible"
      title="新增付款登记"
      width="80%"
      title="新增付款页面"
      :width="'90%'"
      @close="closeDia"
      @confirm="submitForm"
      @cancel="closeDia"
    >
      <el-table :data="dialogTableData" border style="width: 100%" max-height="500px">
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="采购合同号" prop="purchaseContractNumber" show-overflow-tooltip width="200" />
        <el-table-column label="供应商名称" prop="supplierName" show-overflow-tooltip width="200" />
        <el-table-column label="发票号" prop="invoiceNumber" show-overflow-tooltip width="180" />
        <el-table-column label="发票金额(元)" prop="invoiceAmount" width="150">
          <template #default="{ row }">
            {{ row.invoiceAmount ? parseFloat(row.invoiceAmount).toFixed(2) : "0.00" }}
          </template>
        </el-table-column>
        <el-table-column label="待付款金额(元)" prop="unPaymentAmountTotal" width="150">
          <template #default="{ row }">
      <el-table
        v-if="forms.length"
        :data="forms"
        border
        style="width: 100%"
        size="small"
      >
        <el-table-column type="index" label="序号" width="50" align="center"/>
        <el-table-column label="采购合同号" prop="purchaseContractNumber" show-overflow-tooltip />
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" show-overflow-tooltip />
        <el-table-column
          label="产品大类"
          prop="productCategory"
          show-overflow-tooltip
          width="100"
        />
        <el-table-column
          label="规格型号"
          prop="specificationModel"
          show-overflow-tooltip
          width="150"
        />
        <el-table-column
          label="待付款金额(元)"
          prop="pendingTicketsTotal"
          show-overflow-tooltip
          width="170"
        >
          <template #default="{ row, column }">
            <el-text type="danger">
              {{ row.unPaymentAmountTotal ? parseFloat(row.unPaymentAmountTotal).toFixed(2) : "0.00" }}
              {{ formattedNumber(row, column, row.pendingTicketsTotal) }}
            </el-text>
          </template>
        </el-table-column>
        <el-table-column label="本次付款金额(元)" width="180">
          <template #default="scope">
          <template #default="{ row }">
            <el-input-number
              v-model="row.currentPaymentAmount"
              :step="0.01"
              :min="0"
              :max="Number(scope.row.unPaymentAmountTotal || 0)"
              style="width: 100%"
              :max="Number(row.pendingTicketsTotal || 0)"
              :precision="2"
              v-model="scope.row.currentPaymentAmount"
              style="width: 100%"
              placeholder="请输入"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="付款方式" width="150">
          <template #default="scope">
            <el-select v-model="scope.row.paymentMethod" placeholder="请选择" clearable style="width: 100%">
        <el-table-column label="付款方式" width="160">
          <template #default="{ row }">
            <el-select v-model="row.paymentMethod" placeholder="请选择" clearable>
              <el-option label="电汇" value="电汇" />
              <el-option label="承兑" value="承兑" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="付款日期" width="180">
          <template #default="scope">
        <el-table-column label="付款日期" width="170">
          <template #default="{ row }">
            <el-date-picker
              style="width: 100%"
              v-model="scope.row.paymentDate"
              v-model="row.paymentDate"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              type="date"
              placeholder="请选择"
              clearable
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="登记人" width="120">
          <template #default="scope">
            <el-input v-model="scope.row.registrant" placeholder="自动填充" disabled />
        <el-table-column label="登记人" width="140">
          <template #default="{ row }">
            <el-input v-model="row.registrant" disabled />
          </template>
        </el-table-column>
        <el-table-column label="登记日期" width="170">
          <template #default="{ row }">
            <el-input v-model="row.registrationtDate" />
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
      <div v-else class="empty-tip">请选择需要付款的记录</div>
    </FormDialog>
  </div>
</template>
<script setup>
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { ref, reactive, toRefs, getCurrentInstance, nextTick, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user.js";
import {
  byPurchaseId,
  paymentRegistrationAdd,
  paymentRegistrationDel,
  paymentRegistrationEdit,
  getTicketNo,
} from "@/api/procurementManagement/paymentEntry.js";
import {
    delPaymentRegistration,
@@ -219,6 +239,7 @@
    updatePaymentRegistration
} from "@/api/procurementManagement/procurementInvoiceLedger.js";
import useFormData from "@/hooks/useFormData";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance();
const tableColumn = ref([
@@ -230,10 +251,12 @@
  {
    label: "采购合同号",
    prop: "purchaseContractNumber",
    width:160
  },
  {
    label: "销售合同号",
    prop: "salesContractNo",
    width:160
  },
  {
    label: "供应商名称",
@@ -243,6 +266,7 @@
    {
        label: "付款状态",
        prop: "statusName",
    width:110,
        dataType: "tag",
        formatType: (params) => {
            if (params == '未完成付款') {
@@ -255,34 +279,30 @@
        },
    },
  {
    label: "发票号",
    prop: "invoiceNumber",
    width:200
        label: "产品大类",
        prop: "productCategory",
        showOverflowTooltip: true,
  },
  {
    label: "发票金额(元)",
    prop: "invoiceAmount",
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
        label: "规格型号",
        prop: "specificationModel",
        showOverflowTooltip: true,
  },
  {
    label: "已付款金额(元)",
    prop: "paymentAmountTotal",
    prop: "ticketsTotal",
    width: 120,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "待付款金额(元)",
    prop: "unPaymentAmountTotal",
    prop: "pendingTicketsTotal",
    width: 120,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "录入人",
    prop: "issUer",
  },
]);
const tableData = ref([]);
@@ -290,6 +310,7 @@
const selectedRows = ref([]);
const tableLoading = ref(false);
const childrenLoading = ref(false);
const forms = ref([]);
const userStore = useUserStore();
const page = reactive({
  current: 1,
@@ -297,9 +318,9 @@
    total: 0,
});
// å¼¹æ¡†æ•°æ®
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const dialogTableData = ref([]);
const data = reactive({
  searchForm: {
    supplierNameOrContractNo: "",
@@ -310,8 +331,6 @@
    purchaseLedgerId: "",
    salesContractNo: "",
    supplierName: "",
    invoiceNumber: "",
    invoiceAmount: "",
    taxRate: "",
    currentPaymentAmount: "",
    paymentMethod: "",
@@ -328,9 +347,6 @@
      { required: true, message: "请输入", trigger: "blur" },
    ],
    paymentMethod: [{ required: true, message: "请选择", trigger: "change" }],
    invoiceNumber: [
      { required: true, message: "请选择采购合同号", trigger: "change" },
    ],
  },
});
const { form, rules } = toRefs(data);
@@ -343,11 +359,16 @@
    if (!normalized) return 'info';
    return normalized === '未完成付款' ? 'danger' : 'success';
};
const formattedNumber = (row, column, cellValue) => {
  const val = Number(cellValue ?? 0);
  return Number.isFinite(val) ? val.toFixed(2) : "0.00";
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable1 = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceAmount", "paymentAmountTotal", "unPaymentAmountTotal"],
    ["ticketsTotal", "pendingTicketsTotal"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
@@ -376,8 +397,8 @@
  tableLoading.value = true;
  invoiceListPage({ ...searchForm, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.records;
        page.total = res.total;
    tableData.value = res.data.records;
        page.total = res.data.total;
        if (expandedRowKeys.value.length > 0) {
            const arr = []
            const index = tableData.value.findIndex(item => item.id === expandedRowKeys.value[0]);
@@ -423,8 +444,7 @@
        currentPaymentAmount: row.currentPaymentAmount,
        paymentMethod: row.paymentMethod,
    };
    // å­è¡¨ç¼–辑保存按数组提交(与批量登记保持一致)
    updatePaymentRegistration([updateData]).then(() => {
    updatePaymentRegistration(updateData).then((res) => {
        row.editType = !row.editType;
        getList();
        proxy.$modal.msgSuccess("提交成功");
@@ -435,117 +455,75 @@
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = () => {
  // è‡³å°‘选择一条数据
const openForm = (type, row) => {
  if (selectedRows.value.length === 0) {
    proxy.$modal?.msgError ? proxy.$modal.msgError("请选择数据") : proxy.$message.error("请选择数据");
    proxy.$modal.msgError("请选择至少一条数据");
    return;
  }
  // æ ¡éªŒæ˜¯å¦ä¸ºç›¸åŒä¾›åº”商名称
  const firstSupplierName = selectedRows.value[0].supplierName;
  const isSameSupplier = selectedRows.value.every(
    (item) => item.supplierName === firstSupplierName
  );
  if (!isSameSupplier) {
    proxy.$modal?.msgError
      ? proxy.$modal.msgError("请选择相同供应商名称的数据进行付款登记")
      : proxy.$message.error("请选择相同供应商名称的数据进行付款登记");
    return;
  }
  // è¿‡æ»¤å‡ºæœ‰å¾…付款金额的记录
  const validRows = selectedRows.value.filter(
    (item) => Number(item.unPaymentAmountTotal) > 0
  );
  const validRows = selectedRows.value.filter((item) => Number(item.pendingTicketsTotal || 0) !== 0);
  if (validRows.length === 0) {
    proxy.$modal?.msgWarning
      ? proxy.$modal.msgWarning("所选数据均无需再付款")
      : proxy.$message.warning("所选数据均无需再付款");
    proxy.$modal.msgWarning("所选记录均无需付款");
    return;
  }
  const today = getCurrentDate();
  dialogTableData.value = validRows.map((row) => {
    return {
      ...row,
      // åŽç«¯å…³è”“来票台账/登记单”的字段(原逻辑:ticketRegistrationId = row.id)
      ticketRegistrationId: row.id,
      id: null,
      currentPaymentAmount: row.unPaymentAmountTotal || "",
  forms.value = validRows.map((row) => ({
    purchaseContractNumber: row.purchaseContractNumber || "",
    salesContractNo: row.salesContractNo || "",
    supplierName: row.supplierName || "",
    supplierId: row.supplierId,
    productCategory: row.productCategory || "",
    specificationModel: row.specificationModel || "",
    pendingTicketsTotal: Number(row.pendingTicketsTotal || 0),
    currentPaymentAmount: "",
      paymentMethod: "",
      paymentDate: today,
      registrationtDate: today,
      registrant: userStore.nickName || userStore.name,
    };
  });
    paymentDate: "",
    registrant: userStore.nickName,
    registrationtDate: getCurrentDate(),
    ticketRegistrationId: row.id,
    purchaseLedgerId: row.salesLedgerId,
    salesLedgerProductId: row.id,
  }));
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  // æ ¡éªŒè¡¨æ ¼æ•°æ®
  const invalidRows = dialogTableData.value.filter((row) => {
    return (
      !row.currentPaymentAmount ||
      Number(row.currentPaymentAmount) <= 0 ||
      !row.paymentMethod ||
      !row.paymentDate
  if (forms.value.length === 0) {
    proxy.$modal.msgError("请选择付款记录");
    return;
  }
  for (let i = 0; i < forms.value.length; i++) {
    const item = forms.value[i];
    const pendingAmount = Number(item.pendingTicketsTotal || 0);
    const currentAmount = Number(item.currentPaymentAmount);
    if (!item.currentPaymentAmount && item.currentPaymentAmount !== 0) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·å¡«å†™ä»˜æ¬¾é‡‘额`);
      return;
    }
    if (currentAmount > pendingAmount) {
      proxy.$modal.msgError(
        `第 ${i + 1} æ¡ï¼šä»˜æ¬¾é‡‘额不能超过待付款金额(待付款:${pendingAmount.toFixed(
          2
        )})`
    );
  });
  if (invalidRows.length > 0) {
    proxy.$modal?.msgError
      ? proxy.$modal.msgError("请完善所有必填项:付款金额、付款方式、付款日期")
      : proxy.$message.error("请完善所有必填项:付款金额、付款方式、付款日期");
    return;
  }
  // æ ¡éªŒä»˜æ¬¾é‡‘额不能超过待付款金额
  const exceedRows = dialogTableData.value.filter((row) => {
    return Number(row.currentPaymentAmount) > Number(row.unPaymentAmountTotal);
  });
  if (exceedRows.length > 0) {
    proxy.$modal?.msgError
      ? proxy.$modal.msgError("付款金额不能超过待付款金额")
      : proxy.$message.error("付款金额不能超过待付款金额");
    if (!item.paymentMethod) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·é€‰æ‹©ä»˜æ¬¾æ–¹å¼`);
    return;
  }
  // ç»„装数组批量提交(如果后端不支持数组,会走兜底循环提交)
  const submitDataList = dialogTableData.value.map((row) => {
    return {
      ticketRegistrationId: row.ticketRegistrationId,
      purchaseLedgerId: row.purchaseLedgerId,
      purchaseContractNumber: row.purchaseContractNumber,
      salesContractNo: row.salesContractNo,
      supplierName: row.supplierName,
      invoiceNumber: row.invoiceNumber,
      invoiceAmount: row.invoiceAmount,
      taxRate: row.taxRate,
      currentPaymentAmount: row.currentPaymentAmount,
      paymentMethod: row.paymentMethod,
      paymentDate: row.paymentDate,
      registrant: row.registrant,
      registrationtDate: row.registrationtDate,
      id: null,
    };
  });
  paymentRegistrationAdd(submitDataList)
    .then(() => {
      proxy.$modal?.msgSuccess
        ? proxy.$modal.msgSuccess("提交成功")
        : proxy.$message.success("提交成功");
    if (!item.paymentDate) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·é€‰æ‹©ä»˜æ¬¾æ—¥æœŸ`);
      return;
    }
  }
  paymentRegistrationAdd(forms.value).then(() => {
    proxy.$modal.msgSuccess("提交成功");
      closeDia();
      getList();
    })
    .catch((e) => {
      console.error("提交失败:", e);
    });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogTableData.value = [];
  forms.value = [];
  dialogFormVisible.value = false;
};
// åˆ é™¤
@@ -557,7 +535,7 @@
  })
    .then(() => {
      tableLoading.value = true;
            delPaymentRegistration(row.id)
            delPaymentRegistration([row.id])
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
@@ -570,15 +548,6 @@
      proxy.$modal.msg("已取消");
    });
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// å¯¼å‡º
const handleExport = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -606,4 +575,9 @@
::v-deep(.el-checkbox__label) {
  font-weight: bold;
}
.empty-tip {
  text-align: center;
  padding: 20px 0;
  color: #909399;
}
</style>
src/views/procurementManagement/paymentHistory/index.vue
@@ -43,6 +43,13 @@
          æœç´¢
        </el-button>
        <el-button @click="handleExport">导出</el-button>
        <el-button
          type="danger"
          :disabled="selectedRows.length === 0"
          @click="handleBatchDelete"
        >
          æ‰¹é‡åˆ é™¤ ({{ selectedRows.length }})
        </el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
@@ -58,7 +65,18 @@
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      ></PIMTable>
      >
        <template #operation="{ row }">
          <el-button
            type="primary"
            link
            size="small"
            @click="handleDelete(row)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </PIMTable>
    </div>
  </div>
</template>
@@ -66,7 +84,9 @@
<script setup>
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import { paymentHistoryListPage } from "@/api/procurementManagement/paymentEntry.js";
import {delPaymentRegistration } from "@/api/procurementManagement/procurementInvoiceLedger.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
@@ -104,6 +124,13 @@
  {
    label: "登记日期",
    prop: "registrationtDate",
  },
  {
    label: "操作",
    dataType: "slot",
    slot: "operation",
    width: 100,
    align: "center",
  },
]);
const tableData = ref([]);
@@ -170,6 +197,62 @@
  getList();
};
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
      delPaymentRegistration([row.id])
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.value.length} æ¡æ•°æ®å—?`,
    "删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(() => {
      tableLoading.value = true;
      const ids = selectedRows.value.map((item) => item.id);
      delPaymentRegistration(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          selectedRows.value = [];
          getList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// å¯¼å‡º
const handleExport = () => {
  const { paymentDate, ...rest } = searchForm;
src/views/procurementManagement/paymentLedger/index.vue
@@ -43,7 +43,7 @@
            />
            <el-table-column label="供应商名称" prop="supplierName" />
            <el-table-column
              label="发票金额(元)"
              label="合同金额(元)"
              prop="invoiceAmount"
              show-overflow-tooltip
              :formatter="formattedNumber"
@@ -83,6 +83,7 @@
            :column="tableColumnSon"
            :tableData="originalTableDataSon"
            :isSelection="false"
            :isShowPagination="false"
            :tableLoading="tableLoadingSon"
            :isShowSummary="isShowSummarySon"
            :summaryMethod="summarizeMainTable1"
@@ -94,14 +95,6 @@
              </el-text>
            </template>
          </PIMTable>
          <pagination
            v-show="sonTotal > 0"
            :total="sonTotal"
            @pagination="sonPaginationSearch"
            :layout="page.layout"
            :page="sonPage.current"
            :limit="sonPage.size"
          />
        </div>
      </el-col>
    </el-row>
@@ -117,25 +110,6 @@
} from "@/api/procurementManagement/paymentLedger.js";
import Pagination from "../../../components/PIMTable/Pagination.vue";
const tableColumn = ref([
  {
    label: "供应商名称",
    prop: "supplierName",
    width:240
  },
  {
    label: "发票金额(元)",
    prop: "invoiceAmount",
  },
  {
    label: "付款金额(元)",
    prop: "paymentAmount",
  },
  {
    label: "应付金额(元)",
    prop: "payableAmount",
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const data = reactive({
@@ -164,11 +138,16 @@
const tableColumnSon = ref([
  {
    label: "发生日期",
    prop: "happenTime",
    prop: "paymentDate",
        width: 110,
  },
  {
    label: "发票金额(元)",
    label: "采购合同号",
    prop: "purchaseContractNumber",
        width: 150,
  },
  {
    label: "合同金额(元)",
    prop: "invoiceAmount",
        width: 200,
    formatData: (params) => {
@@ -177,7 +156,7 @@
  },
  {
    label: "付款金额(元)",
    prop: "currentPaymentAmount",
    prop: "paymentAmount",
        width: 200,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
@@ -214,7 +193,7 @@
const summarizeMainTable1 = (param) => {
  let summarizeTable = proxy.summarizeTable(
    param,
    ["invoiceAmount", "currentPaymentAmount"],
    ["invoiceAmount", "paymentAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
@@ -245,8 +224,6 @@
  paymentLedgerList({
    ...searchForm.value,
    ...page,
    detailPageNum: detailPageNum.value, // æ–°å¢ž
    detailPageSize: detailPageSize.value, // æ–°å¢ž
  }).then((res) => {
    let result = res.data;
    tableLoading.value = false;
@@ -261,7 +238,7 @@
const getPaymenRecordtList = (supplierId) => {
  tableLoadingSon.value = true;
  paymentRecordList(supplierId)
  paymentRecordList({supplierId: supplierId})
    .then((res) => {
      tableLoadingSon.value = false;
      tableDataSon.value = res.data;
src/views/procurementManagement/procurementInvoiceLedger/index.vue
@@ -82,14 +82,12 @@
            type="primary"
            text
            @click="openEdit(row)"
                        :disabled="row.issUerId !== userStore.id"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="primary"
            text
                        :disabled="row.issUerId !== userStore.id"
            @click="handleDelete(row)"
          >
            åˆ é™¤
src/views/procurementManagement/procurementLedger/index.vue
@@ -275,14 +275,6 @@
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否开票" prop="isInvoice">
              <el-select v-model="form.isInvoice" placeholder="请选择" clearable>
                <el-option label="是" :value="1" />
                <el-option label="否" :value="2" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-form-item label="产品信息:" prop="entryDate">
@@ -718,7 +710,6 @@
    supplierId: "",
    paymentMethod: "",
        executionDate: "",
    isInvoice: "",
  },
  rules: {
    purchaseContractNumber: [
@@ -728,7 +719,6 @@
    supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
        entryDate: [{ required: true, message: "请选择", trigger: "change" }],
        executionDate: [{ required: true, message: "请选择", trigger: "change" }],
    isInvoice: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const {  form, rules } = toRefs(data);
src/views/productManagement/productIdentifier/index.vue
@@ -2,158 +2,230 @@
  <div class="app-container">
    <el-card class="box-card">
             <!-- æœç´¢åŒºåŸŸ -->
       <el-row :gutter="20" class="search-row">
      <el-row :gutter="20"
              class="search-row">
         <el-col :span="6">
           <el-input
             v-model="searchForm.productName"
          <el-input v-model="searchForm.productName"
             placeholder="请输入产品名称"
             clearable
             @keyup.enter="handleSearch"
           >
                    @keyup.enter="handleSearch">
             <template #prefix>
               <el-icon><Search /></el-icon>
              <el-icon>
                <Search />
              </el-icon>
             </template>
           </el-input>
         </el-col>
         <el-col :span="6">
           <el-select v-model="searchForm.identifierType" placeholder="请选择标识类型" clearable>
             <el-option label="二维码" value="二维码"></el-option>
             <el-option label="防伪码" value="防伪码"></el-option>
          <el-select v-model="searchForm.identifierType"
                     placeholder="请选择标识类型"
                     clearable>
            <el-option label="二维码"
                       value="二维码"></el-option>
            <el-option label="防伪码"
                       value="防伪码"></el-option>
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
             <el-option label="已生成" value="已生成"></el-option>
             <el-option label="已分配" value="已分配"></el-option>
             <el-option label="已使用" value="已使用"></el-option>
             <el-option label="已作废" value="已作废"></el-option>
          <el-select v-model="searchForm.status"
                     placeholder="请选择状态"
                     clearable>
            <el-option label="已生成"
                       value="已生成"></el-option>
            <el-option label="已分配"
                       value="已分配"></el-option>
            <el-option label="已使用"
                       value="已使用"></el-option>
            <el-option label="已作废"
                       value="已作废"></el-option>
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button type="primary"
                     @click="handleSearch">搜索</el-button>
           <el-button @click="resetSearch">重置</el-button>
           <el-button style="float: right;" type="primary" @click="handleAdd">
          <el-button style="float: right;"
                     type="primary"
                     @click="handleAdd">
             æ–°å¢žæ ‡è¯†
           </el-button>
         </el-col>
       </el-row>
      <!-- äº§å“æ ‡è¯†åˆ—表 -->
      <el-table
        :data="filteredList"
      <el-table :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
      >
        <el-table-column prop="id" label="ID" width="80" align="center"/>
        <el-table-column prop="productName" label="产品名称" width="150" />
        <el-table-column prop="productCode" label="产品编码" width="120" />
        <el-table-column prop="batchNo" label="批次号" width="120" />
        <el-table-column prop="identifierType" label="标识类型" width="100">
                height="calc(100vh - 22em)">
        <el-table-column prop="id"
                         label="ID"
                         width="80"
                         align="center" />
        <el-table-column prop="productName"
                         label="产品名称"
                         width="150" />
        <el-table-column prop="productCode"
                         label="产品编码"
                         width="120" />
        <el-table-column prop="batchNo"
                         label="批次号"
                         width="120" />
        <el-table-column prop="identifierType"
                         label="标识类型"
                         width="100">
          <template #default="scope">
            <el-tag :type="getIdentifierTypeType(scope.row.identifierType)">
              {{ scope.row.identifierType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="identifierCode" label="标识码" />
        <el-table-column prop="status" label="状态" width="100">
        <el-table-column prop="identifierCode"
                         label="标识码" />
        <el-table-column prop="status"
                         label="状态"
                         width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="generateTime" label="生成时间" width="160" />
        <el-table-column label="操作" fixed="right" align="center" width="280">
        <el-table-column prop="generateTime"
                         label="生成时间"
                         width="160" />
        <el-table-column label="操作"
                         fixed="right"
                         align="center"
                         width="280">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="success" @click="generateQRCode(scope.row)">生成二维码</el-button>
            <el-button link type="primary" @click="handleExport(scope.row)">导出</el-button>
            <el-button link type="primary" @click="handleReassign(scope.row)" v-if="scope.row.status === '已分配'">重新分配</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
            <el-button link
                       type="primary"
                       @click="handleView(scope.row)">查看</el-button>
            <el-button link
                       type="primary"
                       @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link
                       type="success"
                       @click="generateQRCode(scope.row)">生成二维码</el-button>
            <el-button link
                       type="primary"
                       @click="handleExport(scope.row)">导出</el-button>
            <el-button link
                       type="primary"
                       @click="handleReassign(scope.row)"
                       v-if="scope.row.status === '已分配'">重新分配</el-button>
            <el-button link
                       type="danger"
                       @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
      <pagination :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
      />
                  @pagination="handleCurrentChange" />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
    <el-dialog v-model="dialogVisible"
               :title="dialogTitle"
               width="700px">
      <el-form :model="form"
               :rules="rules"
               ref="formRef"
               label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="产品名称" prop="productName">
              <el-input v-model="form.productName" placeholder="请输入产品名称"></el-input>
            <el-form-item label="产品名称"
                          prop="productName">
              <el-input v-model="form.productName"
                        placeholder="请输入产品名称"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品编码" prop="productCode">
              <el-input v-model="form.productCode" placeholder="请输入产品编码"></el-input>
            <el-form-item label="产品编码"
                          prop="productCode">
              <el-input v-model="form.productCode"
                        placeholder="请输入产品编码"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="批次号" prop="batchNo">
              <el-input v-model="form.batchNo" placeholder="请输入批次号"></el-input>
            <el-form-item label="批次号"
                          prop="batchNo">
              <el-input v-model="form.batchNo"
                        placeholder="请输入批次号"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="标识类型" prop="identifierType">
              <el-select v-model="form.identifierType" placeholder="请选择标识类型" style="width: 100%">
                <el-option label="二维码" value="二维码"></el-option>
                <el-option label="防伪码" value="防伪码"></el-option>
            <el-form-item label="标识类型"
                          prop="identifierType">
              <el-select v-model="form.identifierType"
                         placeholder="请选择标识类型"
                         style="width: 100%">
                <el-option label="二维码"
                           value="二维码"></el-option>
                <el-option label="防伪码"
                           value="防伪码"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="生成数量" prop="quantity">
              <el-input-number v-model="form.quantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
            <el-form-item label="生成数量"
                          prop="quantity">
              <el-input-number v-model="form.quantity"
                               :min="1"
                               :max="10000"
                               style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
                <el-option label="已生成" value="已生成"></el-option>
                <el-option label="已分配" value="已分配"></el-option>
                <el-option label="已使用" value="已使用"></el-option>
                <el-option label="已作废" value="已作废"></el-option>
            <el-form-item label="状态"
                          prop="status">
              <el-select v-model="form.status"
                         placeholder="请选择状态"
                         style="width: 100%">
                <el-option label="已生成"
                           value="已生成"></el-option>
                <el-option label="已分配"
                           value="已分配"></el-option>
                <el-option label="已使用"
                           value="已使用"></el-option>
                <el-option label="已作废"
                           value="已作废"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
            <el-form-item label="备注"
                          prop="remark">
              <el-input type="textarea"
                        v-model="form.remark"
                        placeholder="请输入备注信息"
                        rows="3"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="handleSubmit">ç¡® å®š</el-button>
          <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="handleSubmit">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ ‡è¯†ç”Ÿæˆå¯¹è¯æ¡† -->
    <el-dialog v-model="generateDialogVisible" title="标识生成" width="500px">
    <el-dialog v-model="generateDialogVisible"
               title="标识生成"
               width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
@@ -167,30 +239,45 @@
        <el-form-item label="标识类型">
          <span>{{ currentProduct.identifierType }}</span>
        </el-form-item>
        <el-form-item label="生成数量" prop="generateQuantity">
          <el-input-number v-model="generateQuantity" :min="1" :max="10000" style="width: 100%"></el-input-number>
        <el-form-item label="生成数量"
                      prop="generateQuantity">
          <el-input-number v-model="generateQuantity"
                           :min="1"
                           :max="10000"
                           style="width: 100%"></el-input-number>
        </el-form-item>
        <el-form-item label="编码规则" prop="codeRule">
          <el-select v-model="codeRule" placeholder="请选择编码规则" style="width: 100%">
            <el-option label="产品编码+批次号+序号" value="产品编码+批次号+序号"></el-option>
            <el-option label="时间戳+随机数" value="时间戳+随机数"></el-option>
            <el-option label="自定义规则" value="自定义规则"></el-option>
        <el-form-item label="编码规则"
                      prop="codeRule">
          <el-select v-model="codeRule"
                     placeholder="请选择编码规则"
                     style="width: 100%">
            <el-option label="产品编码+批次号+序号"
                       value="产品编码+批次号+序号"></el-option>
            <el-option label="时间戳+随机数"
                       value="时间戳+随机数"></el-option>
            <el-option label="自定义规则"
                       value="自定义规则"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="自定义前缀" prop="customPrefix" v-if="codeRule === '自定义规则'">
          <el-input v-model="customPrefix" placeholder="请输入自定义前缀"></el-input>
        <el-form-item label="自定义前缀"
                      prop="customPrefix"
                      v-if="codeRule === '自定义规则'">
          <el-input v-model="customPrefix"
                    placeholder="请输入自定义前缀"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="generateIdentifiers">生 æˆ</el-button>
          <el-button @click="generateDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="generateIdentifiers">生 æˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- é‡æ–°åˆ†é…å¯¹è¯æ¡† -->
    <el-dialog v-model="reassignDialogVisible" title="重新分配标识" width="500px">
    <el-dialog v-model="reassignDialogVisible"
               title="重新分配标识"
               width="500px">
      <el-form label-width="100px">
        <el-form-item label="产品名称">
          <span>{{ currentProduct.productName }}</span>
@@ -198,26 +285,38 @@
        <el-form-item label="标识码">
          <span>{{ currentProduct.identifierCode }}</span>
        </el-form-item>
        <el-form-item label="新批次号" prop="newBatchNo">
          <el-input v-model="newBatchNo" placeholder="请输入新批次号"></el-input>
        <el-form-item label="新批次号"
                      prop="newBatchNo">
          <el-input v-model="newBatchNo"
                    placeholder="请输入新批次号"></el-input>
        </el-form-item>
        <el-form-item label="分配原因" prop="reassignReason">
          <el-input type="textarea" v-model="reassignReason" rows="3" placeholder="请输入重新分配原因"></el-input>
        <el-form-item label="分配原因"
                      prop="reassignReason">
          <el-input type="textarea"
                    v-model="reassignReason"
                    rows="3"
                    placeholder="请输入重新分配原因"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="saveReassign">ç¡® å®š</el-button>
          <el-button @click="reassignDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="saveReassign">ç¡® å®š</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äºŒç»´ç é¢„览对话框 -->
    <el-dialog v-model="qrCodeDialogVisible" title="二维码预览" width="500px" center>
    <el-dialog v-model="qrCodeDialogVisible"
               title="二维码预览"
               width="500px"
               center>
      <div class="qr-preview-container">
        <div v-if="qrCodeUrl" class="qr-image-container">
          <img :src="qrCodeUrl" alt="二维码" class="qr-image" />
        <div v-if="qrCodeUrl"
             class="qr-image-container">
          <img :src="qrCodeUrl"
               alt="二维码"
               class="qr-image" />
          <div class="qr-info">
            <p><strong>产品名称:</strong>{{ currentQRProduct.productName }}</p>
            <p><strong>产品编码:</strong>{{ currentQRProduct.productCode }}</p>
@@ -226,29 +325,27 @@
            <p><strong>标识类型:</strong>{{ currentQRProduct.identifierType }}</p>
          </div>
        </div>
        <div v-else class="qr-loading">
          <el-icon class="is-loading"><Loading /></el-icon>
        <div v-else
             class="qr-loading">
          <el-icon class="is-loading">
            <Loading />
          </el-icon>
          <p>正在生成二维码...</p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="qrCodeDialogVisible = false">关闭</el-button>
          <el-button
            v-if="qrCodeUrl"
          <el-button v-if="qrCodeUrl"
            type="primary" 
            @click="copyQRContent" 
            icon="CopyDocument"
          >
                     icon="CopyDocument">
            å¤åˆ¶å†…容
          </el-button>
          <el-button
            v-if="qrCodeUrl"
          <el-button v-if="qrCodeUrl"
            type="success" 
            @click="downloadQRCode" 
            icon="Download"
          >
                     icon="Download">
            ä¸‹è½½äºŒç»´ç 
          </el-button>
        </div>
@@ -258,219 +355,225 @@
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Loading, Download } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
import QRCode from 'qrcode'
  import { ref, reactive, computed } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import { Plus, Search, Loading, Download } from "@element-plus/icons-vue";
  import Pagination from "@/components/PIMTable/Pagination.vue";
  import QRCode from "qrcode";
// å“åº”式数据
const loading = ref(false)
  const loading = ref(false);
const searchForm = reactive({
  productName: '',
  identifierType: '',
  status: ''
})
    productName: "",
    identifierType: "",
    status: "",
  });
const identifierList = ref([
  {
    id: 1,
    productName: '工业传感器A型',
    productCode: 'SENSOR001',
    batchNo: 'B202312001',
    identifierType: '二维码',
    identifierCode: 'QR_SENSOR001_B202312001_001',
    status: '已分配',
    generateTime: '2023-12-01 10:00:00',
    remark: '重要产品标识'
      productName: "工业传感器A型",
      productCode: "SENSOR001",
      batchNo: "B202312001",
      identifierType: "二维码",
      identifierCode: "QR_SENSOR001_B202312001_001",
      status: "已分配",
      generateTime: "2023-12-01 10:00:00",
      remark: "重要产品标识",
  },
  {
    id: 2,
    productName: '控制面板B型',
    productCode: 'PANEL002',
    batchNo: 'B202312002',
    identifierType: '防伪码',
    identifierCode: 'SEC_PANEL002_B202312002_001',
    status: '已生成',
    generateTime: '2023-12-02 14:30:00',
    remark: '常规产品标识'
      productName: "控制面板B型",
      productCode: "PANEL002",
      batchNo: "B202312002",
      identifierType: "防伪码",
      identifierCode: "SEC_PANEL002_B202312002_001",
      status: "已生成",
      generateTime: "2023-12-02 14:30:00",
      remark: "常规产品标识",
  },
  {
    id: 3,
    productName: '数据采集器C型',
    productCode: 'COLLECTOR003',
    batchNo: 'B202312003',
    identifierType: '防伪码',
    identifierCode: 'SEC_COLLECTOR003_B202312003_001',
    status: '已使用',
    generateTime: '2023-12-03 09:15:00',
    remark: '测试产品标识'
  }
])
      productName: "数据采集器C型",
      productCode: "COLLECTOR003",
      batchNo: "B202312003",
      identifierType: "防伪码",
      identifierCode: "SEC_COLLECTOR003_B202312003_001",
      status: "已使用",
      generateTime: "2023-12-03 09:15:00",
      remark: "测试产品标识",
    },
  ]);
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
})
    pageSize: 10,
  });
const dialogVisible = ref(false)
const dialogTitle = ref('新增标识')
  const dialogVisible = ref(false);
  const dialogTitle = ref("新增标识");
const form = reactive({
  productName: '',
  productCode: '',
  batchNo: '',
  identifierType: '',
    productName: "",
    productCode: "",
    batchNo: "",
    identifierType: "",
  quantity: 1,
  status: '已生成',
  remark: ''
})
    status: "已生成",
    remark: "",
  });
const rules = {
  productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
  productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
  batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }],
  identifierType: [{ required: true, message: '请选择标识类型', trigger: 'change' }],
  quantity: [{ required: true, message: '请输入生成数量', trigger: 'blur' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
    productName: [{ required: true, message: "请输入产品名称", trigger: "blur" }],
    productCode: [{ required: true, message: "请输入产品编码", trigger: "blur" }],
    batchNo: [{ required: true, message: "请输入批次号", trigger: "blur" }],
    identifierType: [
      { required: true, message: "请选择标识类型", trigger: "change" },
    ],
    quantity: [{ required: true, message: "请输入生成数量", trigger: "blur" }],
    status: [{ required: true, message: "请选择状态", trigger: "change" }],
  };
const isEdit = ref(false)
const editId = ref(null)
const generateDialogVisible = ref(false)
const reassignDialogVisible = ref(false)
const currentProduct = ref({})
const generateQuantity = ref(1)
const codeRule = ref('')
const customPrefix = ref('')
const newBatchNo = ref('')
const reassignReason = ref('')
const formRef = ref()
  const isEdit = ref(false);
  const editId = ref(null);
  const generateDialogVisible = ref(false);
  const reassignDialogVisible = ref(false);
  const currentProduct = ref({});
  const generateQuantity = ref(1);
  const codeRule = ref("");
  const customPrefix = ref("");
  const newBatchNo = ref("");
  const reassignReason = ref("");
  const formRef = ref();
// äºŒç»´ç ç›¸å…³å˜é‡
const qrCodeDialogVisible = ref(false)
const qrCodeUrl = ref('')
const currentQRProduct = ref({})
  const qrCodeDialogVisible = ref(false);
  const qrCodeUrl = ref("");
  const currentQRProduct = ref({});
// è®¡ç®—属性
const filteredList = computed(() => {
  let list = identifierList.value
    let list = identifierList.value;
  if (searchForm.productName) {
    list = list.filter(item => item.productName.includes(searchForm.productName))
      list = list.filter(item =>
        item.productName.includes(searchForm.productName)
      );
  }
  if (searchForm.identifierType) {
    list = list.filter(item => item.identifierType === searchForm.identifierType)
      list = list.filter(
        item => item.identifierType === searchForm.identifierType
      );
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
      list = list.filter(item => item.status === searchForm.status);
  }
  return list
})
    return list;
  });
// æ–¹æ³•
const getIdentifierTypeType = (type) => {
  const getIdentifierTypeType = type => {
  const typeMap = {
    '二维码': 'success',
    '防伪码': 'warning'
  }
  return typeMap[type] || 'info'
}
      äºŒç»´ç : "success",
      é˜²ä¼ªç : "warning",
    };
    return typeMap[type] || "info";
  };
const getStatusType = (status) => {
  const getStatusType = status => {
  const statusMap = {
    '已生成': 'info',
    '已分配': 'primary',
    '已使用': 'success',
    '已作废': 'danger'
  }
  return statusMap[status] || 'info'
}
      å·²ç”Ÿæˆ: "info",
      å·²åˆ†é…: "primary",
      å·²ä½¿ç”¨: "success",
      å·²ä½œåºŸ: "danger",
    };
    return statusMap[status] || "info";
  };
const handleSearch = () => {
  // æœç´¢é€»è¾‘已在computed中处理
}
  };
const resetSearch = () => {
  searchForm.productName = ''
  searchForm.identifierType = ''
  searchForm.status = ''
}
    searchForm.productName = "";
    searchForm.identifierType = "";
    searchForm.status = "";
  };
const handleAdd = () => {
  dialogTitle.value = '新增标识'
  isEdit.value = false
  form.productName = ''
  form.productCode = ''
  form.batchNo = ''
  form.identifierType = ''
  form.quantity = 1
  form.status = '已生成'
  form.remark = ''
  dialogVisible.value = true
}
    dialogTitle.value = "新增标识";
    isEdit.value = false;
    form.productName = "";
    form.productCode = "";
    form.batchNo = "";
    form.identifierType = "";
    form.quantity = 1;
    form.status = "已生成";
    form.remark = "";
    dialogVisible.value = true;
  };
const handleView = (row) => {
  const handleView = row => {
  // æŸ¥çœ‹æ ‡è¯†è¯¦æƒ…
  ElMessage.info('查看标识详情功能待实现')
}
    ElMessage.info("查看标识详情功能待实现");
  };
const handleEdit = (row) => {
  dialogTitle.value = '编辑标识'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  dialogVisible.value = true
}
  const handleEdit = row => {
    dialogTitle.value = "编辑标识";
    isEdit.value = true;
    editId.value = row.id;
    Object.assign(form, row);
    dialogVisible.value = true;
  };
const handleExport = (row) => {
  const handleExport = row => {
  // å¯¼å‡ºæ ‡è¯†
  ElMessage.success(`已导出标识: ${row.identifierCode}`)
}
    ElMessage.success(`已导出标识: ${row.identifierCode}`);
  };
const handleReassign = (row) => {
  currentProduct.value = row
  newBatchNo.value = ''
  reassignReason.value = ''
  reassignDialogVisible.value = true
}
  const handleReassign = row => {
    currentProduct.value = row;
    newBatchNo.value = "";
    reassignReason.value = "";
    reassignDialogVisible.value = true;
  };
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该标识吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该标识吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
  }).then(() => {
    const index = identifierList.value.findIndex(item => item.id === row.id)
      const index = identifierList.value.findIndex(item => item.id === row.id);
    if (index > -1) {
      identifierList.value.splice(index, 1)
      pagination.total--
      ElMessage.success('删除成功')
        identifierList.value.splice(index, 1);
        pagination.total--;
        ElMessage.success("删除成功");
    }
  })
}
    });
  };
// ç”ŸæˆäºŒç»´ç 
const generateQRCode = async (row) => {
  const generateQRCode = async row => {
  try {
    // æ£€æŸ¥å¿…要字段
    if (!row.productName || !row.productCode || !row.batchNo) {
      ElMessage.warning('产品信息不完整,无法生成二维码')
      return
        ElMessage.warning("产品信息不完整,无法生成二维码");
        return;
    }
    
    currentQRProduct.value = row
    qrCodeUrl.value = ''
    qrCodeDialogVisible.value = true
      currentQRProduct.value = row;
      qrCodeUrl.value = "";
      qrCodeDialogVisible.value = true;
    
    // æž„建二维码内容
    let qrContent = ''
    if (row.identifierType === '二维码') {
      qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`
    } else if (row.identifierType === '防伪码') {
      let qrContent = "";
      if (row.identifierType === "二维码") {
        qrContent = `${row.productName}|${row.productCode}|${row.batchNo}|${row.identifierCode}`;
      } else if (row.identifierType === "防伪码") {
      // é˜²ä¼ªç æ ¼å¼ï¼šSEC_产品编码_批次号_时间戳_随机数
      const timestamp = Date.now()
      const random = Math.random().toString(36).substr(2, 8)
      qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`
        const timestamp = Date.now();
        const random = Math.random().toString(36).substr(2, 8);
        qrContent = `SEC_${row.productCode}_${row.batchNo}_${timestamp}_${random}`;
    }
    
    // ç”ŸæˆäºŒç»´ç 
@@ -478,84 +581,87 @@
      width: 256,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
          dark: "#000000",
          light: "#FFFFFF",
      },
      errorCorrectionLevel: row.identifierType === '防伪码' ? 'H' : 'M'
    })
        errorCorrectionLevel: row.identifierType === "防伪码" ? "H" : "M",
      });
    
    ElMessage.success('二维码生成成功!')
      ElMessage.success("二维码生成成功!");
  } catch (error) {
    console.error('生成二维码失败:', error)
    ElMessage.error('生成二维码失败:' + error.message)
    qrCodeDialogVisible.value = false
      console.error("生成二维码失败:", error);
      ElMessage.error("生成二维码失败:" + error.message);
      qrCodeDialogVisible.value = false;
  }
}
  };
// ä¸‹è½½äºŒç»´ç 
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    ElMessage.warning('请先生成二维码')
    return
      ElMessage.warning("请先生成二维码");
      return;
  }
  
  const a = document.createElement('a')
  a.href = qrCodeUrl.value
  a.download = `${currentQRProduct.value.productName}_${currentQRProduct.value.identifierType}_${new Date().getTime()}.png`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('下载成功!')
}
    const a = document.createElement("a");
    a.href = qrCodeUrl.value;
    a.download = `${currentQRProduct.value.productName}_${
      currentQRProduct.value.identifierType
    }_${new Date().getTime()}.png`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    ElMessage.success("下载成功!");
  };
// å¤åˆ¶äºŒç»´ç å†…容
const copyQRContent = async () => {
  if (!currentQRProduct.value) {
    ElMessage.warning('没有可复制的内容')
    return
      ElMessage.warning("没有可复制的内容");
      return;
  }
  
  try {
    let content = ''
    if (currentQRProduct.value.identifierType === '二维码') {
      content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`
    } else if (currentQRProduct.value.identifierType === '防伪码') {
      const timestamp = Date.now()
      const random = Math.random().toString(36).substr(2, 8)
      content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`
      let content = "";
      if (currentQRProduct.value.identifierType === "二维码") {
        content = `${currentQRProduct.value.productName}|${currentQRProduct.value.productCode}|${currentQRProduct.value.batchNo}|${currentQRProduct.value.identifierCode}`;
      } else if (currentQRProduct.value.identifierType === "防伪码") {
        const timestamp = Date.now();
        const random = Math.random().toString(36).substr(2, 8);
        content = `SEC_${currentQRProduct.value.productCode}_${currentQRProduct.value.batchNo}_${timestamp}_${random}`;
    }
    
    await navigator.clipboard.writeText(content)
    ElMessage.success('内容已复制到剪贴板')
      await navigator.clipboard.writeText(content);
      ElMessage.success("内容已复制到剪贴板");
  } catch (error) {
    // é™çº§æ–¹æ¡ˆ
    const textArea = document.createElement('textarea')
    textArea.value = content
    document.body.appendChild(textArea)
    textArea.select()
    document.execCommand('copy')
    document.body.removeChild(textArea)
    ElMessage.success('内容已复制到剪贴板')
      const textArea = document.createElement("textarea");
      textArea.value = content;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
      ElMessage.success("内容已复制到剪贴板");
  }
}
  };
const generateIdentifiers = () => {
  if (!codeRule.value) {
    ElMessage.warning('请选择编码规则')
    return
      ElMessage.warning("请选择编码规则");
      return;
  }
  
  // ç”Ÿæˆæ ‡è¯†çš„逻辑
  const newIdentifiers = []
    const newIdentifiers = [];
  for (let i = 1; i <= generateQuantity.value; i++) {
    let identifierCode = ''
    if (codeRule.value === '产品编码+批次号+序号') {
      identifierCode = `${currentProduct.value.productCode}_${currentProduct.value.batchNo}_${String(i).padStart(3, '0')}`
    } else if (codeRule.value === '时间戳+随机数') {
      identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`
    } else if (codeRule.value === '自定义规则') {
      identifierCode = `${customPrefix.value || 'CUSTOM'}_${Date.now()}_${i}`
      let identifierCode = "";
      if (codeRule.value === "产品编码+批次号+序号") {
        identifierCode = `${currentProduct.value.productCode}_${
          currentProduct.value.batchNo
        }_${String(i).padStart(3, "0")}`;
      } else if (codeRule.value === "时间戳+随机数") {
        identifierCode = `TS_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
      } else if (codeRule.value === "自定义规则") {
        identifierCode = `${customPrefix.value || "CUSTOM"}_${Date.now()}_${i}`;
    }
    
    newIdentifiers.push({
@@ -565,73 +671,78 @@
      batchNo: currentProduct.value.batchNo,
      identifierType: currentProduct.value.identifierType,
      identifierCode: identifierCode,
      status: '已生成',
        status: "已生成",
      generateTime: new Date().toLocaleString(),
      remark: '批量生成'
    })
        remark: "批量生成",
      });
  }
  
  identifierList.value.push(...newIdentifiers)
  pagination.total += newIdentifiers.length
  ElMessage.success(`成功生成 ${newIdentifiers.length} ä¸ªæ ‡è¯†`)
  generateDialogVisible.value = false
}
    identifierList.value.push(...newIdentifiers);
    pagination.total += newIdentifiers.length;
    ElMessage.success(`成功生成 ${newIdentifiers.length} ä¸ªæ ‡è¯†`);
    generateDialogVisible.value = false;
  };
const saveReassign = () => {
  if (!newBatchNo.value) {
    ElMessage.warning('请输入新批次号')
    return
      ElMessage.warning("请输入新批次号");
      return;
  }
  
  const index = identifierList.value.findIndex(item => item.id === currentProduct.value.id)
    const index = identifierList.value.findIndex(
      item => item.id === currentProduct.value.id
    );
  if (index > -1) {
    identifierList.value[index].batchNo = newBatchNo.value
    identifierList.value[index].status = '已分配'
    ElMessage.success('标识重新分配成功')
    reassignDialogVisible.value = false
      identifierList.value[index].batchNo = newBatchNo.value;
      identifierList.value[index].status = "已分配";
      ElMessage.success("标识重新分配成功");
      reassignDialogVisible.value = false;
  }
}
  };
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    formRef.value.validate(valid => {
    if (valid) {
      if (isEdit.value) {
        // ç¼–辑
        const index = identifierList.value.findIndex(item => item.id === editId.value)
          const index = identifierList.value.findIndex(
            item => item.id === editId.value
          );
        if (index > -1) {
          identifierList.value[index] = { ...form, id: editId.value }
          ElMessage.success('编辑成功')
            identifierList.value[index] = { ...form, id: editId.value };
            ElMessage.success("编辑成功");
        }
             } else {
         // æ–°å¢ž
         const newId = Math.max(...identifierList.value.map(item => item.id)) + 1
          const newId =
            Math.max(...identifierList.value.map(item => item.id)) + 1;
         
         // æ ¹æ®æ ‡è¯†ç±»åž‹ç”Ÿæˆä¸åŒçš„æ ‡è¯†ç 
         let identifierCode = ''
         if (form.identifierType === '二维码') {
           identifierCode = `QR_${form.productCode}_${form.batchNo}_001`
         } else if (form.identifierType === '防伪码') {
           identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`
          let identifierCode = "";
          if (form.identifierType === "二维码") {
            identifierCode = `QR_${form.productCode}_${form.batchNo}_001`;
          } else if (form.identifierType === "防伪码") {
            identifierCode = `SEC_${form.productCode}_${form.batchNo}_001`;
         }
         
         identifierList.value.push({
           ...form,
           id: newId,
           identifierCode: identifierCode,
           generateTime: new Date().toLocaleString()
         })
         pagination.total++
         ElMessage.success('新增成功')
            generateTime: new Date().toLocaleString(),
          });
          pagination.total++;
          ElMessage.success("新增成功");
       }
      dialogVisible.value = false
        dialogVisible.value = false;
    }
  })
}
    });
  };
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
}
  const handleCurrentChange = val => {
    pagination.currentPage = val.page;
    pagination.pageSize = val.limit;
  };
</script>
<style scoped>
@@ -698,7 +809,7 @@
.qr-loading .el-icon {
  font-size: 32px;
  color: #409EFF;
    color: #409eff;
}
.qr-loading p {
src/views/productionManagement/operationScheduling/components/formDia.vue
@@ -8,11 +8,11 @@
    >
      <el-button type="primary" @click="addRow" style="margin-bottom: 10px;">新增</el-button>
            <span style="font-size: 18px;margin-left: 10px">待排产数量:{{pendingNum}}</span>
            <div style="margin-bottom: 10px; margin-left: 10px;">
                <el-form-item label="领用:" style="margin-bottom: 0;">
                    <el-input v-model="receive" placeholder="请输入领用" style="width: 200px;" />
                </el-form-item>
            </div>
<!--            <div style="margin-bottom: 10px; margin-left: 10px;">-->
<!--                <el-form-item label="领用:" style="margin-bottom: 0;">-->
<!--                    <el-input v-model="receive" placeholder="请输入领用" style="width: 200px;" />-->
<!--                </el-form-item>-->
<!--            </div>-->
      <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id">
        <el-table-column label="序号" width="60" align="center">
          <template #default="scope">
@@ -22,6 +22,23 @@
        <el-table-column label="工序" prop="process" width="150">
          <template #default="scope">
                        <el-input v-model="scope.row.process" placeholder="请输入工序" />
          </template>
        </el-table-column>
        <el-table-column label="产线" prop="productionLine" width="150">
          <template #default="scope">
            <el-select
              v-model="scope.row.productionLine"
              placeholder="选择产线"
              style="width: 100%;"
              clearable
            >
              <el-option
                v-for="line in productionLines"
                :key="line.value"
                :label="line.label"
                :value="line.value"
              />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="unit" width="90">
@@ -45,11 +62,6 @@
                            clearable
                            style="width: 100%"
                        />
          </template>
        </el-table-column>
        <el-table-column label="损耗" prop="loss" width="150">
          <template #default="scope">
            <el-input v-model="scope.row.loss" placeholder="请输入损耗" />
          </template>
        </el-table-column>
        <el-table-column label="工时定额" width="200" prop="workHours">
@@ -76,6 +88,9 @@
                            v-model="scope.row.schedulingUserId"
                            placeholder="选择人员"
                            style="width: 100%;"
              filterable
              default-first-option
              :reserve-keyword="false"
                        >
                            <el-option
                                v-for="user in userList"
@@ -108,7 +123,7 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, getCurrentInstance} from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {processScheduling} from "@/api/productionManagement/operationScheduling.js";
const { proxy } = getCurrentInstance()
@@ -117,34 +132,56 @@
const dialogFormVisible = ref(false);
const operationType = ref('')
const tableData = ref([
    { process: '', schedulingDate: '', schedulingNum: null, schedulingUserId: '', workHours: null, unit: '', remark: '', loss: '', type: '' }
]);
const tableData = ref([]);
const unitFromRow = ref('');
const idFromRow = ref('');
const pendingNum = ref('');
const specificationModelFromRow = ref('');
const pendingNum = ref(0);
const userList = ref([])
const receive = ref('')
const sunqianUserId = ref('')
// äº§çº¿é€‰é¡¹
const productionLines = ref([
  { label: '产线1', value: '产线1' },
  { label: '产线2', value: '产线2' },
  { label: '产线3', value: '产线3' },
  { label: '产线4', value: '产线4' }
])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    pendingNum.value = row?.pendingNum ?? 0;
    unitFromRow.value = row?.unit ?? '';
    idFromRow.value = row?.id ?? '';
    specificationModelFromRow.value = row?.specificationModel ?? '';
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    pendingNum.value = row.pendingNum
  if (row && row.unit !== undefined) {
    unitFromRow.value = row.unit;
    idFromRow.value = row.id;
    tableData.value.forEach(item => {
      item.unit = row.unit;
      item.id = row.id;
    });
  } else {
    unitFromRow.value = '';
        // æ‰¾åˆ°å­™å€©çš„用户ID并设置为默认值
        const sunqianUser = userList.value.find(user => user.nickName === '孙倩');
        if (sunqianUser) {
            sunqianUserId.value = sunqianUser.userId;
  }
        // åœ¨ç”¨æˆ·åˆ—表加载完成后创建行数据,并将产线数据带入
        tableData.value = [createRow(row)];
    });
}
const createRow = (row) => ({
    id: idFromRow.value,
    process: '包装',
    schedulingDate: '',
    schedulingNum: null,
    schedulingUserId: sunqianUserId.value, // é»˜è®¤è®¾ç½®ä¸ºå­™å€©çš„用户ID
    workHours: null,
    unit: unitFromRow.value,
    remark: '',
    type: specificationModelFromRow.value,
    productionLine: row?.productionLine ?? '', // ä»Žè¡Œæ•°æ®ä¸­èŽ·å–äº§çº¿ä¿¡æ¯
});
const submitForm = () => {
    // 1. æ£€æŸ¥æ¯ä¸€è¡Œæ˜¯å¦å¡«å†™å®Œæ•´
    for (let i = 0; i < tableData.value.length; i++) {
@@ -155,7 +192,8 @@
            row.schedulingNum === '' || row.schedulingNum === null ||
            !row.schedulingUserId ||
            row.workHours === '' || row.workHours === null ||
            !row.unit
            !row.unit ||
            !row.productionLine
        ) {
            proxy.$modal.msgError(`第${i + 1}行数据未填写完整`);
            return;
@@ -169,11 +207,14 @@
        proxy.$modal.msgError('排产数量合计不能超过待排产数量');
        return;
    }
    // 3. å°† receive å­—段添加到每条数据中
    const submitData = tableData.value.map(row => ({
        ...row,
    // 3. å°† receive å­—段添加到每条数据中,并移除 loss å­—段
    const submitData = tableData.value.map(row => {
        const { loss, ...rest } = row;
        return {
            ...rest,
        receive: receive.value
    }));
        };
    });
    processScheduling(submitData).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
@@ -186,6 +227,11 @@
const closeDia = () => {
  dialogFormVisible.value = false;
  receive.value = '';
  tableData.value = [];
  unitFromRow.value = '';
  idFromRow.value = '';
  specificationModelFromRow.value = '';
  pendingNum.value = 0;
  emit('close')
};
defineExpose({
@@ -193,7 +239,7 @@
});
const addRow = () => {
  tableData.value.push({ id: idFromRow.value, process: '', unit: unitFromRow.value, schedulingNum: null, workHours: null, schedulingDate: '', schedulingUserId: '', remark: '', loss: '', type: '' });
  tableData.value.push(createRow());
};
const removeRow = (index) => {
  tableData.value.splice(index, 1);
src/views/productionManagement/operationScheduling/index.vue
@@ -7,11 +7,16 @@
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
                <el-form-item label="项目名称:">
                    <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
                <el-form-item label="合同号:">
                    <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
<!--                <el-form-item label="项目名称:">-->
<!--                    <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"-->
<!--                                        style="width: 200px;"-->
<!--                                        @change="handleQuery" />-->
<!--                </el-form-item>-->
                <el-form-item label="派工日期:">
                    <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                    placeholder="请选择" clearable @change="changeDaterange" />
@@ -60,10 +65,12 @@
const data = reactive({
    searchForm: {
        staffName: "",
        customerName: "",
        salesContractNo: "",
        status: 1,
        entryDate: null, // å½•入日期
        entryDateStart: undefined,
        entryDateEnd: undefined,
        entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // å½•入日期,默认当天
        entryDateStart: dayjs().format("YYYY-MM-DD"),
        entryDateEnd: dayjs().format("YYYY-MM-DD"),
    },
});
const { searchForm } = toRefs(data);
@@ -94,19 +101,52 @@
    {
        label: "派工日期",
        prop: "schedulingDate",
        width: 120,
    },
    {
        label: "派工人",
        prop: "schedulingUserName",
    },
    {
        label: "合同号",
        prop: "salesContractNo",
        width: 200,
    },
    // {
    //     label: "客户合同号",
    //     prop: "customerContractNo",
    //     width: 200,
    // },
    {
        label: "客户名称",
        prop: "customerName",
        width: 200,
    },
    // {
    //     label: "项目名称",
    //     prop: "projectName",
    //     width:300
    // },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 150,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 150,
    },
    {
        label: "绑定机器",
        prop: "speculativeTradingName",
        width: 220,
    },
    // {
    //     label: "产线",
    //     prop: "productionLine",
    //     width: 220,
    // },
    {
        label: "单位",
        prop: "unit",
@@ -218,8 +258,6 @@
            productionDispatchDelete(ids)
                .then((res) => {
                    proxy.$modal.msgSuccess("取消排产成功");
                    getList();
                }).catch(() => {
                    getList();
            })
        })
src/views/productionManagement/processRoute/Edit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,252 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="编辑工艺路线"
        width="400"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ formState.productName && formState.productModelName
              ? `${formState.productName} - ${formState.productModelName}`
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="BOM"
            prop="bomId"
            :rules="[
                {
                required: true,
                message: '请选择BOM',
                trigger: 'change',
              }
            ]"
        >
          <el-select
              v-model="formState.bomId"
              placeholder="请选择BOM"
              clearable
              :disabled="!formState.productModelId || bomOptions.length === 0"
              style="width: 100%"
          >
            <el-option
                v-for="item in bomOptions"
                :key="item.id"
                :label="item.bomNo || `BOM-${item.id}`"
                :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="备注" prop="description">
          <el-input v-model="formState.description" type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <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, onMounted, nextTick, watch} from "vue";
import {update} from "@/api/productionManagement/processRoute.js";
import {getByModel} from "@/api/productionManagement/productBom.js";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  record: {
    type: Object,
    required: true,
  }
});
const emit = defineEmits(['update:visible', 'completed']);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  bomId: undefined,
  description: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
const bomOptions = ref([]);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  isShow.value = false;
};
// è®¾ç½®è¡¨å•数据
const setFormData = () => {
  if (props.record) {
    formState.value = {
      ...props.record,
      productId: props.record.productId,
      productModelId: props.record.productModelId,
      productName: props.record.productName || "",
      // æ³¨æ„ï¼šrecord中的字段是model,需要映射到productModelName
      productModelName: props.record.model || props.record.productModelName || "",
      bomId: props.record.bomId,
      description: props.record.description || '',
    };
    // å¦‚果有产品型号ID,加载BOM列表
    if (props.record.productModelId) {
      loadBomList(props.record.productModelId);
    }
  }
}
// åŠ è½½BOM列表
const loadBomList = async (productModelId) => {
  if (!productModelId) {
    bomOptions.value = [];
    return;
  }
  try {
    const res = await getByModel(productModelId);
    // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
    let bomList = [];
    if (Array.isArray(res)) {
      bomList = res;
    } else if (res && res.data) {
      bomList = Array.isArray(res.data) ? res.data : [res.data];
    } else if (res && typeof res === 'object') {
      bomList = [res];
    }
    bomOptions.value = bomList;
  } catch (error) {
    console.error("加载BOM列表失败:", error);
    bomOptions.value = [];
  }
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    // å…ˆæŸ¥è¯¢BOM列表(必选)
    try {
      const res = await getByModel(product.id);
      // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
      let bomList = [];
      if (Array.isArray(res)) {
        bomList = res;
      } else if (res && res.data) {
        bomList = Array.isArray(res.data) ? res.data : [res.data];
      } else if (res && typeof res === 'object') {
        bomList = [res];
      }
      if (bomList.length > 0) {
        formState.value.productModelId = product.id;
        formState.value.productName = product.productName;
        formState.value.productModelName = product.model;
        // å¦‚果当前选择的BOM不在新列表中,则重置BOM选择
        const currentBomExists = bomList.some(bom => bom.id === formState.value.bomId);
        if (!currentBomExists) {
          formState.value.bomId = undefined;
        }
        bomOptions.value = bomList;
        showProductSelectDialog.value = false;
        // è§¦å‘表单验证更新
        proxy.$refs["formRef"]?.validateField('productModelId');
      } else {
        proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
      }
    } catch (error) {
      // å¦‚果接口返回404或其他错误,说明没有BOM
      proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
    }
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’ŒBOM
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.bomId) {
        proxy.$modal.msgError("请选择BOM");
        return;
      }
      update(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
// ç›‘听弹窗打开,初始化表单数据
watch(() => props.visible, (visible) => {
  if (visible && props.record) {
    nextTick(() => {
      setFormData();
    });
  }
}, { immediate: true });
onMounted(() => {
  if (props.visible && props.record) {
    setFormData();
  }
});
</script>
src/views/productionManagement/processRoute/ItemsForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,531 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="工艺路线项目"
        width="800px"
        @close="closeModal"
    >
      <div class="operate-button">
        <el-button
            type="primary"
            @click="isShowProductSelectDialog = true"
            class="mb5"
            style="margin-bottom: 10px;"
        >
          é€‰æ‹©äº§å“
        </el-button>
        <el-switch
            v-model="isTable"
            inline-prompt
            active-text="表格"
            inactive-text="列表"
            @change="handleViewChange"
        />
      </div>
      <el-table
          v-if="isTable"
          ref="multipleTable"
          v-loading="tableLoading"
          border
          :data="routeItems"
          :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
          row-key="id"
          tooltip-effect="dark"
          class="lims-table"
          style="cursor: move;"
      >
        <el-table-column align="center" label="序号" width="60">
          <template #default="scope">
            {{ scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
            v-for="(item, index) in tableColumn"
            :key="index"
            :label="item.label"
            :width="item.width"
            show-overflow-tooltip
        >
          <template #default="scope" v-if="item.dataType === 'action'">
            <el-button
                v-for="(op, opIndex) in item.operation"
                :key="opIndex"
                :type="op.type"
                :link="op.link"
                size="small"
                @click.stop="op.clickFun(scope.row)"
            >
              {{ op.name }}
            </el-button>
          </template>
          <template #default="scope" v-else>
            <template v-if="item.prop === 'processId'">
              <el-select
                  v-model="scope.row[item.prop]"
                  style="width: 100%;"
                  @mousedown.stop
              >
                <el-option
                    v-for="process in processOptions"
                    :key="process.id"
                    :label="process.name"
                    :value="process.id"
                />
              </el-select>
            </template>
            <template v-else>
              {{ scope.row[item.prop] || '-' }}
            </template>
          </template>
        </el-table-column>
      </el-table>
      <!-- ä½¿ç”¨æ™®é€šdiv替代el-steps -->
      <div
          v-else
          ref="stepsContainer"
          class="mb5 custom-steps"
          style="padding: 10px 0; display: flex; flex-wrap: nowrap; gap: 20px; align-items: flex-start;"
      >
        <div
            v-for="(item, index) in routeItems"
            :key="item.id"
            class="custom-step draggable-step"
            :data-id="item.id"
            style="cursor: move; flex: 0 0 auto; min-width: 220px;"
        >
          <div class="step-content">
            <div class="step-number">{{ index + 1 }}</div>
            <el-card
                :header="item.productName"
                class="step-card"
                style="cursor: move;"
            >
              <div class="step-card-content">
                <p>{{ item.model }}</p>
                <p>{{ item.unit }}</p>
                <el-select
                    v-model="item.processId"
                    style="width: 100%;"
                    @mousedown.stop
                >
                  <el-option
                      v-for="process in processOptions"
                      :key="process.id"
                      :label="process.name"
                      :value="process.id"
                  />
                </el-select>
              </div>
              <template #footer>
                <div class="step-card-footer">
                  <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">删除</el-button>
                </div>
              </template>
            </el-card>
          </div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <ProductSelectDialog
        v-model="isShowProductSelectDialog"
        @confirm="handelSelectProducts"
    />
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import Sortable from 'sortablejs';
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
    default: false
  },
  record: {
    type: Object,
    required: true,
    default: () => ({})
  }
});
const emit = defineEmits(['update:visible', 'completed']);
const processOptions = ref([]);
const tableLoading = ref(false);
const isShowProductSelectDialog = ref(false);
const routeItems = ref([]);
let tableSortable = null;
let stepsSortable = null;
const multipleTable = ref(null);
const stepsContainer = ref(null);
const isTable = ref(true);
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  }
});
const tableColumn = ref([
  { label: "产品名称", prop: "productName", width: 180 },
  { label: "规格名称", prop: "model", width: 150 },
  { label: "单位", prop: "unit", width: 80 },
  { label: "工序名称", prop: "processId", width: 180 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 100,
    operation: [
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          const idx = routeItems.value.findIndex(item => item.id === row.id);
          if (idx > -1) {
            removeItem(idx)
          }
        }
      }
    ]
  }
]);
const removeItem = (index) => {
  routeItems.value.splice(index, 1);
  nextTick(() => initSortable());
};
const removeItemByID = (id) => {
  const idx = routeItems.value.findIndex(item => item.id === id);
  if (idx > -1) {
    routeItems.value.splice(idx, 1);
    nextTick(() => initSortable());
  }
};
const closeModal = () => {
  isShow.value = false;
};
const handelSelectProducts = (products) => {
  destroySortable();
  const newData = products.map(({ id, ...product }) => ({
    ...product,
    productModelId: id,
    routeId: props.record.id,
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
    processId: undefined
  }));
  console.log('选择产品前数组:', routeItems.value);
  routeItems.value.push(...newData);
  routeItems.value = [...routeItems.value];
  console.log('选择产品后数组:', routeItems.value);
  // å»¶è¿Ÿåˆå§‹åŒ–,确保DOM完全渲染
  nextTick(() => {
    // å¼ºåˆ¶é‡æ–°æ¸²æŸ“组件
    if (proxy?.$forceUpdate) {
      proxy.$forceUpdate();
    }
    const temp = [...routeItems.value];
    routeItems.value = [];
    nextTick(() => {
      routeItems.value = temp;
      initSortable();
    });
  });
};
const findProcessRouteItems = () => {
  tableLoading.value = true;
  findProcessRouteItemList({ routeId: props.record.id })
      .then(res => {
        tableLoading.value = false;
        routeItems.value = res.data.map(item => ({
          ...item,
          processId: item.processId === 0 ? undefined : item.processId
        }));
        // å»¶è¿Ÿåˆå§‹åŒ–,确保DOM完全渲染
        nextTick(() => {
          setTimeout(() => initSortable(), 100);
        });
      })
      .catch(err => {
        tableLoading.value = false;
        console.error("获取列表失败:", err);
      });
};
const findProcessList = () => {
  processList({})
      .then(res => {
        processOptions.value = res.data;
      })
      .catch(err => {
        console.error("获取工序失败:", err);
      });
};
const { proxy } = getCurrentInstance() || {};
const handleSubmit = () => {
  const hasEmptyProcess = routeItems.value.some(item => !item.processId);
  if (hasEmptyProcess) {
    proxy?.$modal?.msgError("请为所有项目选择工序");
    return;
  }
  addOrUpdateProcessRouteItem({
    routeId: props.record.id,
    processRouteItem: routeItems.value.map(({ id, ...item }) => item)
  })
      .then(res => {
        isShow.value = false;
        emit('completed');
        proxy?.$modal?.msgSuccess("提交成功");
      })
      .catch(err => {
        proxy?.$modal?.msgError(`提交失败:${err.msg || "网络异常"}`);
      });
};
const destroySortable = () => {
  if (tableSortable) {
    tableSortable.destroy();
    tableSortable = null;
  }
  if (stepsSortable) {
    stepsSortable.destroy();
    stepsSortable = null;
  }
};
const initSortable = () => {
  destroySortable();
  if (isTable.value) {
    if (!multipleTable.value) return;
    const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
        multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
    if (!tbody) return;
    tableSortable = new Sortable(tbody, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.el-table__row',
      filter: '.el-button, .el-select',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
        // ä½¿ç”¨æ•°ç»„ splice æ–¹æ³•重新排序,与表格模式保持一致
        const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
        routeItems.value.splice(evt.newIndex, 0, moveItem);
        routeItems.value = [...routeItems.value];
        console.log('排序后数组:', routeItems.value);
      }
    });
  } else {
    if (!stepsContainer.value) return;
    // ä¿®æ”¹ï¼šç›´æŽ¥ä½¿ç”¨stepsContainer.value作为拖拽容器
    const stepsList = stepsContainer.value;
    if (!stepsList) {
      console.warn('未找到步骤条拖拽容器');
      return;
    }
    // ä¿®æ”¹ï¼šç®€åŒ–拖拽配置
    stepsSortable = new Sortable(stepsList, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      draggable: '.draggable-step', // å¯æ‹–拽元素
      handle: '.draggable-step, .step-card', // æ‹–拽手柄
      filter: '.el-button, .el-select, .el-input', // è¿‡æ»¤æŒ‰é’®/选择器
      forceFallback: true,
      fallbackClass: 'sortable-fallback',
      preventOnFilter: true,
      scroll: true,
      scrollSensitivity: 30,
      scrollSpeed: 10,
      bubbleScroll: true,
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
        // ä½¿ç”¨æ•°ç»„ splice æ–¹æ³•重新排序
        const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
        routeItems.value.splice(evt.newIndex, 0, moveItem);
        routeItems.value = [...routeItems.value];
      }
    });
    // è°ƒè¯•:打印容器和实例,确认绑定成功
    console.log('步骤条拖拽容器:', stepsList);
    console.log('Sortable实例:', stepsSortable);
  }
};
const handleViewChange = () => {
  destroySortable();
  // å»¶è¿Ÿåˆå§‹åŒ–,确保视图切换后DOM完全渲染
  nextTick(() => {
    setTimeout(() => initSortable(), 100);
  });
};
onMounted(() => {
  findProcessRouteItems();
  findProcessList();
});
onUnmounted(() => {
  destroySortable();
});
defineExpose({
  closeModal,
  handleSubmit,
  isShow
});
</script>
<style scoped>
:deep(.sortable-ghost) {
  opacity: 0.6;
  background-color: #f5f7fa !important;
}
:deep(.el-table__row) {
  transition: background-color 0.2s;
}
:deep(.el-table__row:hover) {
  background-color: #f9fafc !important;
}
:deep(.el-card__footer){
  padding: 0 !important;
}
.operate-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* ä¿®æ”¹ï¼šè‡ªå®šä¹‰æ­¥éª¤æ¡å®¹å™¨æ ·å¼ */
.custom-steps {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 20px;
  min-height: 100px;
}
/* ä¿®æ”¹ï¼šè‡ªå®šä¹‰æ­¥éª¤é¡¹æ ·å¼ */
.custom-step {
  cursor: move !important;
  padding: 8px;
  position: relative;
  transition: all 0.2s ease;
  flex: 0 0 auto;
  min-width: 220px;
  touch-action: none;
}
/* æ‹–拽悬浮样式,提示可拖拽 */
.custom-step:hover {
  background-color: rgba(64, 158, 255, 0.05);
  transform: translateY(-2px);
}
.sortable-ghost {
  opacity: 0.4;
  background-color: #f5f7fa !important;
  border: 2px dashed #409eff;
  margin: 10px;
  transform: scale(1.02);
}
.sortable-fallback {
  opacity: 0.9;
  background-color: #f5f7fa;
  border: 1px solid #409eff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  transform: rotate(2deg);
  margin: 10px;
}
.step-card {
  cursor: move !important;
  transition: box-shadow 0.2s ease;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: auto;
  margin: 10px;
  height: 240px;
}
.step-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.step-content {
  width: 220px;
  user-select: none;
}
.step-card-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.step-card-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 10px;
}
/* è‡ªå®šä¹‰åºå·æ ·å¼ä¼˜åŒ– */
.step-number {
  font-weight: bold;
  text-align: center;
  width: 36px;
  height: 36px;
  line-height: 36px;
  margin: 0 auto 10px;
  background: #409eff;
  color: #fff;
  border-radius: 50%;
  font-size: 14px;
}
</style>
src/views/productionManagement/processRoute/New.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,194 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增工艺路线"
        width="400"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
            prop="productModelId"
            :rules="[
                {
                required: true,
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ formState.productName && formState.productModelName
              ? `${formState.productName} - ${formState.productModelName}`
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="BOM"
            prop="bomId"
            :rules="[
                {
                required: true,
                message: '请选择BOM',
                trigger: 'change',
              }
            ]"
        >
          <el-select
              v-model="formState.bomId"
              placeholder="请选择BOM"
              clearable
              :disabled="!formState.productModelId || bomOptions.length === 0"
              style="width: 100%"
          >
            <el-option
                v-for="item in bomOptions"
                :key="item.id"
                :label="item.bomNo || `BOM-${item.id}`"
                :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="备注" prop="description">
          <el-input v-model="formState.description" type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          single
      />
      <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/processRoute.js";
import {getByModel} from "@/api/productionManagement/productBom.js";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
});
const emit = defineEmits(['update:visible', 'completed']);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  bomId: undefined,
  description: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
const bomOptions = ref([]);
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    bomId: undefined,
    description: '',
  };
  bomOptions.value = [];
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    // å…ˆæŸ¥è¯¢BOM列表(必选)
    try {
      const res = await getByModel(product.id);
      // å¤„理返回的BOM数据:可能是数组、对象或包含data字段
      let bomList = [];
      if (Array.isArray(res)) {
        bomList = res;
      } else if (res && res.data) {
        bomList = Array.isArray(res.data) ? res.data : [res.data];
      } else if (res && typeof res === 'object') {
        bomList = [res];
      }
      if (bomList.length > 0) {
        formState.value.productModelId = product.id;
        formState.value.productName = product.productName;
        formState.value.productModelName = product.model;
        formState.value.bomId = undefined; // é‡ç½®BOM选择
        bomOptions.value = bomList;
        showProductSelectDialog.value = false;
        // è§¦å‘表单验证更新
        proxy.$refs["formRef"]?.validateField('productModelId');
      } else {
        proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
      }
    } catch (error) {
      // å¦‚果接口返回404或其他错误,说明没有BOM
      proxy.$modal.msgError("该产品没有BOM,请先创建BOM");
    }
  }
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // éªŒè¯æ˜¯å¦é€‰æ‹©äº†äº§å“å’ŒBOM
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.bomId) {
        proxy.$modal.msgError("请选择BOM");
        return;
      }
      add(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/productionManagement/processRoute/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,204 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="规格名称:">
          <el-input v-model="searchForm.model" placeholder="请输入" clearable prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </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>
        <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>删除工艺路线</el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      />
    </div>
    <new-process
        v-if="isShowNewModal"
        v-model:visible="isShowNewModal"
        @completed="getList"
    />
    <edit-process
        v-if="isShowEditModal"
        v-model:visible="isShowEditModal"
        :record="record"
        @completed="getList"
    />
    <route-item-form
        v-if="isShowItemModal"
        v-model:visible="isShowItemModal"
        :record="record"
        @completed="getList"
    />
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import NewProcess from "@/views/productionManagement/processRoute/New.vue";
import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
import {listPage, del} from "@/api/productionManagement/processRoute.js";
import { useRouter } from 'vue-router'
const router = useRouter()
const data = reactive({
  searchForm: {
    model: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "工艺路线编号",
    prop: "processRouteCode",
  },
  {
    label: "产品名称",
    prop: "productName",
  },
  {
    label: "规格名称",
    prop: "model",
  },
  {
    label: "BOM编号",
    prop: "bomNo",
  },
  {
    label: "描述",
    prop: "description",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 280,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          showEditModal(row);
        }
      },
      {
        name: "路线项目",
        type: "text",
        clickFun: (row) => {
          showItemModal(row);
        }
      }
    ]
  }
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const isShowNewModal = ref(false);
const isShowEditModal = ref(false);
const isShowItemModal = ref(false);
const record = ref({});
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
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 };
  params.entryDate = undefined
  listPage(params).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records.map(item => ({
      ...item,
    }));
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开新增弹框
const showNewModal = () => {
  isShowNewModal.value = true
};
const showEditModal = (row) => {
  isShowEditModal.value = true
  record.value = row
};
const showItemModal = (row) => {
  router.push({
    path: '/productionManagement/processRouteItem',
    query: {
      id: row.id,
      processRouteCode: row.processRouteCode || '',
      productName: row.productName || '',
      model: row.model || '',
      bomNo: row.bomNo || '',
      description: row.description || '',
      type: 'route',
    }
  })
};
// åˆ é™¤
function handleDelete() {
  const ids = selectedRows.value.map((item) => item.id);
  proxy.$modal
      .confirm('是否确认删除已勾选的数据项?')
      .then(function () {
        return del(ids);
      })
      .then(() => {
        getList();
        proxy.$modal.msgSuccess("删除成功");
      })
      .catch(() => {});
}
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/productionManagement/processRoute/processRouteItem/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,876 @@
<template>
  <div class="app-container">
    <PageHeader content="工艺路线项目" />
    <!-- å·¥è‰ºè·¯çº¿ä¿¡æ¯å±•示 -->
    <el-card v-if="routeInfo.processRouteCode" class="route-info-card" shadow="hover">
      <div class="route-info">
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">工艺路线编号</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.processRouteCode }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">产品名称</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.productName || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">规格名称</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.model || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">BOM编号</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
          </div>
        </div>
        <div class="info-item full-width" v-if="routeInfo.description">
          <div class="info-label-wrapper">
            <span class="info-label">描述</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.description }}</span>
          </div>
        </div>
      </div>
    </el-card>
    <!-- è¡¨æ ¼è§†å›¾ -->
    <div v-if="viewMode === 'table'" class="section-header">
      <div class="section-title">工艺路线项目列表</div>
      <div class="section-actions">
        <el-button
            icon="Grid"
            @click="toggleView"
            style="margin-right: 10px;"
        >
          å¡ç‰‡è§†å›¾
        </el-button>
        <el-button type="primary" @click="handleAdd">新增</el-button>
      </div>
    </div>
    <el-table
        v-if="viewMode === 'table'"
        ref="tableRef"
        v-loading="tableLoading"
        border
        :data="tableData"
        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
        row-key="id"
        tooltip-effect="dark"
        class="lims-table"
    >
      <el-table-column align="center" label="序号" width="60" type="index" />
      <el-table-column label="工序名称" prop="processId" width="200">
        <template #default="scope">
          {{ getProcessName(scope.row.processId) || '-' }}
        </template>
      </el-table-column>
      <el-table-column label="产品名称" prop="productName" min-width="160" />
      <el-table-column label="规格名称" prop="model" min-width="140" />
      <el-table-column label="单位" prop="unit" width="100" />
      <el-table-column label="操作" align="center" fixed="right" width="150">
        <template #default="scope">
          <el-button type="primary" link size="small" @click="handleEdit(scope.row)">编辑</el-button>
          <el-button type="danger" link size="small" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- å¡ç‰‡è§†å›¾ -->
    <template v-else>
      <div class="section-header">
        <div class="section-title">工艺路线项目列表</div>
        <div class="section-actions">
          <el-button
              icon="Menu"
              @click="toggleView"
              style="margin-right: 10px;"
          >
            è¡¨æ ¼è§†å›¾
          </el-button>
          <el-button type="primary" @click="handleAdd">新增</el-button>
        </div>
      </div>
      <div v-loading="tableLoading" class="card-container">
        <div
            ref="cardsContainer"
            class="cards-wrapper"
        >
        <div
            v-for="(item, index) in tableData"
            :key="item.id || index"
            class="process-card"
            :data-index="index"
        >
          <!-- åºå·åœ†åœˆ -->
          <div class="card-header">
            <div class="card-number">{{ index + 1 }}</div>
            <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
          </div>
          <!-- äº§å“ä¿¡æ¯ -->
          <div class="card-content">
            <div v-if="item.productName" class="product-info">
              <div class="product-name">{{ item.productName }}</div>
              <div v-if="item.model" class="product-model">
                {{ item.model }}
                <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
              </div>
            </div>
            <div v-else class="product-info empty">暂无产品信息</div>
          </div>
          <!-- æ“ä½œæŒ‰é’® -->
          <div class="card-footer">
            <el-button type="primary" link size="small" @click="handleEdit(item)">编辑</el-button>
            <el-button type="danger" link size="small" @click="handleDelete(item)">删除</el-button>
          </div>
        </div>
      </div>
      </div>
    </template>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog
        v-model="dialogVisible"
        :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
        width="500px"
        @close="closeDialog"
    >
      <el-form
          ref="formRef"
          :model="form"
          :rules="rules"
          label-width="120px"
      >
        <el-form-item label="工序" prop="processId">
          <el-select
              v-model="form.processId"
              placeholder="请选择工序"
              clearable
              style="width: 100%"
          >
            <el-option
                v-for="process in processOptions"
                :key="process.id"
                :label="process.name"
                :value="process.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="产品名称" prop="productModelId">
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ form.productName && form.model
              ? `${form.productName} - ${form.model}`
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="单位" prop="unit">
          <el-input
              v-model="form.unit"
              :placeholder="form.productModelId ? '根据选择的产品自动带出' : '请先选择产品'"
              clearable
              :disabled="true"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="closeDialog">取消</el-button>
        <el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¯¹è¯æ¡† -->
    <ProductSelectDialog
        v-model="showProductSelectDialog"
        @confirm="handleProductSelect"
        single
    />
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProcessRouteItemList, addOrUpdateProcessRouteItem, sortProcessRouteItem, batchDeleteProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
import { findProductProcessRouteItemList, deleteRouteItem, addRouteItem, addOrUpdateProductProcessRouteItem, sortRouteItem } from "@/api/productionManagement/productProcessRoute.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import { useRoute } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import Sortable from 'sortablejs'
const route = useRoute()
const { proxy } = getCurrentInstance() || {};
const routeId = computed(() => route.query.id);
const orderId = computed(() => route.query.orderId);
const pageType = computed(() => route.query.type);
const tableLoading = ref(false);
const tableData = ref([]);
const dialogVisible = ref(false);
const operationType = ref('add'); // add | edit
const formRef = ref(null);
const submitLoading = ref(false);
const cardsContainer = ref(null);
const tableRef = ref(null);
const viewMode = ref('table'); // table | card
const routeInfo = ref({
  processRouteCode: '',
  productName: '',
  model: '',
  bomNo: '',
  description: ''
});
const processOptions = ref([]);
const showProductSelectDialog = ref(false);
let tableSortable = null;
let cardSortable = null;
// åˆ‡æ¢è§†å›¾
const toggleView = () => {
  viewMode.value = viewMode.value === 'table' ? 'card' : 'table';
  // åˆ‡æ¢è§†å›¾åŽé‡æ–°åˆå§‹åŒ–拖拽排序
  nextTick(() => {
    initSortable();
  });
};
const form = ref({
  id: undefined,
  routeId: routeId.value,
  processId: undefined,
  productModelId: undefined,
  productName: "",
  model: "",
  unit: "",
});
const rules = {
  processId: [{ required: true, message: '请选择工序', trigger: 'change' }],
  productModelId: [{ required: true, message: '请选择产品', trigger: 'change' }],
};
// æ ¹æ®å·¥åºID获取工序名称
const getProcessName = (processId) => {
  if (!processId) return '';
  const process = processOptions.value.find(p => p.id === processId);
  return process ? process.name : '';
};
// èŽ·å–åˆ—è¡¨
const getList = () => {
  tableLoading.value = true;
  const listPromise =
    pageType.value === "order"
      ? findProductProcessRouteItemList({ orderId: orderId.value })
      : findProcessRouteItemList({ routeId: routeId.value });
  listPromise
    .then(res => {
      tableData.value = res.data || [];
      tableLoading.value = false;
      // åˆ—表加载完成后初始化拖拽排序
      nextTick(() => {
        initSortable();
      });
    })
    .catch(err => {
      tableLoading.value = false;
      console.error("获取列表失败:", err);
      proxy?.$modal?.msgError("获取列表失败");
    });
};
// èŽ·å–å·¥åºåˆ—è¡¨
const getProcessList = () => {
  processList({})
    .then(res => {
      processOptions.value = res.data || [];
    })
    .catch(err => {
      console.error("获取工序失败:", err);
    });
};
// èŽ·å–å·¥è‰ºè·¯çº¿è¯¦æƒ…ï¼ˆä»Žè·¯ç”±å‚æ•°èŽ·å–ï¼‰
const getRouteInfo = () => {
  routeInfo.value = {
    processRouteCode: route.query.processRouteCode || '',
    productName: route.query.productName || '',
    model: route.query.model || '',
    bomNo: route.query.bomNo || '',
    description: route.query.description || ''
  };
};
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add';
  resetForm();
  dialogVisible.value = true;
};
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit';
  form.value = {
    id: row.id,
    routeId: routeId.value,
    processId: row.processId,
    productModelId: row.productModelId,
    productName: row.productName || "",
    model: row.model || "",
    unit: row.unit || "",
  };
  dialogVisible.value = true;
};
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该工艺路线项目?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      // ç”Ÿäº§è®¢å•下使用 productProcessRoute çš„删除接口(路由后拼接 id),其它情况使用工艺路线项目批量删除接口
      const deletePromise =
        pageType.value === 'order'
          ? deleteRouteItem(row.id)
          : batchDeleteProcessRouteItem([row.id]);
      deletePromise
        .then(() => {
          proxy?.$modal?.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy?.$modal?.msgError('删除失败');
        });
    })
    .catch(() => {});
};
// äº§å“é€‰æ‹©
const handleProductSelect = (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    form.value.productModelId = product.id;
    form.value.productName = product.productName;
    form.value.model = product.model;
    form.value.unit = product.unit || "";
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证
    formRef.value?.validateField('productModelId');
  }
};
// æäº¤
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      submitLoading.value = true;
      if (operationType.value === 'add') {
        // æ–°å¢žï¼šä¼ å•个对象,包含dragSort字段
        // dragSort = å½“前列表长度 + 1,表示新增记录排在最后
        const dragSort = tableData.value.length + 1;
        const isOrderPage = pageType.value === 'order';
        const addPromise = isOrderPage
          ? addRouteItem({
              productOrderId: orderId.value,
              productRouteId: routeId.value,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              dragSort,
            })
          : addOrUpdateProcessRouteItem({
              routeId: routeId.value,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              dragSort,
            });
        addPromise
          .then(() => {
            proxy?.$modal?.msgSuccess('新增成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy?.$modal?.msgError('新增失败');
          })
          .finally(() => {
            submitLoading.value = false;
          });
      } else {
        // ç¼–辑:生产订单下使用 productProcessRoute/updateRouteItem,其它情况使用工艺路线项目更新接口
        const isOrderPage = pageType.value === 'order';
        const updatePromise = isOrderPage
          ? addOrUpdateProductProcessRouteItem({
              id: form.value.id,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
            })
          : addOrUpdateProcessRouteItem({
              routeId: routeId.value,
              processId: form.value.processId,
              productModelId: form.value.productModelId,
              id: form.value.id,
            });
        updatePromise
          .then(() => {
            proxy?.$modal?.msgSuccess('修改成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy?.$modal?.msgError('修改失败');
          })
          .finally(() => {
            submitLoading.value = false;
          });
      }
    }
  });
};
// é‡ç½®è¡¨å•
const resetForm = () => {
  form.value = {
    id: undefined,
    routeId: routeId.value,
    processId: undefined,
    productModelId: undefined,
    productName: "",
    model: "",
    unit: "",
  };
  formRef.value?.resetFields();
};
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogVisible.value = false;
  resetForm();
};
// åˆå§‹åŒ–拖拽排序
const initSortable = () => {
  destroySortable();
  if (viewMode.value === 'table') {
    // è¡¨æ ¼è§†å›¾çš„æ‹–拽排序
    if (!tableRef.value) return;
    const tbody = tableRef.value.$el.querySelector('.el-table__body tbody') ||
        tableRef.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
    if (!tbody) return;
    tableSortable = new Sortable(tbody, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.el-table__row',
      filter: '.el-button, .el-select',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return;
        // é‡æ–°æŽ’序数组
        const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
        tableData.value.splice(evt.newIndex, 0, moveItem);
        // è®¡ç®—新的序号(dragSort从1开始)
        const newIndex = evt.newIndex;
        const dragSort = newIndex + 1;
        // è°ƒç”¨æŽ’序接口
        if (moveItem.id) {
          const isOrderPage = pageType.value === 'order';
          const sortPromise = isOrderPage
            ? sortRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              })
            : sortProcessRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              });
          sortPromise
            .then(() => {
              // æ›´æ–°æ‰€æœ‰è¡Œçš„dragSort
              tableData.value.forEach((item, index) => {
                if (item.id) {
                  item.dragSort = index + 1;
                }
              });
              proxy?.$modal?.msgSuccess('排序成功');
            })
            .catch((err) => {
              // æŽ’序失败,恢复原数组
              tableData.value.splice(newIndex, 1);
              tableData.value.splice(evt.oldIndex, 0, moveItem);
              proxy?.$modal?.msgError('排序失败');
              console.error("排序失败:", err);
            });
        }
      }
    });
  } else {
    // å¡ç‰‡è§†å›¾çš„æ‹–拽排序
    if (!cardsContainer.value) return;
    cardSortable = new Sortable(cardsContainer.value, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.process-card',
      filter: '.el-button',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex]) return;
        // é‡æ–°æŽ’序数组
        const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
        tableData.value.splice(evt.newIndex, 0, moveItem);
        // è®¡ç®—新的序号(dragSort从1开始)
        const newIndex = evt.newIndex;
        const dragSort = newIndex + 1;
        // è°ƒç”¨æŽ’序接口
        if (moveItem.id) {
          const isOrderPage = pageType.value === 'order';
          const sortPromise = isOrderPage
            ? sortRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              })
            : sortProcessRouteItem({
                id: moveItem.id,
                dragSort: dragSort
              });
          sortPromise
            .then(() => {
              // æ›´æ–°æ‰€æœ‰è¡Œçš„dragSort
              tableData.value.forEach((item, index) => {
                if (item.id) {
                  item.dragSort = index + 1;
                }
              });
              proxy?.$modal?.msgSuccess('排序成功');
            })
            .catch((err) => {
              // æŽ’序失败,恢复原数组
              tableData.value.splice(newIndex, 1);
              tableData.value.splice(evt.oldIndex, 0, moveItem);
              proxy?.$modal?.msgError('排序失败');
              console.error("排序失败:", err);
            });
        }
      }
    });
  }
};
// é”€æ¯æ‹–拽排序
const destroySortable = () => {
  if (tableSortable) {
    tableSortable.destroy();
    tableSortable = null;
  }
  if (cardSortable) {
    cardSortable.destroy();
    cardSortable = null;
  }
};
onMounted(() => {
  getRouteInfo();
  getList();
  getProcessList();
});
onUnmounted(() => {
  destroySortable();
});
</script>
<style scoped>
.card-container {
  padding: 20px 0;
}
.cards-wrapper {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding: 10px 0;
  min-height: 200px;
}
.cards-wrapper::-webkit-scrollbar {
  height: 8px;
}
.cards-wrapper::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 4px;
}
.cards-wrapper::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 4px;
}
.cards-wrapper::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8;
}
.process-card {
  flex-shrink: 0;
  width: 220px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 16px;
  display: flex;
  flex-direction: column;
  cursor: move;
  transition: all 0.3s;
}
.process-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  transform: translateY(-2px);
}
.card-header {
  text-align: center;
  margin-bottom: 12px;
}
.card-number {
  width: 36px;
  height: 36px;
  line-height: 36px;
  border-radius: 50%;
  background: #409eff;
  color: #fff;
  font-weight: bold;
  font-size: 16px;
  margin: 0 auto 8px;
}
.card-process-name {
  font-size: 14px;
  color: #333;
  font-weight: 500;
  word-break: break-all;
}
.card-content {
  flex: 1;
  margin-bottom: 12px;
  min-height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.product-info {
  font-size: 13px;
  color: #666;
  text-align: center;
  width: 100%;
}
.product-info.empty {
  color: #999;
  text-align: center;
  padding: 20px 0;
}
.product-name {
  margin-bottom: 6px;
  word-break: break-all;
  line-height: 1.5;
  text-align: center;
}
.product-model {
  color: #909399;
  font-size: 12px;
  word-break: break-all;
  line-height: 1.5;
  text-align: center;
}
.product-unit {
  margin-left: 4px;
  color: #409eff;
}
.card-footer {
  display: flex;
  justify-content: space-around;
  padding-top: 12px;
  border-top: 1px solid #f0f0f0;
}
.card-footer .el-button {
  padding: 0;
  font-size: 12px;
}
:deep(.sortable-ghost) {
  opacity: 0.5;
  background-color: #f5f7fa !important;
}
:deep(.sortable-drag) {
  opacity: 0.8;
}
/* è¡¨æ ¼è§†å›¾æ ·å¼ */
:deep(.el-table__row) {
  transition: background-color 0.2s;
  cursor: move;
}
:deep(.el-table__row:hover) {
  background-color: #f9fafc !important;
}
/* åŒºåŸŸæ ‡é¢˜æ ·å¼ */
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
  padding-left: 12px;
  position: relative;
  margin-bottom: 0;
}
.section-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 16px;
  background: #409eff;
  border-radius: 2px;
}
.section-actions {
  display: flex;
  align-items: center;
}
/* å·¥è‰ºè·¯çº¿ä¿¡æ¯å¡ç‰‡æ ·å¼ */
.route-info-card {
  margin-bottom: 20px;
  border: 1px solid #e4e7ed;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border-radius: 8px;
  overflow: hidden;
}
.route-info {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  padding: 4px;
}
.info-item {
  display: flex;
  flex-direction: column;
  background: #ffffff;
  border-radius: 6px;
  padding: 14px 16px;
  border: 1px solid #f0f2f5;
  transition: all 0.3s ease;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.info-item:hover {
  border-color: #409eff;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
  transform: translateY(-1px);
}
.info-item.full-width {
  grid-column: 1 / -1;
}
.info-label-wrapper {
  margin-bottom: 8px;
}
.info-label {
  display: inline-block;
  color: #909399;
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 2px 0;
  position: relative;
}
.info-label::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 20px;
  height: 2px;
  background: linear-gradient(90deg, #409eff, transparent);
  border-radius: 1px;
}
.info-value-wrapper {
  flex: 1;
}
.info-value {
  display: block;
  color: #303133;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.5;
  word-break: break-all;
}
</style>
src/views/productionManagement/productStructure/Detail/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,300 @@
<template>
  <div class="app-container">
    <PageHeader content="产品结构详情">
      <template #right-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="addItem">添加
        </el-button>
        <el-button v-if="!dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="dataValue.isEdit = true">编辑
        </el-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="cancelEdit">取消
        </el-button>
        <el-button v-if="!isOrderPage"
                   type="primary"
                   :loading="dataValue.loading"
                   @click="submit"
                   :disabled="!dataValue.isEdit">确认
        </el-button>
      </template>
    </PageHeader>
    <el-table
        :data="tableData"
        border
        :preserve-expanded-content="false"
        :default-expand-all="true"
        style="width: 100%"
    >
      <el-table-column type="expand">
        <template #default="props">
          <el-form ref="form"
                   :model="dataValue">
            <el-table :data="dataValue.dataList"
                      style="width: 100%">
              <el-table-column prop="productName"
                               label="产品"/>
              <el-table-column prop="model"
                               label="规格">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :prop="`dataList.${$index}.model`"
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择规格"
                               clearable
                               :disabled="!dataValue.isEdit"
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog($index) }">
                      <el-option v-if="row.model"
                                 :label="row.model"
                                 :value="row.model" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="processId"
                               label="消耗工序">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.processId`"
                                :rules="[{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                style="margin: 0">
                    <el-select v-model="row.processId"
                               placeholder="请选择"
                               filterable
                               clearable
                               style="width: 100%"
                               :disabled="!dataValue.isEdit">
                      <el-option v-for="item in dataValue.processOptions"
                                 :key="item.id"
                                 :label="item.name"
                                 :value="item.id" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unitQuantity"
                               label="单位产出所需数量">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.unitQuantity`"
                                :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column v-if="isOrderPage"
                               prop="demandedQuantity"
                               label="需求总量">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
                                :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unit"
                               label="单位">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.unit`"
                                :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input v-model="row.unit"
                              placeholder="请输入单位"
                              clearable
                              :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column label="操作" fixed="right" width="100">
                <template #default="{ row, $index }">
                  <el-button v-if="dataValue.isEdit"
                             type="danger"
                             text
                             @click="dataValue.dataList.splice($index, 1)">删除
                  </el-button>
                </template>
              </el-table-column>
            </el-table>
          </el-form>
        </template>
      </el-table-column>
      <el-table-column label="BOM编号" prop="bomNo" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" />
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           @confirm="handleProduct" />
  </div>
</template>
<script setup lang="ts">
import {
  computed,
  defineAsyncComponent,
  defineComponent,
  onMounted,
  reactive,
  ref,
} from "vue";
import { queryList, add } from "@/api/productionManagement/productStructure.js";
import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
import { list } from "@/api/productionManagement/productionProcess";
import { ElMessage } from "element-plus";
import {useRoute, useRouter} from "vue-router";
defineComponent({
  name: "StructureEdit",
});
const ProductSelectDialog = defineAsyncComponent(
    () => import("@/views/basicData/product/ProductSelectDialog.vue")
);
const form = ref();
const route = useRoute()
const router = useRouter()
const routeId = computed({
  get() {
    return route.query.id;
  },
  set(val) {
    emit('update:router', val)
  }
});
// ä»Žè·¯ç”±å‚数获取产品信息
const routeBomNo = computed(() => route.query.bomNo || '');
const routeProductName = computed(() => route.query.productName || '');
const routeProductModelName = computed(() => route.query.productModelName || '');
const routeOrderId = computed(() => route.query.orderId);
const pageType = computed(() => route.query.type);
const isOrderPage = computed(() => pageType.value === 'order' && routeOrderId.value);
const dataValue = reactive({
  dataList: [],
  productOptions: [],
  processOptions: [],
  showProductDialog: false,
  currentRowIndex: null,
  loading: false,
  isEdit: false,
});
const tableData = reactive([
  {
    productName: "",
    model: "",
    bomNo: "",
  }
])
const openDialog = index => {
  dataValue.currentRowIndex = index;
  dataValue.showProductDialog = true;
};
const fetchData = async () => {
  if (isOrderPage.value) {
    // è®¢å•情况:使用订单的产品结构接口
    const { data } = await listProcessBom({ orderId: routeOrderId.value });
    dataValue.dataList = data || [];
  } else {
    // éžè®¢å•情况:使用原来的接口
    const { data } = await queryList(routeId.value);
    dataValue.dataList = data || [];
  }
};
const fetchProcessOptions = async () => {
  const { data } = await list(routeId.value);
  dataValue.processOptions = data;
};
const handleProduct = row => {
  if (row?.length > 1) {
    ElMessage.error("只能选择一个产品");
  }
  dataValue.dataList[dataValue.currentRowIndex].productName =
      row[0].productName;
  dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
  dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
  dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
  dataValue.showProductDialog = false;
};
const submit = () => {
  form.value
      .validate(valid => {
        dataValue.loading = true;
        if (valid) {
          add({
            bomId: routeId.value,
            productStructureList: dataValue.dataList || [],
          }).then(res => {
            router.push({
              path: '/productionManagement/productionManagement/productStructure/index',
            })
            ElMessage.success("保存成功");
            dataValue.loading = false;
          });
        }
      })
      .finally(() => {
        dataValue.loading = false;
      });
};
const addItem = () => {
  dataValue.dataList.push({
    productName: "",
    productId: "",
    model: undefined,
    productModelId: undefined,
    processId: "",
    unitQuantity: 0,
    demandedQuantity: 0,
    unit: "",
  });
};
const cancelEdit = () => {
  dataValue.isEdit = false;
  dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
};
onMounted(() => {
  // ä»Žè·¯ç”±å‚数回显数据
  tableData[0].productName = routeProductName.value;
  tableData[0].model = routeProductModelName.value;
  tableData[0].bomNo = routeBomNo.value;
  // è®¢å•情况下禁用编辑
  if (isOrderPage.value) {
    dataValue.isEdit = false;
  }
  fetchData();
  fetchProcessOptions();
});
</script>
src/views/productionManagement/productStructure/StructureEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,311 @@
<template>
  <el-dialog v-model="visible"
             title="结构"
             width="1200"
             close-on-click-modal
             @close="visible = false">
    <el-button v-if="dataValue.isEdit"
               type="primary"
               @click="addItem"
               style="margin-bottom: 10px">添加
    </el-button>
    <el-button v-if="!dataValue.isEdit"
               type="primary"
               @click="dataValue.isEdit = true"
               style="margin-bottom: 10px">编辑
    </el-button>
    <el-button v-if="dataValue.isEdit"
               type="primary"
               @click="cancelEdit"
               style="margin-bottom: 10px">取消
    </el-button>
    <el-table
        :data="tableData"
        border
        :preserve-expanded-content="false"
        style="width: 100%"
    >
      <el-table-column type="expand">
        <template #default="props">
          <el-form ref="form"
                   :model="dataValue">
            <el-table :data="dataValue.dataList"
                      style="width: 100%">
              <el-table-column prop="productName"
                               label="产品"
                               width="150" />
              <el-table-column prop="model"
                               label="规格"
                               width="150">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :prop="`dataList.${$index}.model`"
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择产品"
                               clearable
                               :disabled="!dataValue.isEdit"
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog($index) }">
                      <el-option v-if="row.model"
                                 :label="row.model"
                                 :value="row.model" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="processId"
                               label="消耗工序"
                               width="150">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.processId`"
                                :rules="[{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                style="margin: 0">
                    <el-select v-model="row.processId"
                               placeholder="请选择"
                               filterable
                               clearable
                               style="width: 100%"
                               :disabled="!dataValue.isEdit">
                      <el-option v-for="item in dataValue.processOptions"
                                 :key="item.id"
                                 :label="item.name"
                                 :value="item.id" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unitQuantity"
                               label="单位产出所需数量"
                               width="150">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.unitQuantity`"
                                :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="demandedQuantity"
                               label="需求总量"
                               width="150">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.demandedQuantity`"
                                :rules="[{ required: true, message: '请输入需求总量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.demandedQuantity"
                                     :min="0"
                                     :precision="2"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="unit"
                               label="单位"
                               width="150">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.unit`"
                                :rules="[{ required: true, message: '请输入单位', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input v-model="row.unit"
                              placeholder="请输入单位"
                              clearable
                              :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column prop="diskQuantity"
                               label="盘数(盘)"
                               width="150">
                <template #default="{ row, $index }">
                  <el-form-item :prop="`dataList.${$index}.diskQuantity`"
                                :rules="[{ required: true, message: '请输入盘数', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.diskQuantity"
                                     :min="0"
                                     :precision="0"
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!dataValue.isEdit" />
                  </el-form-item>
                </template>
              </el-table-column>
              <el-table-column label="操作">
                <template #default="{ row, $index }">
                  <el-button type="danger"
                             text
                             @click="dataValue.dataList.splice($index, 1)">删除
                  </el-button>
                </template>
              </el-table-column>
            </el-table>
          </el-form>
        </template>
      </el-table-column>
      <el-table-column label="产品编码" prop="productCode" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" />
      <el-table-column label="单位" prop="unit" />
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           @confirm="handleProduct" />
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary"
                   :loading="dataValue.loading"
                   @click="submit"
                   :disabled="!dataValue.isEdit">
          ç¡®è®¤
        </el-button>
        <el-button @click="visible = false">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
  import {
    computed,
    defineAsyncComponent,
    defineComponent,
    onMounted,
    reactive,
    ref,
  } from "vue";
  import { queryList, add } from "@/api/productionManagement/productStructure.js";
  import { list } from "@/api/productionManagement/productionProcess";
  import { ElMessage } from "element-plus";
  defineComponent({
    name: "StructureEdit",
  });
  const ProductSelectDialog = defineAsyncComponent(
    () => import("@/views/basicData/product/ProductSelectDialog.vue")
  );
  const form = ref();
  const props = defineProps({
    showModel: {
      type: Boolean,
      default: false,
    },
    record: {
      type: Object,
      required: true,
    },
  });
  const emits = defineEmits(["update:showModel"]);
  const visible = computed({
    get() {
      return props.showModel;
    },
    set(val) {
      emits("update:showModel", val);
    },
  });
  const dataValue = reactive({
    dataList: [],
    productOptions: [],
    processOptions: [],
    showProductDialog: false,
    currentRowIndex: null,
    loading: false,
    isEdit: false,
  });
  const tableData = [
    {
      productName: props.record.productName,
      model: props.record.model,
      unit: props.record.unit,
      productCode: props.record.productCode,
    }
  ]
  const openDialog = index => {
    dataValue.currentRowIndex = index;
    dataValue.showProductDialog = true;
  };
  const fetchData = async () => {
    const { data } = await queryList(props.record.id);
    dataValue.dataList = data;
  };
  const fetchProcessOptions = async () => {
    const { data } = await list(props.record.id);
    dataValue.processOptions = data;
  };
  const handleProduct = row => {
    if (row?.length > 1) {
      ElMessage.error("只能选择一个产品");
    }
    dataValue.dataList[dataValue.currentRowIndex].productName =
      row[0].productName;
    dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
    dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
    dataValue.showProductDialog = false;
  };
  const submit = () => {
    form.value
      .validate(valid => {
        dataValue.loading = true;
        if (valid) {
          add({
            parentId: props.record.id,
            productStructureList: dataValue.dataList || [],
          }).then(res => {
            ElMessage.success("保存成功");
            visible.value = false;
            dataValue.loading = false;
          });
        }
      })
      .finally(() => {
        dataValue.loading = false;
      });
  };
  const addItem = () => {
    dataValue.dataList.push({
      productName: "",
      productId: "",
      model: undefined,
      productModelId: undefined,
      processId: "",
      unitQuantity: 0,
      demandedQuantity: 0,
      unit: "",
      diskQuantity: 0,
    });
  };
  const cancelEdit = () => {
    dataValue.isEdit = false;
    dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
  };
  onMounted(() => {
    fetchData();
    fetchProcessOptions();
  });
</script>
src/views/productionManagement/productStructure/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,340 @@
<template>
  <div class="app-container">
    <div style="text-align: right; margin-bottom: 10px;">
      <el-button type="primary" @click="handleAdd">新增</el-button>
      <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button>
    </div>
    <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
    >
      <template #detail="{row}">
        <el-button
            type="primary"
            text
            @click="showDetail(row)">{{ row.bomNo }}
        </el-button>
      </template>
    </PIMTable>
    <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow"/>
    <!-- æ–°å¢ž/编辑弹窗 -->
    <el-dialog
        v-model="dialogVisible"
        :title="operationType === 'add' ? '新增BOM' : '编辑BOM'"
        width="600px"
        @close="closeDialog"
    >
      <el-form
          ref="formRef"
          :model="form"
          :rules="rules"
          label-width="120px"
      >
        <el-form-item label="产品名称" prop="productModelId">
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ form.productName || '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="版本号" prop="version">
          <el-input v-model="form.version" placeholder="请输入版本号" clearable />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input
              v-model="form.remark"
              type="textarea"
              :rows="3"
              placeholder="请输入备注"
              clearable
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="closeDialog">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
    <!-- äº§å“é€‰æ‹©å¼¹çª— -->
    <ProductSelectDialog
        v-model="showProductSelectDialog"
        @confirm="handleProductSelect"
        single
    />
  </div>
</template>
<script setup>
import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
import { listPage, add, update, batchDelete } from "@/api/productionManagement/productBom.js";
import { useRouter } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
const router = useRouter()
const { proxy } = getCurrentInstance()
const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
const tableColumn = ref([
  {
    label: "BOM编号",
    prop: "bomNo",
    dataType: 'slot',
    slot: "detail",
    minWidth: 140
  },
  {
    label: "产品名称",
    prop: "productName",
    minWidth: 160
  },
  {
    label: "规格型号",
    prop: "productModelName",
    minWidth: 140
  },
  {
    label: "版本号",
    prop: "version",
    width: 100
  },
  {
    label: "备注",
    prop: "remark",
    minWidth: 160
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          handleEdit(row)
        }
      },
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          handleDelete(row)
        }
      }
    ]
  }
]);
const tableData = ref([]);
const tableLoading = ref(false);
const showEdit = ref(false);
const selectedRows = ref([]);
const currentRow = ref({});
const dialogVisible = ref(false);
const operationType = ref('add'); // add | edit
const formRef = ref(null);
const showProductSelectDialog = ref(false);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const data = reactive({
  form: {
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: ""
  },
  rules: {
    productModelId: [{ required: true, message: "请选择产品", trigger: "change" }],
    version: [{ required: true, message: "请输入版本号", trigger: "blur" }]
  }
});
const { form, rules } = toRefs(data);
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// åˆ†é¡µ
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
// æŸ¥è¯¢åˆ—表
const getList = () => {
  tableLoading.value = true;
  listPage({
    current: page.current,
    size: page.size,
  })
    .then((res) => {
      const records = res?.data?.records || [];
      tableData.value = records;
      page.total = res?.data?.total || 0;
    })
    .catch((err) => {
      console.error("获取列表失败:", err);
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add';
  Object.assign(form.value, {
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: ""
  });
  dialogVisible.value = true;
};
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit';
  Object.assign(form.value, {
    id: row.id,
    productName: row.productName || "",
    productModelName: row.productModelName || "",
    productModelId: row.productModelId || "",
    remark: row.remark || "",
    version: row.version || ""
  });
  dialogVisible.value = true;
};
// åˆ é™¤ï¼ˆå•条)
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该BOM?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      batchDelete([row.id])
        .then(() => {
          proxy.$modal.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy.$modal.msgError('删除失败');
        });
    })
    .catch(() => {});
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$modal.msgWarning('请选择数据');
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      batchDelete(ids)
        .then(() => {
          proxy.$modal.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy.$modal.msgError('删除失败');
        });
    })
    .catch(() => {});
};
// äº§å“é€‰æ‹©
const handleProductSelect = (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    form.value.productModelId = product.id;
    form.value.productName = product.productName;
    form.value.productModelName = product.model;
  }
  showProductSelectDialog.value = false;
};
// æäº¤è¡¨å•
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      const payload = { ...form.value };
      if (operationType.value === 'add') {
        add(payload)
          .then(() => {
            proxy.$modal.msgSuccess('新增成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError('新增失败');
          });
      } else {
        update(payload)
          .then(() => {
            proxy.$modal.msgSuccess('修改成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError('修改失败');
          });
      }
    }
  });
};
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogVisible.value = false;
  formRef.value?.resetFields();
};
// æŸ¥çœ‹è¯¦æƒ…
const showDetail = (row) => {
  router.push({
    path: '/productionManagement/productStructureDetail',
    query: {
      id: row.id,
      bomNo: row.bomNo || '',
      productName: row.productName || '',
      productModelName: row.productModelName || ''
    }
  });
};
onMounted(() => {
  getList();
});
</script>
src/views/productionManagement/productionCosting/index.vue
@@ -14,6 +14,15 @@
                    clearable
                    prefix-icon="Search"
                />
                <span class="search_title ml10">合同号:</span>
                <el-input
                    v-model="searchForm.salesContractNo"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
@@ -56,6 +65,26 @@
        prop: "schedulingUserName",
        width: 90,
    },
    {
        label: "合同号",
        prop: "salesContractNo",
        width: 220,
    },
    // {
    //     label: "客户合同号",
    //     prop: "customerContractNo",
    //     width: 250,
    // },
    {
        label: "客户名称",
        prop: "customerName",
        width: 250,
    },
    // {
    //     label: "项目名称",
    //     prop: "projectName",
    //     width:300
    // },
    {
        label: "产品大类",
        prop: "productCategory",
@@ -101,6 +130,7 @@
const data = reactive({
    searchForm: {
        schedulingUserName: "",
        salesContractNo: "",
        entryDate: [
            dayjs().format("YYYY-MM-DD"),
            dayjs().add(1, "day").format("YYYY-MM-DD"),
@@ -151,7 +181,7 @@
        type: "warning",
    })
        .then(() => {
            proxy.download("/basic/customer/export", {}, "生产核算.xlsx");
            proxy.download("/salesLedger/productionAccounting/export", {}, "生产核算.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
src/views/productionManagement/productionDispatching/components/autoDispatchDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="自动派工"
        width="80%"
        @close="closeDia"
    >
      <el-form :model="form" label-width="140px" label-position="top" ref="formRef">
        <el-divider content-position="left">派工列表</el-divider>
        <el-table
            :data="dispatchList"
            border
            style="width: 100%; margin-top: 20px;"
            :row-class-name="tableRowClassName"
        >
          <el-table-column label="序号" type="index" width="60" align="center" />
          <el-table-column label="合同号" prop="salesContractNo" width="200" />
          <el-table-column label="客户名称" prop="customerName" width="200" />
          <!-- <el-table-column label="项目名称" prop="projectName" width="250" /> -->
          <el-table-column label="产品大类" prop="productCategory" width="150" />
          <el-table-column label="规格型号" prop="specificationModel" width="200" />
          <el-table-column label="绑定机器" prop="speculativeTradingName" width="120" />
          <el-table-column label="总数量" prop="quantity" width="100" align="right" />
          <el-table-column label="已排产" prop="schedulingNum" width="100" align="right" fixed="right" />
          <el-table-column label="待排产" prop="pendingQuantity" width="100" align="right" fixed="right" />
          <el-table-column label="本次排产" width="150" align="center" fixed="right">
            <template #default="{ row }">
              <el-input-number
                  v-model="row.schedulingNum"
                  :min="0"
                  :max="row.pendingQuantity"
                  :step="1"
                  :precision="0"
                  size="small"
                  style="width: 120px"
                  @change="(value) => changeCurrentNum(value, row)"
              />
            </template>
          </el-table-column>
        </el-table>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认派工</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, reactive, toRefs, computed} from "vue";
import {productionDispatch, productionDispatchList} from "@/api/productionManagement/productionOrder.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
  form: {},
  dispatchList: [], // æ´¾å·¥åˆ—表数据
});
const { form, dispatchList } = toRefs(data);
// è¡¨æ ¼è¡Œæ ·å¼
const tableRowClassName = ({ rowIndex }) => {
  if (rowIndex % 2 === 1) {
    return 'even-row'
  }
  return ''
}
// ä¿®æ”¹æœ¬æ¬¡æŽ’产数量
const changeCurrentNum = (value, row) => {
  if (value > row.pendingQuantity) {
    row.schedulingNum = row.pendingQuantity
    proxy.$modal.msgWarning('排产数量不可大于待排产数量')
  }
}
// æ‰“开弹框
const openDialog = (type, rows) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  // å¤„理传入的数据
  dispatchList.value = rows.map(row => ({
    ...row,
    schedulingNum: 0, // åˆå§‹åŒ–本次排产数量为0
    pendingQuantity: (Number(row.quantity) || 0) - (Number(row.schedulingNum) || 0) // è®¡ç®—待排产数量
  }))
}
// æäº¤è¡¨å•
const submitForm = () => {
  // æ£€æŸ¥æ˜¯å¦æœ‰æŽ’产数据
  const hasSchedulingData = dispatchList.value.some(item => item.schedulingNum > 0)
  if (!hasSchedulingData) {
    proxy.$modal.msgWarning('请至少为一条记录设置排产数量')
    return
  }
  // æž„造提交数据 - ç›´æŽ¥ä¼ é€’数组,不过滤
  const submitData = dispatchList.value
  console.log('提交自动派工数据:', submitData)
  // è°ƒç”¨API(这里需要根据实际接口调整)
  productionDispatchList(submitData).then(res => {
    proxy.$modal.msgSuccess(res.msg);
    closeDia();
  }).catch(err => {
    proxy.$modal.msgError("派工失败");
    console.error('派工失败:', err);
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  dispatchList.value = []
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
<style scoped>
:deep(.even-row) {
  background-color: #fafafa;
}
:deep(.el-table .cell) {
  padding: 8px 12px;
}
:deep(.el-table th) {
  background-color: #f5f7fa;
  color: #606266;
  font-weight: 600;
}
</style>
src/views/productionManagement/productionDispatching/components/formDia.vue
@@ -7,29 +7,43 @@
        @close="closeDia"
    >
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
<!--          <el-col :span="12">-->
<!--            <el-form-item label="项目名称:" prop="projectName">-->
<!--              <el-input v-model="form.projectName" placeholder="请输入" clearable disabled/>-->
<!--            </el-form-item>-->
<!--          </el-col>-->
        <!-- <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input v-model="form.projectName" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="产品大类:" prop="productCategory">
              <el-input v-model="form.productCategory" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
        </el-row> -->
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="总数量:" prop="quantity">
                            <el-input v-model="form.quantity" placeholder="请输入" clearable disabled/>
            <el-form-item label="规格型号:" prop="specificationModel">
              <el-input v-model="form.specificationModel" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="绑定机器:" prop="speculativeTradingName">
              <el-input v-model="form.speculativeTradingName" placeholder="自动获取" clearable disabled/>
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="总数量:" prop="quantity">
              <el-input v-model="form.quantity" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
                        <el-form-item label="待排产数量:" prop="pendingQuantity">
                            <el-input v-model="form.pendingQuantity" placeholder="请输入" clearable disabled/>
                        </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="本次排产数量:" prop="schedulingNum">
                            <el-input-number
@@ -44,6 +58,11 @@
                            />
                        </el-form-item>
                    </el-col>
          <el-col :span="12">
            <el-form-item label="产品大类:" prop="productCategory">
              <el-input v-model="form.productCategory" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
@@ -52,6 +71,9 @@
                                v-model="form.schedulingUserId"
                                placeholder="选择人员"
                                style="width: 100%;"
                filterable
                default-first-option
                :reserve-keyword="false"
                            >
                                <el-option
                                    v-for="user in userList"
@@ -89,7 +111,6 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
import useUserStore from "@/store/modules/user.js";
@@ -103,11 +124,13 @@
  form: {
        projectName: "",
        productCategory: "",
        specificationModel: "", // è§„格型号
        quantity: "",
        schedulingNum: "",
        schedulingUserId: "",
        schedulingDate: "",
        pendingQuantity: "",
        speculativeTradingName: "", // ç»‘定机器名称
  },
  rules: {
        schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
@@ -118,6 +141,7 @@
const { form, rules } = toRefs(data);
const userList = ref([])
const userStore = useUserStore()
// æ‰“开弹框
const openDialog = (type, row) => {
@@ -143,7 +167,6 @@
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
            form.value.salesLedgerProductId = form.value.id
            productionDispatch(form.value).then(res => {
                proxy.$modal.msgSuccess("提交成功");
                closeDia();
src/views/productionManagement/productionDispatching/index.vue
@@ -1,5 +1,32 @@
<template>
    <div class="app-container">
        <!-- ç‚’机1-4 å±•示(总量 / æ­£åœ¨ç”Ÿäº§é‡ / ç©ºä½™é‡ï¼‰ -->
        <div class="machines-grid">
            <div v-for="machine in machines" :key="machine.id" class="machine-card">
                <div class="machine-title">{{ machine.name }}</div>
                <div class="machine-metrics">
                <div class="machine-control">
                    <span>总量(kg):</span>
                    <el-input-number v-model="machineData[machine.name].workLoad" :min="0" :step="1" size="small" />
                </div>
                <div><span> é¢„计投入量(kg):</span><span>{{ machineData[machine.name].currentWorkLoad }}</span></div>
                <div><span>空余工作量(kg):</span><span>{{ machineData[machine.name].vacant }}</span></div>
            </div>
            </div>
            <div class="save-button-container">
                <div class="loss-rate-container">
                    <span class="loss-rate-label">损耗率(%):</span>
                    <el-select v-model="rate" placeholder="请选择损耗率" style="width: 120px" size="small">
                        <el-option label="6" :value="6" />
                        <el-option label="7" :value="7" />
                        <el-option label="8" :value="8" />
                        <el-option label="9" :value="9" />
                        <el-option label="10" :value="10" />
                    </el-select>
                </div>
                <el-button type="primary" @click="saveMachineTotals" size="small">保存设置</el-button>
            </div>
        </div>
        <div class="search_form">
            <div>
                <span class="search_title">客户名称:</span>
@@ -11,22 +38,38 @@
                    clearable
                    prefix-icon="Search"
                />
                <span class="search_title ml10">项目名称:</span>
                <span class="search_title ml10">合同号:</span>
                <el-input
                    v-model="searchForm.projectName"
                    v-model="searchForm.salesContractNo"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search"
                />
<!--                <span class="search_title ml10">项目名称:</span>-->
<!--                <el-input-->
<!--                    v-model="searchForm.projectName"-->
<!--                    style="width: 240px"-->
<!--                    placeholder="请输入"-->
<!--                    @change="handleQuery"-->
<!--                    clearable-->
<!--                    prefix-icon="Search"-->
<!--                />-->
                <span class="search_title ml10">录入日期:</span>
                <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                placeholder="请选择" clearable @change="changeDaterange" />
                <el-checkbox
                    style="margin-left: 10px"
                    v-model="searchForm.status"
                    label="不显示待排数量为0"
                    @change="handleQuery"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">生产派工</el-button>
            <el-button type="success" @click="openAutoDispatch">自动派工</el-button>
                <el-button @click="handleOut">导出</el-button>
            </div>
        </div>
@@ -44,33 +87,55 @@
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <auto-dispatch-dia ref="autoDispatchDia" @close="handleQuery"></auto-dispatch-dia>
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch} from "vue";
import FormDia from "@/views/productionManagement/productionDispatching/components/formDia.vue";
import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
import AutoDispatchDia from "@/views/productionManagement/productionDispatching/components/autoDispatchDia.vue";
import dayjs from "dayjs";
import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js";
import { ElMessageBox } from "element-plus";
const data = reactive({
    searchForm: {
        customerName: "",
        salesContractNo: "",
        projectName: "",
        entryDate: null, // å½•入日期
        entryDateStart: undefined,
        entryDateEnd: undefined,
        status: true,
        entryDate: [dayjs().format("YYYY-MM-DD"), dayjs().format("YYYY-MM-DD")], // å½•入日期,默认当天
        entryDateStart: dayjs().format("YYYY-MM-DD"),
        entryDateEnd: dayjs().format("YYYY-MM-DD"),
    },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
    {
        label: "合同号",
        prop: "salesContractNo",
        width: 220,
    },
    {
        label: "客户名称",
        prop: "customerName",
        width: 250,
    },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 160,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 120,
    },
    {
        label: "绑定机器",
        prop: "speculativeTradingName",
        width: 160,
    },
    {
        label: "单位",
@@ -79,7 +144,34 @@
    },
    {
        label: "录入日期",
        prop: "registerDate",
        prop: "entryDate",
        width: 120,
    },
    {
        label: "状态",
        prop: "status",
        dataType: "tag",
        formatType: (params) => {
            if (params == '生产中') {
                return "warning";
            } else if (params == '未开始') {
                return "danger";
            } else {
                return "success";
            }
        },
    },
    {
        label: "生产进度",
        prop: "progress",
        formatData: (cellValue) => {
            // å¦‚果值为空或undefined,显示空字符串
            if (cellValue === null || cellValue === undefined || cellValue === '') {
                return '';
            }
            // ç›´æŽ¥åœ¨æ•°å­—后面添加百分号
            return `${cellValue}%`;
        }
    },
    {
        label: "数量",
@@ -88,10 +180,13 @@
    {
        label: "排产数量",
        prop: "schedulingNum",
        width: 100,
    },
    {
        label: "待排数量",
        prop: "pendingQuantity",
        width: 100,
        fixed: 'right',
    },
]);
const tableData = ref([]);
@@ -103,7 +198,140 @@
    total: 0,
});
const formDia = ref()
const autoDispatchDia = ref()
const { proxy } = getCurrentInstance()
// ç‚’机数据
const machineData = reactive({
    "炒机1": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
    "炒机2": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
    "炒机3": { workLoad: 0, currentWorkLoad: 0, vacant: 0 },
    "炒机4": { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
})
// ç‚’机配置数组
const machines = [
    { id: 1, name: '炒机1' },
    { id: 2, name: '炒机2' },
    { id: 3, name: '炒机3' },
    { id: 4, name: '炒机4' }
]
// ä¿å­˜ç‚’机总量设置
const saveMachineTotals = () => {
    // éªŒè¯æŸè€—率是否已选择
    if (rate.value === null || rate.value === undefined || isNaN(rate.value)) {
        proxy.$message.warning('请选择损耗率');
        return;
    }
    // æž„造保存数据数组,使用machines数组循环构建
    const saveData = machines.map(machine => {
        const saveItem = {
            name: machine.name, // ç‚’机名称
            workLoad: machineData[machine.name].workLoad,  // æ€»é‡
            currentWorkLoad: machineData[machine.name].currentWorkLoad, //  é¢„计投入量
            vacant: machineData[machine.name].vacant   // ç©ºä½™é‡
        };
        // å¦‚果是修改操作,需要传递id字段
        if (hasQueryData.value) {
            const queryData = getMachineQueryData(machine.id);
            if (queryData && queryData.id) {
                saveItem.id = queryData.id;
            }
        }
        return saveItem;
    });
    // æž„造损耗率数据
    const rateData = {
        rate: rate.value
    };
    // å¦‚果有ID,说明是修改操作
    if (rateId.value) {
        rateData.id = rateId.value;
    }
    // æ ¹æ®æ˜¯å¦æœ‰æŸ¥è¯¢æ•°æ®å†³å®šè°ƒç”¨æ–°å¢žæŽ¥å£è¿˜æ˜¯ä¿®æ”¹æŽ¥å£
    const saveApi = hasQueryData.value ? updateSpeculatTrading : addSpeculatTrading;
    const successMessage = hasQueryData.value ? '炒机设置修改成功' : '炒机设置新增成功';
    // æ ¹æ®æ˜¯å¦æœ‰ID决定调用新增接口还是修改接口
    const rateApi = rateId.value ? updateLossRate : addLossRate;
    const rateSuccessMessage = rateId.value ? '损耗率修改成功' : '损耗率新增成功';
    // å¹¶è¡Œè°ƒç”¨ä¸¤ä¸ªæŽ¥å£
    Promise.all([
        saveApi(saveData),
        rateApi(rateData)
    ]).then(([saveRes, rateRes]) => {
        proxy.$message.success(successMessage);
        proxy.$message.success(rateSuccessMessage);
        // ä¿å­˜æˆåŠŸåŽï¼Œè®¾ç½®hasQueryData为true,下次保存将调用修改接口
        if (!hasQueryData.value) {
            hasQueryData.value = true;
        }
        // å¦‚果返回了ID,保存起来
        if (rateRes && rateRes.data && rateRes.data.id) {
            rateId.value = rateRes.data.id;
        }
        // ä¿å­˜æˆåŠŸåŽé‡æ–°è°ƒç”¨æŸ¥è¯¢é¡µé¢
        getList();
    }).catch(err => {
        proxy.$message.error('保存失败');
        console.error('保存失败:', err);
    });
}
// èŽ·å–ç‚’æœºæŸ¥è¯¢æ•°æ®
const machineQueryData = ref([]);
const getMachineQueryData = (machineId) => {
    return machineQueryData.value.find(item => item.id === machineId);
};
const getMachineIndex = (item) => {
    // å…¼å®¹å¤šç§å­—段命名,返回 1-4 ä¹‹ä¸€ï¼Œå¦åˆ™è¿”回 0(未知)
    const candidates = [item.machineId, item.machineNo, item.machine, item.deviceNo, item.deviceId]
    for (const v of candidates) {
        if (v === undefined || v === null) continue
        const n = Number(String(v).replace(/[^\d]/g, "")) // æŠ½å–æ•°å­—
        if ([1,2,3,4].includes(n)) return n
    }
    return 0
}
const computeTodaySummary = () => {
    const todayStr = dayjs().format("YYYY-MM-DD")
    // é‡ç½®æ‰€æœ‰ç‚’机数据
    machines.forEach(machine => {
        machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
    })
    tableData.value.forEach(item => {
        // ä»…统计当天
        const isToday = dayjs(item.entryDate).format("YYYY-MM-DD") === todayStr
        if (!isToday) return
        // ä½¿ç”¨æ­£ç¡®çš„字段名:workLoad(炒机工作量), currentWorkLoad(炒机正在工作量)
        const workLoad = Number(item.workLoad) || 0
        const currentWorkLoad = Number(item.currentWorkLoad) || 0
        const machineName = item.speculativeTradingName || '炒机1'
        if (machineData[machineName]) {
            machineData[machineName].workLoad += workLoad
            machineData[machineName].currentWorkLoad += currentWorkLoad
            machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad
        }
    })
}
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -111,6 +339,56 @@
    page.current = 1;
    getList();
};
// æ˜¯å¦æœ‰æŸ¥è¯¢æ•°æ®
const hasQueryData = ref(false)
// æŸè€—率
const rate = ref(6)
// æŸè€—率ID
const rateId = ref(null)
// èŽ·å–ç‚’æœºæ­£åœ¨å·¥ä½œé‡æ•°æ®
const getMachineProductionData = () => {
    schedulingList().then((res) => {
        // å¤„理炒机正在工作量数据
        if (res.data && Array.isArray(res.data)) {
            // è®¾ç½®æ˜¯å¦æœ‰æŸ¥è¯¢æ•°æ®
            hasQueryData.value = res.data.length > 0
            // ä¿å­˜æŸ¥è¯¢æ•°æ®åˆ°machineQueryData
            machineQueryData.value = res.data;
            // é‡ç½®æ‰€æœ‰ç‚’机数据
            machines.forEach(machine => {
                machineData[machine.name] = { workLoad: 0, currentWorkLoad: 0, vacant: 0 }
            });
            // éåŽ†æ•°æ®ï¼Œæ ¹æ®æŸ¥è¯¢è¿”å›žçš„æ•°æ®ç»“æž„å¤„ç†
            res.data.forEach(item => {
                // æ ¹æ®name字段确定炒机
                const machineName = item.name || '炒机1';
                if (machineData[machineName]) {
                    // å¦‚果查询数据中有workLoad,则初始化炒机总量
                    if (item.workLoad !== null && item.workLoad !== undefined) {
                        machineData[machineName].workLoad = Number(item.workLoad) || 0;
                    }
                    // å¦‚果查询数据中有currentWorkLoad,则设置正在工作量
                    if (item.currentWorkLoad !== null && item.currentWorkLoad !== undefined) {
                        machineData[machineName].currentWorkLoad = Number(item.currentWorkLoad) || 0;
                    }
                    // è®¡ç®—空余工作量
                    machineData[machineName].vacant = machineData[machineName].workLoad - machineData[machineName].currentWorkLoad;
                }
            });
        }
    }).catch(err => {
        console.error('获取炒机正在工作量数据失败:', err);
    });
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
@@ -134,14 +412,38 @@
    schedulingListPage(params).then((res) => {
        tableLoading.value = false;
        // å¤„理每条数据,增加pendingQuantity字段
        tableData.value = res.data.data.records.map(item => ({
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
        }));
        page.total = res.data.data.total;
        page.total = res.data.total;
        computeTodaySummary()
        // åŒæ—¶èŽ·å–ç‚’æœºæ­£åœ¨å·¥ä½œé‡æ•°æ®
        getMachineProductionData();
        // èŽ·å–æŸè€—çŽ‡æ•°æ®
        getLossRateData();
    }).catch(() => {
        tableLoading.value = false;
    })
};
// èŽ·å–æŸè€—çŽ‡æ•°æ®
const getLossRateData = () => {
    getLossRate().then((res) => {
        const data = res.data || res;
        if (data && data.rate !== undefined && data.rate !== null) {
            rate.value = Number(data.rate); // ç¡®ä¿è½¬æ¢ä¸ºæ•°å­—
            rateId.value = data.id || null;
        } else {
            rate.value = 6;
            rateId.value = null;
        }
    }).catch(err => {
        console.error('获取损耗率数据失败:', err);
        rate.value = 6;
        rateId.value = null;
    });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
@@ -163,6 +465,26 @@
    })
};
// æ‰“开自动派工弹框
const openAutoDispatch = () => {
    if (selectedRows.value.length === 0) {
        proxy.$message.error("请选择至少一条数据");
        return;
    }
    // è¿‡æ»¤æŽ‰å¾…排产数量为0的数据
    const validRows = selectedRows.value.filter(row => row.pendingQuantity > 0);
    if (validRows.length === 0) {
        proxy.$message.warning("选中的数据无需派工");
        return;
    }
    nextTick(() => {
        autoDispatchDia.value?.openDialog('auto', validRows)
    })
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -171,7 +493,7 @@
        type: "warning",
    })
        .then(() => {
            proxy.download("/productionOrder/exportOne", {}, "生产派工.xlsx");
            proxy.download("/salesLedger/scheduling/exportOne", {}, "生产派工.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
@@ -180,7 +502,129 @@
onMounted(() => {
    getList();
    getLossRateData();
});
</script>
<style scoped></style>
<style scoped>
.summary-bar{
    display: flex;
    gap: 16px;
    margin: 10px 0 16px 0;
}
.summary-item{
    background: #f5f7fa;
    border: 1px solid #ebeef5;
    border-radius: 6px;
    padding: 10px 16px;
    min-width: 160px;
}
.summary-label{
    color: #909399;
    font-size: 12px;
    margin-bottom: 6px;
}
.summary-value{
    color: #303133;
    font-size: 20px;
    font-weight: 600;
}
.summary-control{
    display: flex;
    align-items: center;
    height: 28px;
}
.machines-grid{
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
    margin-bottom: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 1px solid #e9ecef;
}
.machine-card{
    border: 1px solid #dee2e6;
    border-radius: 8px;
    padding: 16px;
    background: #fff;
    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
    transition: all 0.3s ease;
}
.machine-card:hover{
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.machine-title{
    font-weight: 600;
    font-size: 16px;
    margin-bottom: 12px;
    color: #2c3e50;
    text-align: center;
    padding-bottom: 8px;
    border-bottom: 2px solid #3498db;
}
.machine-metrics{
    display: flex;
    flex-direction: column;
    gap: 10px;
    color: #495057;
}
.machine-control{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 0;
    border-bottom: 1px solid #f1f3f4;
}
.machine-control span{
    font-size: 14px;
    white-space: nowrap;
    color: #6c757d;
    font-weight: 500;
}
.machine-metrics > div:not(.machine-control) {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 4px 0;
    font-size: 14px;
}
.machine-metrics > div:not(.machine-control) span:first-child {
    color: #6c757d;
}
.machine-metrics > div:not(.machine-control) span:last-child {
    font-weight: 600;
    color: #2c3e50;
}
.save-button-container{
    grid-column: 1 / -1;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 16px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid #e9ecef;
}
.loss-rate-container{
    display: flex;
    align-items: center;
    gap: 8px;
}
.loss-rate-label{
    font-size: 14px;
    color: #6c757d;
    font-weight: 500;
    white-space: nowrap;
}
</style>
src/views/productionManagement/productionOrder/index.vue
@@ -1,391 +1,301 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">产品大类:</span>
                <el-tree-select
                    v-model="searchForm.productCategory"
                    :data="productOptions"
                    placeholder="请选择"
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName"
                    placeholder="请输入"
                    clearable
                    check-strictly
                    :render-after-expand="false"
                    style="width: 240px"
                    @change="handleQuery"
                />
                <span class="search_title ml10">录入日期:</span>
                <el-date-picker v-model="searchForm.registerDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                placeholder="请选择" clearable @change="changeDaterange" />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="合同号:">
          <el-input v-model="searchForm.salesContractNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productCategory"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="规格:">
          <el-input v-model="searchForm.specificationModel"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
            <div>
                <el-button type="primary" @click="openDialog('create')">新增订单</el-button>
                <el-button @click="handleOut">导出</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"
            >
                <template #action="{ row }">
                    <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
                    <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
                @pagination="pagination">
        <template #completionStatus="{ row }">
          <el-progress
            :percentage="toProgressPercentage(row?.completionStatus)"
            :color="progressColor(toProgressPercentage(row?.completionStatus))"
            :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
          />
                </template>
            </PIMTable>
        </div>
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="40%" @close="closeDialog">
        <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
            <el-form-item label="录入日期" prop="registerDate">
                <el-date-picker v-model="form.registerDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="请选择录入日期" style="width: 100%"/>
            </el-form-item>
            <el-form-item label="产品大类" prop="productCategory">
                <el-tree-select
                    v-model="form.productCategory"
                    :data="productOptions"
                    placeholder="请选择产品大类"
                    clearable
                    check-strictly
                    :render-after-expand="false"
                    style="width: 100%"
                    @change="handleCategoryChange"
                />
            </el-form-item>
            <el-form-item label="规格型号" prop="productModelId">
                <el-select v-model="form.productModelId" placeholder="请选择规格型号" style="width: 100%" @change="handleModelChange">
                    <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id"/>
    <el-dialog v-model="bindRouteDialogVisible"
               title="绑定工艺路线"
               width="500px">
      <el-form label-width="90px">
        <el-form-item label="工艺路线">
          <el-select v-model="bindForm.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>
            </el-form-item>
            <el-form-item label="单位" prop="unit">
                <el-input v-model="form.unit" placeholder="自动带出" disabled/>
            </el-form-item>
            <el-form-item label="数量" prop="quantity">
                <el-input-number v-model="form.quantity" :min="0" :step="0.1" style="width: 100%"/>
            </el-form-item>
        </el-form>
        <template #footer>
            <div class="dialog-footer">
                <el-button type="primary" @click="submitForm">确认</el-button>
                <el-button @click="closeDialog">取消</el-button>
            </div>
        <span class="dialog-footer">
          <el-button @click="bindRouteDialogVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary"
                     :loading="bindRouteSaving"
                     @click="handleBindRouteConfirm">ç¡® è®¤</el-button>
        </span>
        </template>
    </el-dialog>
    </div>
</template>
<script setup>
import {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
  import { onMounted, ref } from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {schedulingListPage, addProductionOrder, updateProductionOrder, deleteProductionOrder} from "@/api/productionManagement/productionOrder.js";
import {productTreeList, modelList} from "@/api/basicData/product.js";
  import { useRouter } from "vue-router";
  import {
    productOrderListPage,
    listProcessRoute,
    bindingRoute,
    listProcessBom,
  } from "@/api/productionManagement/productionOrder.js";
  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
const { proxy } = getCurrentInstance();
  const router = useRouter();
const tableColumn = ref([
    {
        label: "录入日期",
        prop: "registerDate",
        width: 120,
    },
    {
        label: "生产订单号",
        prop: "orderNo",
      prop: "npsNo",
      width: '120px',
    },
    {
        label: "产品大类",
      label: "销售合同号",
      prop: "salesContractNo",
      width: '150px',
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: '200px',
    },
    {
      label: "产品名称",
        prop: "productCategory",
      width: '120px',
    },
    {
        label: "规格型号",
      label: "规格",
        prop: "specificationModel",
      width: '120px',
    },
    {
        label: "单位",
        prop: "unit",
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: '200px',
    },
    {
        label: "数量",
      label: "需求数量",
        prop: "quantity",
    },
    // {
    //     label: "排产数量",
    //     prop: "schedulingNum",
    //     width: 100,
    // },
    // {
    //     label: "完工数量",
    //     prop: "successNum",
    //     width: 100,
    // },
    {
        label: "操作",
        prop: "action",
        width: 120,
        fixed: "right",
      label: "完成数量",
      prop: "completeQuantity",
    },
    {
        dataType: "slot",
      label: "完成进度",
      prop: "completionStatus",
      slot: "completionStatus",
      width: 180,
    },
    {
      label: "开始日期",
      prop: "startTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: 120,
    },
    {
      label: "结束日期",
      prop: "endTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: 120,
    },
    {
      dataType: "action",
      label: "操作",
        align: "center",
        slot: "action"
    }
      fixed: "right",
      width: 200,
      operation: [
        {
          name: "工艺路线",
          type: "text",
          clickFun: row => {
            showRouteItemModal(row);
          },
        },
        {
          name: "绑定工艺路线",
          type: "text",
          showHide: row => !row.processRouteCode,
          clickFun: row => {
            openBindRouteDialog(row);
          },
        },
        {
          name: "产品结构",
          type: "text",
          clickFun: row => {
            showProductStructure(row);
          },
        },
      ],
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = ref({
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const dialogVisible = ref(false);
const dialogTitle = ref("");
const dialogMode = ref(""); // 'create' æˆ– 'edit'
const formRef = ref();
const productOptions = ref([]);
const modelOptions = ref([]);
const form = reactive({
    id: null,
    registerDate: dayjs().format("YYYY-MM-DD"),
    productCategory: "",
    productCategoryName: "",
    productModelId: "",
    specificationModel: "",
    unit: "",
    quantity: null,
});
const formRules = {
    registerDate: [{ required: true, message: "请选择录入日期", trigger: "change" }],
    productCategory: [{ required: true, message: "请选择产品大类", trigger: "change" }],
    productModelId: [{ required: true, message: "请选择规格型号", trigger: "change" }],
    quantity: [{ required: true, message: "请输入数量", trigger: "blur" }],
};
const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
        productCategory: "",
        registerDate: null, // å½•入日期
        entryDateStart: undefined,
        entryDateEnd: undefined,
      specificationModel: "",
    },
});
const { searchForm } = toRefs(data);
const openDialog = (mode, row = null) => {
    dialogMode.value = mode;
    if (mode === 'create') {
        dialogTitle.value = "新增生产订单";
        resetForm();
    } else if (mode === 'edit') {
        dialogTitle.value = "编辑生产订单";
        resetForm();
  const toProgressPercentage = val => {
    const n = Number(val);
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return Math.round(n);
  };
        
        console.log('编辑数据:', row);
  // 30/50/80/100 åˆ†æ®µé¢œè‰²ï¼šçº¢/橙/蓝/绿
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
    if (p < 30) return "#f56c6c";
    if (p < 50) return "#e6a23c";
    if (p < 80) return "#409eff";
    return "#67c23a";
  };
        
        // å¡«å……编辑数据
        form.id = row.id;
        form.registerDate = row.registerDate;
        form.productCategoryName = row.productCategory;
        form.specificationModel = row.specificationModel;
        form.unit = row.unit;
        form.quantity = row.quantity;
        // å…ˆåŠ è½½äº§å“é€‰é¡¹ï¼Œç„¶åŽæ ¹æ®åç§°æŸ¥æ‰¾å¯¹åº”çš„ID
        if (productOptions.value.length === 0) {
            getProductOptions().then(() => {
                findAndSetProductCategory(row.productCategory);
  // ç»‘定工艺路线弹框
  const bindRouteDialogVisible = ref(false);
  const bindRouteLoading = ref(false);
  const bindRouteSaving = ref(false);
  const routeOptions = ref([]);
  const bindForm = reactive({
    orderId: null,
    routeId: null,
            });
        } else {
            findAndSetProductCategory(row.productCategory);
        }
    }
    
    dialogVisible.value = true;
    if (productOptions.value.length === 0) {
        getProductOptions();
    }
};
const closeDialog = () => {
    dialogVisible.value = false;
};
const resetForm = () => {
    form.id = null;
    form.registerDate = dayjs().format("YYYY-MM-DD");
    form.productCategory = "";
    form.productCategoryName = "";
    form.productModelId = "";
    form.specificationModel = "";
    form.unit = "";
    form.quantity = null;
    modelOptions.value = [];
};
const handleCategoryChange = (value) => {
    form.productCategory = value;
    form.productCategoryName = findNodeById(productOptions.value, value) || "";
    form.productModelId = "";
    form.specificationModel = "";
    form.unit = "";
    modelOptions.value = [];
    if (value) {
        getModels(value);
    }
};
const handleModelChange = (value) => {
    form.productModelId = value;
    const selected = modelOptions.value.find(item => item.id === value);
    if (selected) {
        form.specificationModel = selected.model;
        form.unit = selected.unit || "";
    } else {
        form.specificationModel = "";
        form.unit = "";
    }
};
const submitForm = () => {
    formRef.value?.validate(async (valid) => {
        if (!valid) return;
        if (!form.unit) {
            proxy.$modal.msgWarning("请先选择规格型号以带出单位");
  const openBindRouteDialog = async row => {
    bindForm.orderId = row.id;
    bindForm.routeId = null;
    bindRouteDialogVisible.value = true;
    routeOptions.value = [];
    if (!row.productModelId) {
      proxy.$modal.msgWarning("当前订单缺少产品型号,无法查询工艺路线");
      bindRouteDialogVisible.value = false;
            return;
        }
    bindRouteLoading.value = true;
        try {
            const payload = {
                registerDate: form.registerDate,
                productCategory: form.productCategoryName,
                specificationModel: form.specificationModel,
                unit: form.unit,
                quantity: form.quantity,
      const res = await listProcessRoute({ productModelId: row.productModelId });
      routeOptions.value = res.data || [];
    } catch (e) {
      console.error("获取工艺路线列表失败:", e);
      proxy.$modal.msgError("获取工艺路线列表失败");
    } finally {
      bindRouteLoading.value = false;
    }
            };
            
            if (dialogMode.value === 'create') {
                await addProductionOrder(payload);
                proxy.$modal.msgSuccess("新增成功");
            } else if (dialogMode.value === 'edit') {
                payload.id = form.id;
                await updateProductionOrder(payload);
                proxy.$modal.msgSuccess("编辑成功");
  const handleBindRouteConfirm = async () => {
    if (!bindForm.routeId) {
      proxy.$modal.msgWarning("请选择工艺路线");
      return;
            }
            closeDialog();
    bindRouteSaving.value = true;
    try {
      await bindingRoute({
        id: bindForm.orderId,
        routeId: bindForm.routeId,
      });
      proxy.$modal.msgSuccess("绑定成功");
      bindRouteDialogVisible.value = false;
            getList();
        } catch (err) {
            console.error(`${dialogMode.value === 'create' ? '新增' : '编辑'}失败`, err);
            proxy.$modal.msgError(`${dialogMode.value === 'create' ? '新增' : '编辑'}失败,请重试`);
        }
    });
};
// ç¼–辑方法
const handleEdit = (row) => {
    openDialog('edit', row);
};
// åˆ é™¤æ–¹æ³•
const handleDelete = (row) => {
    proxy.$modal.confirm(`确定要删除生产订单"${row.orderNo}"吗?`, "删除确认", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
    }).then(async () => {
        try {
            await deleteProductionOrder([row.id]);
            proxy.$modal.msgSuccess("删除成功");
            getList(); // åˆ·æ–°åˆ—表
        } catch (err) {
            console.error("删除失败", err);
            proxy.$modal.msgError("删除失败,请重试");
        }
    }).catch(() => {
        proxy.$modal.msg("已取消删除");
    });
};
const getProductOptions = () => {
    return productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res || []);
    });
};
const getModels = (value) => {
    return modelList({ id: value }).then((res) => {
        modelOptions.value = res || [];
    });
};
const convertIdToValue = (data) => {
    return data.map((item) => {
        const { id, children, ...rest } = item;
        const newItem = {
            ...rest,
            value: id,
        };
        if (children && children.length > 0) {
            newItem.children = convertIdToValue(children);
        }
        return newItem;
    });
};
const findNodeById = (nodes, value) => {
    for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].value === value) {
            return nodes[i].label;
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
            const label = findNodeById(nodes[i].children, value);
            if (label) return label;
        }
    }
    return null;
};
const findNodeByLabel = (nodes, label) => {
    for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].label === label) {
            return nodes[i].value;
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
            const value = findNodeByLabel(nodes[i].children, label);
            if (value) return value;
        }
    }
    return null;
};
const findAndSetProductCategory = (categoryName) => {
    const categoryId = findNodeByLabel(productOptions.value, categoryName);
    if (categoryId) {
        form.productCategory = categoryId;
        // åŠ è½½å¯¹åº”çš„è§„æ ¼åž‹å·é€‰é¡¹
        getModels(categoryId).then(() => {
            // æ ¹æ®è§„格型号名称查找对应的ID
            const modelItem = modelOptions.value.find(item => item.model === form.specificationModel);
            if (modelItem) {
                form.productModelId = modelItem.id;
            }
        });
    } catch (e) {
      console.error("绑定工艺路线失败:", e);
      proxy.$modal.msgError("绑定工艺路线失败");
    } finally {
      bindRouteSaving.value = false;
    }
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.value.current = 1;
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.value.current = obj.page;
    page.value.size = obj.limit;
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const changeDaterange = (value) => {
  const changeDaterange = value => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
@@ -398,29 +308,59 @@
const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page.value };
    params.registerDate = undefined
    if (params.productCategory) {
        // å¦‚果是对象类型,获取其label(名称)而不是value(ID)
        if (typeof params.productCategory === "object") {
            params.productCategory = findNodeById(productOptions.value, params.productCategory) || params.productCategory;
        }
        // å¦‚果是ID,转换为名称
        else if (typeof params.productCategory === "string" || typeof params.productCategory === "number") {
            const categoryName = findNodeById(productOptions.value, params.productCategory);
            if (categoryName) {
                params.productCategory = categoryName;
            }
        }
    }
    schedulingListPage(params).then((res) => {
        console.log('params---', res)
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    productOrderListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.data.records;
        page.value.total = res.data.data.total;
    }).catch(() => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  const showRouteItemModal = async row => {
    const orderId = row.id;
    try {
      const res = await getOrderProcessRouteMain(orderId);
      const data = res.data || {};
      if (!data || !data.id) {
        proxy.$modal.msgWarning("未找到关联的工艺路线");
        return;
      }
      router.push({
        path: "/productionManagement/processRouteItem",
        query: {
          id: data.id,
          processRouteCode: data.processRouteCode || "",
          productName: data.productName || "",
          model: data.model || "",
          bomNo: data.bomNo || "",
          description: data.description || "",
          orderId,
          type: "order",
        },
      });
    } catch (e) {
      console.error("获取工艺路线主信息失败:", e);
      proxy.$modal.msgError("获取工艺路线信息失败");
    }
  };
  const showProductStructure = row => {
    router.push({
      path: "/productionManagement/productStructureDetail",
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productCategory || "",
        productModelName: row.specificationModel || "",
        orderId: row.id,
        type: "order",
      },
    });
};
// å¯¼å‡º
@@ -431,17 +371,21 @@
        type: "warning",
    })
        .then(() => {
            proxy.download("/productionOrder/export", {}, "生产订单.xlsx");
        proxy.download("/productOrder/export", {...searchForm.value}, "生产订单.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
  const handleConfirmRoute = () => {};
onMounted(() => {
    getList();
    getProductOptions();
});
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.search_form{
  align-items: start;
}</style>
src/views/productionManagement/productionProcess/Edit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="编辑工序"
        width="400"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="工序名称:"
            prop="name"
            :rules="[
                {
                required: true,
                message: '请输入工序名称',
              },
              {
                max: 100,
                message: '最多100个字符',
              }
            ]">
          <el-input v-model="formState.name" />
        </el-form-item>
        <el-form-item label="工序编号" prop="no">
          <el-input v-model="formState.no"  />
        </el-form-item>
        <el-form-item label="工资定额" prop="salaryQuota">
          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </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/productionProcess.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  record: {
    type: Object,
    required: true,
  }
});
const emit = defineEmits(['update:visible', 'completed']);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  id: props.record.id,
  name: props.record.name,
  no: props.record.no,
  remark: props.record.remark,
  salaryQuota: props.record.salaryQuota,
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
// ç›‘听 record å˜åŒ–,更新表单数据
watch(() => props.record, (newRecord) => {
  if (newRecord && isShow.value) {
    formState.value = {
      id: newRecord.id,
      name: newRecord.name || '',
      no: newRecord.no || '',
      remark: newRecord.remark || '',
      salaryQuota: newRecord.salaryQuota || '',
    };
  }
}, { immediate: true, deep: true });
// ç›‘听弹窗打开,重新初始化表单数据
watch(() => props.visible, (visible) => {
  if (visible && props.record) {
    formState.value = {
      id: props.record.id,
      name: props.record.name || '',
      no: props.record.no || '',
      remark: props.record.remark || '',
      salaryQuota: props.record.salaryQuota || '',
    };
  }
});
let { proxy } = getCurrentInstance()
const closeModal = () => {
  isShow.value = false;
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      update(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/productionManagement/productionProcess/New.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="新增工序"
        width="400"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="工序名称:"
            prop="name"
            :rules="[
                {
                required: true,
                message: '请输入工序名称',
              },
              {
                max: 100,
                message: '最多100个字符',
              }
            ]">
          <el-input v-model="formState.name" />
        </el-form-item>
        <el-form-item label="工序编号" prop="no">
          <el-input v-model="formState.no"  />
        </el-form-item>
        <el-form-item label="工资定额" prop="salaryQuota">
          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        </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/productionProcess.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
});
const emit = defineEmits(['update:visible', 'completed']);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  name: '',
  remark: '',
  salaryQuota:  '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
let { proxy } = getCurrentInstance()
const closeModal = () => {
  isShow.value = false;
};
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      add(formState.value).then(res => {
        // å…³é—­æ¨¡æ€æ¡†
        isShow.value = false;
        // å‘ŠçŸ¥çˆ¶ç»„件已完成
        emit('completed');
        proxy.$modal.msgSuccess("提交成功");
      })
    }
  })
};
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
</script>
src/views/productionManagement/productionProcess/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="工序名称:">
          <el-input v-model="searchForm.name"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="工序编号:">
          <el-input v-model="searchForm.no"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 200px;"
                    @change="handleQuery" />
        </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>
        <el-button type="info" plain @click="handleImport">导入</el-button>
        <el-button type="danger"
                   @click="handleDelete"
                   :disabled="selectedRows.length === 0"
                   plain>删除工序</el-button>
      </div>
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
    </div>
    <new-process v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 @completed="getList" />
    <edit-process v-if="isShowEditModal"
                  v-model:visible="isShowEditModal"
                  :record="record"
                  @completed="getList" />
    <ImportDialog
      ref="importDialogRef"
      v-model="importDialogVisible"
      title="导入工序"
      :action="importAction"
      :headers="importHeaders"
      :auto-upload="false"
      :on-success="handleImportSuccess"
      :on-error="handleImportError"
      @confirm="handleImportConfirm"
      @download-template="handleDownloadTemplate"
      @close="handleImportClose"
    />
  </div>
</template>
<script setup>
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import NewProcess from "@/views/productionManagement/productionProcess/New.vue";
  import EditProcess from "@/views/productionManagement/productionProcess/Edit.vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import { listPage, del, importData, downloadTemplate } from "@/api/productionManagement/productionProcess.js";
  import { getToken } from "@/utils/auth";
  const data = reactive({
    searchForm: {
      name: "",
      no: "",
    },
  });
  const { searchForm } = toRefs(data);
  const tableColumn = ref([
    {
      label: "工序编号",
      prop: "no",
    },
    {
      label: "工序名称",
      prop: "name",
    },
    {
      label: "工资定额",
      prop: "salaryQuota",
    },
    {
      label: "备注",
      prop: "remark",
    },
     {
      label: "更新时间",
      prop: "updateTime",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 280,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            showEditModal(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const isShowNewModal = ref(false);
  const isShowEditModal = ref(false);
  const record = ref({});
  const importDialogVisible = ref(false);
  const importDialogRef = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const { proxy } = getCurrentInstance();
  // å¯¼å…¥ç›¸å…³é…ç½®
  const importAction = import.meta.env.VITE_APP_BASE_API + "/productProcess/importData";
  const importHeaders = { Authorization: "Bearer " + getToken() };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  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 };
    params.entryDate = undefined;
    listPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
          ...item,
        }));
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  // æ‰“开新增弹框
  const showNewModal = () => {
    isShowNewModal.value = true;
  };
  const showEditModal = row => {
    isShowEditModal.value = true;
    record.value = row;
  };
  // åˆ é™¤
  function handleDelete() {
    const no = selectedRows.value.map(item => item.no);
    const ids = selectedRows.value.map(item => item.id);
    if (no.length > 2) {
      proxy.$modal
        .confirm(
          '是否确认删除工序编号为"' +
            no[0] +
            "、" +
            no[1] +
            '"等' +
            no.length +
            "条数据项?"
        )
        .then(function () {
          return del(ids);
        })
        .then(() => {
          getList();
          proxy.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    } else {
      proxy.$modal
        .confirm('是否确认删除工序编号为"' + no + '"的数据项?')
        .then(function () {
          return del(ids);
        })
        .then(() => {
          getList();
          proxy.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    }
  }
  // å¯¼å…¥
  const handleImport = () => {
    importDialogVisible.value = true;
  };
  // ç¡®è®¤å¯¼å…¥
  const handleImportConfirm = () => {
    if (importDialogRef.value) {
      importDialogRef.value.submit();
    }
  };
  // å¯¼å…¥æˆåŠŸ
  const handleImportSuccess = (response) => {
    if (response.code === 200) {
      proxy.$modal.msgSuccess("导入成功");
      importDialogVisible.value = false;
      if (importDialogRef.value) {
        importDialogRef.value.clearFiles();
      }
      getList();
    } else {
      proxy.$modal.msgError(response.msg || "导入失败");
    }
  };
  // å¯¼å…¥å¤±è´¥
  const handleImportError = (error) => {
    proxy.$modal.msgError("导入失败:" + (error.message || "未知错误"));
  };
  // å…³é—­å¯¼å…¥å¼¹çª—
  const handleImportClose = () => {
    if (importDialogRef.value) {
      importDialogRef.value.clearFiles();
    }
  };
  // ä¸‹è½½æ¨¡æ¿
  const handleDownloadTemplate = async () => {
    try {
      const res = await downloadTemplate();
      const blob = new Blob([res], {
        type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      });
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = "工序导入模板.xlsx";
      link.click();
      window.URL.revokeObjectURL(url);
      proxy.$modal.msgSuccess("模板下载成功");
    } catch (error) {
      proxy.$modal.msgError("模板下载失败");
    }
  };
  // å¯¼å‡º
  // const handleOut = () => {
  //     ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
  //         confirmButtonText: "确认",
  //         cancelButtonText: "取消",
  //         type: "warning",
  //     })
  //         .then(() => {
  //             proxy.download("/salesLedger/scheduling/exportTwo", {}, "工序排产.xlsx");
  //         })
  //         .catch(() => {
  //             proxy.$modal.msg("已取消");
  //         });
  // };
  onMounted(() => {
    getList();
  });
</script>
<style scoped></style>
src/views/productionManagement/productionReporting/Input.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
        title="投入"
        @close="closeModal"
    >
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="data"
          :page="page"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="closeModal">关闭</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, computed, onMounted} from "vue";
import { productionProductInputListPage } from "@/api/productionManagement/productionProductInput";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  productionProductMainId: {
    type: Number,
    required: true,
  },
});
const emit = defineEmits(['update:visible', 'completed']);
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  fetchData();
};
const tableLoading = ref(false);
const tableColumn = [
  {
    label: '报工单号',
    prop: 'productNo',
  },
  {
    label: '投入产品名称',
    prop: 'productName',
  },
  {
    label: '投入产品型号',
    prop: 'model',
  },
  {
    label: '投入数量',
    prop: 'quantity',
  },
  {
    label: '单位',
    prop: 'unit',
  },
]
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const data = ref([])
const closeModal = () => {
  isShow.value = false;
};
const fetchData = () => {
  tableLoading.value = true;
  const params = { productMainId: props.productionProductMainId, ...page };
  productionProductInputListPage(params).then(res => {
    tableLoading.value = false;
    data.value = res.data.records;
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
defineExpose({
  closeModal,
  isShow,
});
onMounted(() => {
  fetchData()
})
</script>
src/views/productionManagement/productionReporting/Output.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<template>
  <div>
    <el-dialog v-model="isShow"
               title="产出"
               @close="closeModal">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="data"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"></PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="closeModal">关闭</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { ref, computed, onMounted } from "vue";
  import { productionProductOutputListPage } from "@/api/productionManagement/productionProductOutput.js";
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true,
    },
    productionProductMainId: {
      type: Number,
      required: true,
    },
  });
  const emit = defineEmits(["update:visible", "completed"]);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    fetchData();
  };
  const tableLoading = ref(false);
  const tableColumn = [
    {
      label: "报工单号",
      prop: "productNo",
    },
    {
      label: "产品型号",
      prop: "model",
    },
    {
      label: "产出数量",
      prop: "quantity",
    },
  ];
  const isShow = computed({
    get() {
      return props.visible;
    },
    set(val) {
      emit("update:visible", val);
    },
  });
  const data = ref([]);
  const closeModal = () => {
    isShow.value = false;
  };
  const fetchData = () => {
    tableLoading.value = true;
    const params = { productMainId: props.productionProductMainId, ...page };
    productionProductOutputListPage(params)
      .then(res => {
        tableLoading.value = false;
        data.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  defineExpose({
    closeModal,
    isShow,
  });
  onMounted(() => {
    fetchData();
  });
</script>
src/views/productionManagement/productionReporting/components/formDia.vue
@@ -14,6 +14,13 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
                        <el-form-item label="待生产数量:" prop="pendingNum">
                            <el-input v-model="form.pendingNum" placeholder="请输入" clearable disabled/>
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
            <el-form-item label="本次生产数量:" prop="finishedNum">
                            <el-input-number
                                v-model="form.finishedNum"
@@ -27,11 +34,16 @@
                            />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="单价(元):" prop="unitPrice">
              <el-input v-model="form.unitPrice" placeholder="请输入" clearable @input="calculateTotalPrice"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="待生产数量:" prop="pendingNum">
              <el-input v-model="form.pendingNum" placeholder="请输入" clearable disabled/>
            <el-form-item label="总价(元):" prop="totalPrice">
              <el-input v-model="form.totalPrice" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -42,6 +54,9 @@
                                v-model="form.schedulingUserId"
                                placeholder="选择人员"
                                style="width: 100%;"
                filterable
                default-first-option
                :reserve-keyword="false"
                            >
                                <el-option
                                    v-for="user in userList"
@@ -79,7 +94,6 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js";
const { proxy } = getCurrentInstance()
@@ -95,6 +109,8 @@
        finishedNum: "",
        schedulingUserId: "",
        schedulingDate: "",
        unitPrice: "",
        totalPrice: "",
  },
  rules: {
        schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
@@ -118,6 +134,19 @@
        proxy.$modal.msgWarning('本次生产数量不可大于排产数量')
    }
    form.value.pendingNum = form.value.schedulingNum - form.value.finishedNum;
    calculateTotalPrice();
}
// è®¡ç®—总价
const calculateTotalPrice = () => {
    const quantity = Number(form.value.finishedNum ?? 0);
    const unitPrice = Number(form.value.unitPrice ?? 0);
    if (quantity > 0 && unitPrice > 0) {
        form.value.totalPrice = (quantity * unitPrice).toFixed(2);
    } else {
        form.value.totalPrice = '0.00';
    }
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
src/views/productionManagement/productionReporting/index.vue
@@ -1,40 +1,38 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <el-form :model="searchForm" :inline="true">
                <el-form-item label="客户名称:">
                    <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search"
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="报工人员名称:">
          <el-input v-model="searchForm.nickName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
                <el-form-item label="项目名称:">
                    <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
        <el-form-item label="工单号:">
          <el-input v-model="searchForm.workOrderNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                                        style="width: 200px;"
                                        @change="handleQuery" />
                </el-form-item>
                <el-form-item label="排产日期:">
                    <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                    placeholder="请选择" clearable @change="changeDaterange" />
                </el-form-item>
                <el-form-item label="状态:">
                    <el-select v-model="searchForm.status" placeholder="请选择状态" style="width: 140px" clearable>
                        <el-option label="待生产" :value="1"></el-option>
                        <el-option label="已报工" :value="3"></el-option>
                        <el-option label="生产中" :value="2"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleQuery">搜索</el-button>
          <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="openForm('add')">生产报工</el-button>
      <div style="text-align: right"
           class="mb10">
        <!-- <el-button type="primary"
                   @click="openForm('add')">生产报工</el-button> -->
                <el-button @click="handleOut">导出</el-button>
            </div>
            <PIMTable
                rowKey="id"
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
@@ -44,205 +42,188 @@
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"
            >
                :total="page.total">
                <template #expand="{ row }">
                    <el-table
                        :data="expandData"
          <el-table :data="expandData"
                        border
                        show-summary
                        :summary-method="summarizeMainTable"
                        v-loading="childrenLoading"
                    >
                        <el-table-column
                            align="center"
                    v-loading="childrenLoading">
            <el-table-column align="center"
                            label="序号"
                            type="index"
                            width="60"
                        />
                        <el-table-column label="本次生产数量" prop="finishedNum" align="center" width="400">
                             width="60" />
            <el-table-column label="本次生产数量"
                             prop="finishedNum"
                             align="center"
                             width="400">
                            <template #default="scope">
                                <el-input-number :step="0.01" :min="0" style="width: 100%"
                <el-input-number :step="0.01"
                                 :min="0"
                                 style="width: 100%"
                                                                 v-model="scope.row.finishedNum"
                                                                 :disabled="!scope.row.editType"
                                                                 :precision="2"
                                                                 placeholder="请输入"
                                                                 clearable
                                                                 @change="changeNum(scope.row)"
                                />
                                 @change="changeNum(scope.row)" />
                            </template>
                        </el-table-column>
<!--                        <el-table-column label="待生产数量" prop="pendingNum" width="240" align="center"></el-table-column>-->
                        <el-table-column label="生产人" prop="schedulingUserId" width="400">
            <el-table-column label="生产人"
                             prop="schedulingUserId"
                             width="400">
                            <template #default="scope">
                                <el-select
                                    v-model="scope.row.schedulingUserId"
                <el-select v-model="scope.row.schedulingUserId"
                                    placeholder="选择人员"
                                    :disabled="!scope.row.editType"
                                    style="width: 100%;"
                                >
                                    <el-option
                                        v-for="user in userList"
                           style="width: 100%;">
                  <el-option v-for="user in userList"
                                        :key="user.userId"
                                        :label="user.nickName"
                                        :value="user.userId"
                                    />
                             :value="user.userId" />
                                </el-select>
                            </template>
                        </el-table-column>
                        <el-table-column label="生产日期" prop="schedulingDate" width="400">
            <el-table-column label="生产日期"
                             prop="schedulingDate"
                             width="400">
                            <template #default="scope">
                                <el-date-picker
                                    v-model="scope.row.schedulingDate"
                <el-date-picker v-model="scope.row.schedulingDate"
                                    type="date"
                                    :disabled="!scope.row.editType"
                                    placeholder="请选择日期"
                                    value-format="YYYY-MM-DD"
                                    format="YYYY-MM-DD"
                                    clearable
                                    style="width: 100%"
                                />
                                style="width: 100%" />
                            </template>
                        </el-table-column>
                        <el-table-column label="操作" width="60">
            <el-table-column label="操作"
                             >
                            <template #default="scope">
                                <el-button
                                    link
                <el-button link
                                    type="primary"
                                    size="small"
                                    @click="changeEditType(scope.row)"
                                    v-if="!scope.row.editType"
                                    :disabled="scope.row.parentStatus === 3"
                                >编辑</el-button
                                >
                                <el-button
                                    link
                           :disabled="scope.row.parentStatus === 3">编辑</el-button>
                <el-button link
                                    type="primary"
                                    size="small"
                                    @click="saveReceiptPayment(scope.row)"
                                    v-if="scope.row.editType"
                                >保存</el-button
                                >
                           v-if="scope.row.editType">保存</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </template>
            </PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <input-modal v-if="isShowInput"
                 v-model:visible="isShowInput"
                 :production-product-main-id="isShowingId" />
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {
    productionReportUpdate,
    workListPage,
    workListPageById
    workListPageById,
    productionReportDelete,
} from "@/api/productionManagement/productionReporting.js";
  import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
  import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
const data = reactive({
    searchForm: {
        staffName: "",
        entryDate: null, // å½•入日期
        entryDateStart: undefined,
        entryDateEnd: undefined,
      nickName: "",
      workOrderNo: "",
      workOrderStatus: "",
    },
});
const { searchForm } = toRefs(data);
const expandedRowKeys = ref([]);
const expandData = ref([]);
const userList = ref([])
  const userList = ref([]);
const tableColumn = ref([
    {
        type: "expand",
        dataType: "slot",
        slot: "expand",
    },
    {
        label: "状态",
        prop: "status",
        dataType: "tag",
        formatData: (params) => {
            if (params == 3) {
                return "已报工";
            } else if (params == 1) {
                return "待生产";
            } else {
                return '生产中';
            }
        },
        formatType: (params) => {
            if (params == 3) {
                return "success";
            } else if (params == 1) {
                return "primary";
            } else {
                return 'warning';
            }
        },
    },
    {
        label: "排产日期",
        prop: "schedulingDate",
      label: "报工单号",
      prop: "productNo",
        width: 120,
    },
    {
        label: "排产人",
        prop: "schedulingUserName",
      label: "报工人员",
      prop: "nickName",
      width: 120,
    },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 150,
      label: "工单编号",
      prop: "workOrderNo",
      width: 120,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 150,
      label: "销售合同号",
      prop: "salesContractNo",
      width: 120,
    },
    {
      label: "产品名称",
      prop: "productName",
      width: 120,
    },
    {
      label: "产品规格型号",
      prop: "productModelName",
      width: 120,
    },
    {
      label: "产出数量",
      prop: "quantity",
      width: 120,
    },
    // {
    //   label: "报废数量",
    //   prop: "scrapQuantity",
    //   width: 120,
    // },
    {
        label: "单位",
        prop: "unit",
      width: 120,
    },
    {
      label: "创建时间",
      prop: "createTime",
      width: 120,
    },
    {
        label: "工序",
        prop: "process",
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      operation: [
        {
          name: "查看投入",
          type: "text",
          clickFun: row => {
            showInput(row);
          },
    },
    {
        label: "口味分类",
        prop: "type",
        width: 150,
          name: "删除",
          type: "danger",
          clickFun: row => {
            deleteReport(row);
    },
    {
        label: "损耗",
        prop: "loss",
        width: 150,
    },
    {
        label: "排产数量",
        prop: "schedulingNum",
        width: 100,
    },
    {
        label: "生产数量",
        prop: "finishedNum",
        width: 100,
    },
    {
        label: "待生产数量",
        prop: "pendingFinishNum",
        width: 100,
    },
    {
        label: "备注",
        prop: "remark",
        width: 200,
      ],
    },
]);
const tableData = ref([]);
@@ -254,8 +235,8 @@
    size: 100,
    total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
  const formDia = ref();
  const { proxy } = getCurrentInstance();
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -263,7 +244,7 @@
    page.current = 1;
    getList();
};
const changeDaterange = (value) => {
  const changeDaterange = value => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
@@ -273,7 +254,25 @@
    }
    handleQuery();
};
const pagination = (obj) => {
  const deleteReport = row => {
    ElMessageBox.confirm("确定删除该报工吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      productionReportDelete({ id: row.id }).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        } else {
          ElMessageBox.alert(res.msg || "删除失败", "提示", {
            confirmButtonText: "确定",
          });
        }
      });
    });
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
@@ -281,22 +280,25 @@
const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    expandedRowKeys.value = []
    workListPage(params).then(res => {
    params.entryDate = undefined;
    expandedRowKeys.value = [];
    productionProductMainListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
          pendingFinishNum:
            (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
        }));
        page.total = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
      .catch(err => {
        tableLoading.value = false;
      });
};
// å±•开行
const expandChange = (row, expandedRows) => {
    userListNoPageByTenantId().then((res) => {
    userListNoPageByTenantId().then(res => {
        userList.value = res.data;
    });
    if (expandedRows.length > 0) {
@@ -304,14 +306,16 @@
            expandedRowKeys.value = [];
            try {
                childrenLoading.value = true;
                workListPageById({ id: row.id }).then((res) => {
          workListPageById({ id: row.id }).then(res => {
                    childrenLoading.value = false;
                    const index = tableData.value.findIndex((item) => item.id === row.id);
            const index = tableData.value.findIndex(item => item.id === row.id);
                    if (index > -1) {
                        expandData.value = res.data.map(item => ({
                            ...item,
                            pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
                            parentStatus: row.status // æ–°å¢žçˆ¶è¡¨çŠ¶æ€
                pendingNum:
                  (Number(item.schedulingNum) || 0) -
                  (Number(item.finishedNum) || 0),
                parentStatus: row.status, // æ–°å¢žçˆ¶è¡¨çŠ¶æ€
                        }));
                    }
                    expandedRowKeys.value.push(row.id);
@@ -320,46 +324,50 @@
                childrenLoading.value = false;
                console.log(error);
            }
        })
      });
    } else {
        expandedRowKeys.value = [];
    }
};
const changeNum = (row) => {
  const changeNum = row => {
    // æ‰¾åˆ°çˆ¶è¡¨æ ¼æ•°æ®
    const parentRow = tableData.value.find(item => item.id === expandedRowKeys.value[0]);
    const parentRow = tableData.value.find(
      item => item.id === expandedRowKeys.value[0]
    );
    // è®¡ç®—所有子表格 finishedNum çš„æ€»å’Œ
    const totalFinishedNum = expandData.value.reduce((sum, item) => sum + (Number(item.finishedNum) || 0), 0);
    const totalFinishedNum = expandData.value.reduce(
      (sum, item) => sum + (Number(item.finishedNum) || 0),
      0
    );
    // çˆ¶è¡¨æ ¼çš„æŽ’产数量
    const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
    
    if (totalFinishedNum > schedulingNum) {
        // å›žé€€æœ¬æ¬¡è¾“å…¥
        row.finishedNum = schedulingNum - (totalFinishedNum - Number(row.finishedNum));
        proxy.$modal.msgWarning('所有本次生产数量之和不可大于排产数量');
      row.finishedNum =
        schedulingNum - (totalFinishedNum - Number(row.finishedNum));
      proxy.$modal.msgWarning("所有本次生产数量之和不可大于排产数量");
    }
    row.pendingNum = row.schedulingNum - row.finishedNum;
}
  };
// ç¼–辑修改状态
const changeEditType = (row) => {
  const changeEditType = row => {
    row.editType = !row.editType;
};
// ä¿å­˜è®°å½•
const saveReceiptPayment = (row) => {
    productionReportUpdate(row).then((res) => {
  const saveReceiptPayment = row => {
    productionReportUpdate(row).then(res => {
        row.editType = !row.editType;
        getList();
        proxy.$modal.msgSuccess("提交成功");
    });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
};
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, [
        "finishedNum"
    ]);
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["finishedNum"]);
};
// æ‰“开弹框
const openForm = (type, row) => {
@@ -372,35 +380,19 @@
        return;
    }
    nextTick(() => {
        const rowInfo = type === 'add' ? selectedRows.value[0] : row
        formDia.value?.openDialog(type, rowInfo)
    })
      const rowInfo = type === "add" ? selectedRows.value[0] : row;
      formDia.value?.openDialog(type, rowInfo);
    });
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            staffJoinDel(ids).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
  // æ‰“开投入模态框
  const isShowInput = ref(false);
  const isShowingId = ref(0);
  const showInput = row => {
    isShowInput.value = true;
    isShowingId.value = row.id;
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -409,7 +401,7 @@
        type: "warning",
    })
        .then(() => {
            proxy.download("/salesLedger/work/export", {}, "生产报工.xlsx");
        proxy.download("/productionProductMain/export", {}, "生产报工.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
src/views/productionManagement/workOrder/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,645 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div class="search-row">
        <div class="search-item">
          <span class="search_title">工单编号:</span>
          <el-input v-model="searchForm.workOrderNo"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search" />
        </div>
        <div class="search-item">
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </div>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination">
                <template #completionStatus="{ row }">
                  <el-progress :percentage="toProgressPercentage(row?.completionStatus)" :color="progressColor(toProgressPercentage(row?.completionStatus))" :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
                </template>
              </PIMTable>
    </div>
    <el-dialog v-model="editDialogVisible"
               title="编辑时间"
               width="500px">
      <el-form :model="editrow"
               label-width="120px">
        <el-form-item label="计划开始时间">
          <el-date-picker v-model="editrow.planStartTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="计划结束时间">
          <el-date-picker v-model="editrow.planEndTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="实际开始时间">
          <el-date-picker v-model="editrow.actualStartTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="实际结束时间">
          <el-date-picker v-model="editrow.actualEndTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleUpdate">确定</el-button>
          <el-button @click="editDialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="transferCardVisible"
               title="流转卡"
               width="1000px">
      <div class="transfer-card-title">工单流转卡</div>
      <div class="transfer-card-container">
        <div class="transfer-card-info">
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">工单编号</span>
              <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
            </div>
            <!-- <div class="info-item">
              <span class="info-label">产品编号</span>
              <span class="info-value">{{ transferCardRowData.productNo }}</span>
            </div> -->
            <div class="info-item">
              <span class="info-label">产品名称</span>
              <span class="info-value">{{ transferCardRowData.productName }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">产品规格</span>
              <span class="info-value">{{ transferCardRowData.model }}</span>
            </div>
            <!-- <div class="info-item">
              <span class="info-label">工单状态</span>
              <span class="info-value">{{
                transferCardRowData.status === 1 ? '待确认' :
                transferCardRowData.status === 2 ? '待生产' :
                transferCardRowData.status === 3 ? '生产中' :
                transferCardRowData.status === 4 ? '已生产' :
                transferCardRowData.status
              }}</span>
            </div> -->
            <div class="info-item">
              <span class="info-label">计划开始时间</span>
              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划结束时间</span>
              <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">备注</span>
              <span class="info-value">{{ transferCardRowData.remark }}</span>
            </div>
          </div>
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">需求数量</span>
              <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">完成数量</span>
              <span class="info-value">{{ transferCardRowData.completeQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">良品数量</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">不良品数</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际开始时间</span>
              <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际结束时间</span>
              <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
            </div>
          </div>
        </div>
        <div class="transfer-card-qr">
          <div class="qr-container">
            <img :src="transferCardQrUrl"
                 alt="流转卡二维码"
                 style="width: 200px; height: 200px;" />
            <!-- <div class="qr-tip"
                 style="margin-top: 10px; text-align: center;">流转卡二维码</div> -->
          </div>
        </div>
      </div>
      <div class="print-button-container"
           style=" text-align: center;
      margin-bottom: 40px;">
        <el-button type="primary"
                   style="margin-top: 20px;"
                   @click="printTransferCard">打印流转卡</el-button>
      </div>
    </el-dialog>
    <el-dialog v-model="reportDialogVisible"
               title="报工"
               width="500px">
      <el-form :model="reportForm"
               label-width="120px">
        <el-form-item label="待生产数量">
          <el-input v-model="reportForm.planQuantity"
                    readonly
                    style="width: 300px" />
        </el-form-item>
        <el-form-item label="本次生产数量">
          <el-input v-model.number="reportForm.quantity"
                    type="number"
                    min="1"
                    style="width: 300px"
                    placeholder="请输入本次生产数量" />
        </el-form-item>
        <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
                     style="width: 300px"
                     placeholder="请选择班组信息"
                     clearable
                     filterable
                     @change="handleUserChange">
            <el-option v-for="user in userOptions"
                       :key="user.userId"
                       :label="user.userName"
                       :value="user.userId" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleReport">确定</el-button>
          <el-button @click="reportDialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
    productWorkOrderPage,
    updateProductWorkOrder,
    addProductMain,
  } from "@/api/productionManagement/workOrder.js";
  import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
  import QRCode from "qrcode";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  const { proxy } = getCurrentInstance();
  const tableColumn = ref([
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: "140",
    },
    {
      label: "生产订单号",
      prop: "productOrderNpsNo",
      width: "140",
    },
    {
      label: "产品名称",
      prop: "productName",
      width: "140",
    },
    {
      label: "规格",
      prop: "model",
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "工序名称",
      prop: "processName",
    },
    {
      label: "需求数量",
      prop: "planQuantity",
      width: "140",
    },
    {
      label: "完成数量",
      prop: "completeQuantity",
      width: "140",
    },
    {
      label: "完成进度",
      prop: "completionStatus",
      dataType: "slot",
      slot: "completionStatus",
      width: "140",
    },
    {
      label: "计划开始时间",
      prop: "planStartTime",
      width: "140",
    },
    {
      label: "计划结束时间",
      prop: "planEndTime",
      width: "140",
    },
    {
      label: "实际开始时间",
      prop: "actualStartTime",
      width: "140",
    },
    {
      label: "实际结束时间",
      prop: "actualEndTime",
      width: "140",
    },
    {
      label: "操作",
      width: "200",
      align: "center",
      dataType: "action",
      fixed: "right",
      operation: [
        {
          name: "编辑",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "流转卡",
          clickFun: row => {
            showTransferCard(row);
          },
        },
        {
          name: "报工",
          clickFun: row => {
            showReportDialog(row);
          },
          disabled: row => row.planQuantity <= 0,
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const qrCodeUrl = ref("");
  const qrRowData = ref(null);
  const editDialogVisible = ref(false);
  const transferCardVisible = ref(false);
  const transferCardData = ref([]);
  const transferCardQrUrl = ref("");
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const userOptions = ref([]);
  const reportForm = reactive({
    planQuantity: 0,
    quantity: 0,
    userName: "",
    workOrderId: "",
    reportWork: "",
    productProcessRouteItemId: "",
    userId: "",
    productMainId: null,
  });
  const currentReportRowData = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const data = reactive({
    searchForm: {
      workOrderNo: "",
    },
  });
  const { searchForm } = toRefs(data);
  const toProgressPercentage = val => {
    const n = Number(val);
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return Math.round(n);
  };
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
    if (p < 30) return "#f56c6c";
    if (p < 50) return "#e6a23c";
    if (p < 80) return "#409eff";
    return "#67c23a";
  };
  let editrow = ref(null);
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  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 };
    productWorkOrderPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  const showTransferCard = async row => {
    transferCardRowData.value = row;
    const qrContent = String(row.id);
    transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
    transferCardVisible.value = true;
  };
  const printTransferCard = () => {
    window.print();
  };
  const handleEdit = row => {
    editrow.value = JSON.parse(JSON.stringify(row));
    editDialogVisible.value = true;
  };
  const handleUpdate = () => {
    updateProductWorkOrder(editrow.value)
      .then(res => {
        proxy.$modal.msgSuccess("提交成功");
        editDialogVisible.value = false;
        getList();
      })
      .catch(() => {
        ElMessageBox.alert("修改失败", "提示", {
          confirmButtonText: "确定",
        });
      });
  };
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.planQuantity = row.planQuantity;
    reportForm.quantity = row.quantity;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
    reportForm.productMainId = row.productMainId;
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯ï¼Œè®¾ç½®ä¸ºé»˜è®¤é€‰ä¸­
    getUserProfile()
      .then(res => {
        if (res.code === 200) {
          reportForm.userId = res.data.userId;
          reportForm.userName = res.data.userName;
        }
      })
      .catch(err => {
        console.error("获取用户信息失败", err);
      });
    reportDialogVisible.value = true;
  };
  const handleReport = () => {
    if (reportForm.planQuantity <= 0) {
      ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (!reportForm.quantity || reportForm.quantity <= 0) {
      ElMessageBox.alert("请输入有效的本次生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (reportForm.quantity > reportForm.planQuantity) {
      ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    // console.log(reportForm);
    addProductMain(reportForm).then(res => {
      if (res.code === 200) {
        proxy.$modal.msgSuccess("报工成功");
        reportDialogVisible.value = false;
        getList();
      } else {
        ElMessageBox.alert(res.msg || "报工失败", "提示", {
          confirmButtonText: "确定",
        });
      }
    });
  };
  // èŽ·å–ç”¨æˆ·åˆ—è¡¨
  const getUserList = () => {
    userListNoPageByTenantId()
      .then(res => {
        if (res.code === 200) {
          userOptions.value = res.data || [];
        }
      })
      .catch(err => {
        console.error("获取用户列表失败", err);
      });
  };
  // ç”¨æˆ·é€‰æ‹©å˜åŒ–æ—¶æ›´æ–° userName
  const handleUserChange = (userId) => {
    if (userId) {
      const selectedUser = userOptions.value.find(user => user.userId === userId);
      if (selectedUser) {
        reportForm.userName = selectedUser.userName;
      }
    } else {
      reportForm.userName = "";
    }
  };
  onMounted(() => {
    getList();
    getUserList();
  });
</script>
<style scoped lang="scss">
  .search_form {
    margin-bottom: 20px;
    .search-row {
      display: flex;
      gap: 20px;
      align-items: center;
      .search-item {
        display: flex;
        align-items: center;
        gap: 10px;
      }
    }
  }
  .transfer-card-title {
    font-size: 24px;
    font-weight: bold;
    text-align: center;
    margin-bottom: 20px;
  }
  .transfer-card-container {
    display: flex;
    gap: 20px;
    height: 350px;
    .transfer-card-info {
      flex: 1;
      overflow: auto;
      .info-group {
        width: 50%;
        float: left;
      }
      .info-item {
        display: flex;
        margin-bottom: 15px;
        .info-label {
          width: 120px;
          font-weight: bold;
          margin-right: 20px;
        }
        .info-value {
          flex: 1;
        }
      }
    }
    .transfer-card-qr {
      width: 240px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    }
  }
</style>
<style  lang="scss">
  @media print {
    @page {
      size: landscape;
    }
    body * {
      visibility: hidden;
    }
    .el-dialog__wrapper,
    .el-dialog,
    .el-dialog__body,
    .transfer-card-title,
    .transfer-card-container,
    .transfer-card-container *,
    .info-item,
    .info-label,
    .info-value {
      visibility: visible;
    }
    .print-button-container {
      visibility: hidden;
    }
    .el-dialog__wrapper {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      margin: 0;
    }
    .el-dialog {
      width: 100% !important;
      max-width: 800px;
      margin: 0 auto !important;
    }
    .el-dialog__header,
    .el-dialog__footer {
      display: none;
    }
    .el-dialog__body {
      padding: 20px;
    }
    .transfer-card-container {
      height: auto;
      display: flex;
      gap: 20px;
    }
    .transfer-card-info {
      flex: 1;
      .info-group {
        width: 100%;
        float: none;
        margin-bottom: 20px;
      }
      .info-item {
        display: flex;
        margin-bottom: 10px;
        .info-label {
          width: 100px;
          font-weight: bold;
          margin-right: 15px;
          white-space: nowrap;
        }
        .info-value {
          flex: 1;
          word-break: break-word;
        }
      }
    }
    .transfer-card-qr {
      width: 160px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    }
    .qr-container img {
      width: 140px !important;
      height: 140px !important;
    }
  }
</style>
src/views/qualityManagement/finalInspection/components/filesDia.vue
@@ -51,7 +51,6 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
  qualityInspectParamDel,
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -58,7 +58,7 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
                            <el-select v-model="form.checkName" placeholder="请选择" clearable filterable>
                            <el-select v-model="form.checkName" placeholder="请选择" clearable>
                                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                                                     :value="item.nickName"/>
                            </el-select>
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue
@@ -34,7 +34,6 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
  qualityInspectParamDel,
src/views/qualityManagement/finalInspection/index.vue
@@ -44,7 +44,7 @@
                             @close="closeDia">
            <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
                <el-form-item label="检验员:" prop="checkName">
                    <el-select v-model="form.checkName" placeholder="请选择" clearable filterable>
                    <el-select v-model="form.checkName" placeholder="请选择" clearable>
                        <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                                             :value="item.nickName"/>
                    </el-select>
src/views/qualityManagement/metricBinding/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,504 @@
<template>
  <div class="app-container metric-binding">
    <!-- å·¦ä¾§ï¼šæ£€æµ‹æ ‡å‡†åˆ—表(只读) -->
    <div class="left-panel">
      <PIMTable
        rowKey="id"
        :column="standardColumns"
        :tableData="standardTableData"
        :page="page"
        :isSelection="false"
        :rowClassName="rowClassNameCenter"
        :tableLoading="tableLoading"
        :rowClick="handleTableRowClick"
        @pagination="handlePagination"
        :total="page.total"
      >
        <template #standardNoCell="{ row }">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
            {{ row.standardNo }}
          </span>
        </template>
        <!-- è¡¨å¤´æœç´¢ -->
        <template #standardNoHeader>
          <el-input
            v-model="searchForm.standardNo"
            placeholder="标准编号"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #standardNameHeader>
          <el-input
            v-model="searchForm.standardName"
            placeholder="标准名称"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #inspectTypeHeader>
          <el-select
            v-model="searchForm.inspectType"
            placeholder="类别"
            clearable
            size="small"
            style="width: 120px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="原材料检验" value="0" />
            <el-option label="过程检验" value="1" />
            <el-option label="出厂检验" value="2" />
          </el-select>
        </template>
        <template #stateHeader>
          <el-select
            v-model="searchForm.state"
            placeholder="状态"
            clearable
            size="small"
            style="width: 110px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
          </el-select>
        </template>
      </PIMTable>
    </div>
    <!-- å³ä¾§ï¼šç»‘定列表 -->
    <div class="right-panel">
      <div class="right-header">
        <div class="title">绑定关系</div>
        <div class="desc" v-if="currentStandard">
          å½“前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
        </div>
        <div class="desc" v-else>请选择左侧检测标准</div>
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">添加绑定</el-button>
        <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">删除</el-button>
      </div>
      <el-table
        v-loading="bindingLoading"
        :data="bindingTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleBindingSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="productName" label="产品名称" min-width="140" />
        <el-table-column label="操作" width="120" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="danger" size="small" @click="handleUnbind(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- æ·»åŠ ç»‘å®šå¼¹æ¡† -->
    <el-dialog
      v-model="bindingDialogVisible"
      title="添加绑定"
      width="520px"
      @close="closeBindingDialog"
    >
      <el-form label-width="100px">
        <el-form-item label="产品">
          <el-tree-select
            v-model="selectedProductIds"
            multiple
            collapse-tags
            collapse-tags-tooltip
            placeholder="请选择产品(可多选)"
            clearable
            check-strictly
            :data="productOptions"
            :render-after-expand="false"
            style="width: 100%"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="closeBindingDialog">取消</el-button>
          <el-button type="primary" @click="submitBinding">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from 'element-plus'
import PIMTable from '@/components/PIMTable/PIMTable.vue'
import { productTreeList } from '@/api/basicData/product.js'
import {
  qualityTestStandardListPage
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
import {
  qualityTestStandardBindingList,
  qualityTestStandardBindingAdd,
  qualityTestStandardBindingDel
} from '@/api/qualityManagement/qualityTestStandardBinding.js'
const { proxy } = getCurrentInstance()
// å·¦ä¾§æ ‡å‡†åˆ—表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    state: '',
    inspectType: ''
  }
})
const { searchForm } = toRefs(data)
// å·¦ä¾§
const standardTableData = ref([])
const tableLoading = ref(false)
const page = reactive({ current: 1, size: 10, total: 0 })
// å·¥åºä¸‹æ‹‰ï¼ˆç”¨äºŽåˆ—表回显)
const processOptions = ref([])
const getProcessList = async () => {
  try {
    const res = await productProcessListPage({ current: 1, size: 1000 })
    if (res?.code === 200) {
      const records = res?.data?.records || []
      processOptions.value = records.map((item) => ({
        label: item.processName || item.name || item.label,
        value: item.id || item.processId || item.value
      }))
    }
  } catch (error) {
    console.error('获取工序列表失败:', error)
  }
}
const standardColumns = ref([
  { label: '标准编号', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, align: 'center', headerSlot: 'standardNoHeader' },
  { label: '标准名称', prop: 'standardName', minWidth: 180, align: 'center', headerSlot: 'standardNameHeader' },
  {
    label: '类别',
    prop: 'inspectType',
    headerSlot: 'inspectTypeHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = { 0: '原材料检验', 1: '过程检验', 2: '出厂检验' }
      return map[val] || val
    }
  },
  {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
  },
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160,
    align: 'center'
  }
  // {
  //   label: '状态',
  //   prop: 'state',
  //   headerSlot: 'stateHeader',
  //   dataType: 'tag',
  //   formatData: (val) => {
  //     const map = { 0: '草稿', 1: '通过', 2: '撤销' }
  //     return map[val] || val
  //   },
  //   formatType: (val) => {
  //     if (val == 1) return 'success'
  //     if (val == 2) return 'warning'
  //     return 'info'
  //   }
  // }
])
const currentStandard = ref(null)
// å³ä¾§ç»‘定
const bindingTableData = ref([])
const bindingLoading = ref(false)
const bindingSelectedRows = ref([])
const bindingDialogVisible = ref(false)
// äº§å“æ ‘(用于绑定选择)
const productOptions = ref([])
const selectedProductIds = ref([])
const getProductOptions = async () => {
  // é¿å…é‡å¤è¯·æ±‚
  if (productOptions.value?.length) return
  const res = await productTreeList()
  productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
}
function convertIdToValue(data) {
  return (data || []).map((item) => {
    const { id, children, ...rest } = item
    const newItem = {
      ...rest,
      value: id
    }
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children)
    }
    return newItem
  })
}
const handleQuery = () => {
  page.current = 1
  getStandardList()
}
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
const getStandardList = () => {
  tableLoading.value = true
  qualityTestStandardListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
    state: 1
  })
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
    })
    .finally(() => {
      tableLoading.value = false
    })
}
// è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
const loadBindingList = () => {
  if (!currentStandard.value?.id) {
    bindingTableData.value = []
    return
  }
  bindingLoading.value = true
  qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
    .then((res) => {
      const base = res?.data || []
      // å°†å½“前标准的工序和备注带到绑定列表中展示
      bindingTableData.value = base.map((item) => ({
        ...item,
        processId: currentStandard.value?.processId,
        remark: currentStandard.value?.remark
      }))
    })
    .finally(() => {
      bindingLoading.value = false
    })
}
const handleBindingSelectionChange = (selection) => {
  bindingSelectedRows.value = selection
}
const openBindingDialog = () => {
  if (!currentStandard.value?.id) return
  selectedProductIds.value = []
  getProductOptions()
  bindingDialogVisible.value = true
}
const closeBindingDialog = () => {
  bindingDialogVisible.value = false
}
const submitBinding = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  const ids = (selectedProductIds.value || []).filter(Boolean)
  if (!ids.length) {
    proxy.$message.warning('请选择产品')
    return
  }
  const payload = ids.map((pid) => ({
    productId: pid,
    testStandardId
  }))
  await qualityTestStandardBindingAdd(payload)
  proxy.$message.success('添加成功')
  bindingDialogVisible.value = false
  loadBindingList()
}
const handleUnbind = async (row) => {
  if (!row?.id) return
  try {
    await ElMessageBox.confirm('确认删除该绑定?', '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardBindingDel([row.qualityTestStandardBindingId])
  proxy.$message.success('删除成功')
  loadBindingList()
}
const handleBatchUnbind = async () => {
  if (!bindingSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = bindingSelectedRows.value.map((i) => i.qualityTestStandardBindingId)
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardBindingDel(ids)
  proxy.$message.success('删除成功')
  loadBindingList()
}
onMounted(() => {
  getStandardList()
  getProcessList()
})
</script>
<style scoped>
.metric-binding {
  display: flex;
  gap: 16px;
}
.left-panel,
.right-panel {
  flex: 1;
  background: #ffffff;
  padding: 16px;
  box-sizing: border-box;
}
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.toolbar-right {
  flex-shrink: 0;
}
.right-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
.right-header .title {
  font-size: 16px;
  font-weight: 600;
}
.right-header .desc {
  font-size: 13px;
  color: #666;
}
.right-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 10px;
}
.link-text {
  color: #409eff;
  cursor: default;
}
.clickable-link {
  color: #409eff;
  cursor: pointer;
}
.clickable-link:hover {
  text-decoration: underline;
}
:deep(.row-center td) {
  text-align: center !important;
}
/* el-table è¡¨å¤´/内容统一居中(row-class-name ä¸ä½œç”¨äºŽè¡¨å¤´ï¼‰ */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
/* PIMTable è¡¨å¤´å±…中 */
:deep(.lims-table .pim-table-header-cell) {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
:deep(.lims-table .pim-table-header-title) {
  text-align: center;
  width: 100%;
}
:deep(.lims-table .pim-table-header-extra) {
  width: 100%;
  margin-top: 4px;
}
</style>
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    :title="computedTitle"
    :operation-type="operationType"
    width="520px"
    @close="emit('close')"
    @cancel="handleCancel"
    @confirm="handleConfirm"
  >
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      label-width="100px"
    >
      <el-form-item label="参数项" prop="parameterItem">
        <el-input v-model="form.parameterItem" placeholder="请输入参数项" />
      </el-form-item>
      <el-form-item label="单位" prop="unit">
        <el-input v-model="form.unit" placeholder="请输入单位" />
      </el-form-item>
      <el-form-item label="标准值" prop="standardValue">
        <el-input v-model="form.standardValue" placeholder="请输入标准值" />
      </el-form-item>
      <el-form-item label="内控值" prop="controlValue">
        <el-input v-model="form.controlValue" placeholder="请输入内控值" />
      </el-form-item>
      <el-form-item label="默认值" prop="defaultValue">
        <el-input v-model="form.defaultValue" placeholder="请输入默认值" />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { computed, ref } from 'vue'
import FormDialog from '@/components/Dialog/FormDialog.vue'
const props = defineProps({
  modelValue: { type: Boolean, default: false },
  operationType: { type: String, default: 'add' }, // add | edit
  form: { type: Object, required: true }
})
const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
const formRef = ref(null)
const rules = {
  parameterItem: [{ required: true, message: '请输入参数项', trigger: 'blur' }],
  unit: [{ required: true, message: '请输入单位', trigger: 'blur' }]
}
const computedTitle = computed(() => (props.operationType === 'edit' ? '编辑标准参数' : '新增标准参数'))
const handleConfirm = () => {
  formRef.value?.validate?.((valid) => {
    if (valid) emit('confirm')
  })
}
const handleCancel = () => {
  emit('cancel')
  dialogVisible.value = false
}
const resetFields = () => {
  formRef.value?.resetFields?.()
}
defineExpose({ resetFields })
</script>
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    :title="computedTitle"
    :operation-type="operationType"
    :width="width"
    @close="emit('close')"
    @cancel="handleCancel"
    @confirm="handleConfirm"
  >
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      label-width="100px"
    >
      <el-form-item label="标准编号" prop="standardNo">
        <el-input v-model="form.standardNo" placeholder="请输入标准编号" />
      </el-form-item>
      <el-form-item label="标准名称" prop="standardName">
        <el-input v-model="form.standardName" placeholder="请输入标准名称" />
      </el-form-item>
      <el-form-item label="类别" prop="inspectType">
        <el-select v-model="form.inspectType" placeholder="请选择类别" style="width: 100%">
          <el-option label="原材料检验" value="0" />
          <el-option label="过程检验" value="1" />
          <el-option label="出厂检验" value="2" />
        </el-select>
      </el-form-item>
      <el-form-item label="工序" prop="processId">
        <el-select v-model="form.processId" placeholder="请选择工序" style="width: 100%">
          <el-option
            v-for="item in processOptions"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="状态" prop="state">
        <el-select v-model="form.state" placeholder="请选择状态" style="width: 100%">
          <el-option label="草稿" value="0" />
          <el-option label="通过" value="1" />
          <el-option label="撤销" value="2" />
        </el-select>
      </el-form-item>
      <el-form-item label="备注" prop="remark">
        <el-input
          v-model="form.remark"
          type="textarea"
          :rows="3"
          placeholder="请输入备注"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { computed, ref } from 'vue'
import FormDialog from '@/components/Dialog/FormDialog.vue'
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  operationType: {
    type: String,
    default: 'add'
  },
  form: {
    type: Object,
    required: true
  },
  rules: {
    type: Object,
    default: () => ({})
  },
  processOptions: {
    type: Array,
    default: () => []
  },
  width: {
    type: String,
    default: '500px'
  }
})
const emit = defineEmits(['update:modelValue', 'close', 'cancel', 'confirm'])
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
const formRef = ref(null)
const computedTitle = computed(() => {
  if (props.operationType === 'edit') return '编辑检测标准'
  if (props.operationType === 'copy') return '复制检测标准'
  return '新增检测标准'
})
const handleConfirm = () => {
  if (!formRef.value) {
    emit('confirm')
    return
  }
  formRef.value.validate((valid) => {
    if (valid) {
      emit('confirm')
    }
  })
}
const handleCancel = () => {
  emit('cancel')
  dialogVisible.value = false
}
const resetFields = () => {
  formRef.value?.resetFields?.()
}
defineExpose({
  resetFields
})
</script>
src/views/qualityManagement/metricMaintenance/index.vue
@@ -1,415 +1,834 @@
<template>
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
            v-model="search"
            style="width: 210px"
            placeholder="输入关键字进行搜索"
            @change="searchFilter"
            @clear="searchFilter"
            clearable
            prefix-icon="Search"
        />
  <div class="app-container metric-maintenance">
    <!-- å·¦ä¾§ï¼šæ£€æµ‹æ ‡å‡†åˆ—表 -->
    <div class="left-panel">
      <div class="toolbar">
        <div class="toolbar-left"></div>
        <div class="toolbar-right">
          <el-button type="primary" @click="openStandardDialog('add')">新增</el-button>
          <el-button type="success" plain @click="handleBatchAudit(1)">批准</el-button>
          <el-button type="warning" plain @click="handleBatchAudit(2)">撤销</el-button>
          <el-button type="danger" plain @click="handleBatchDelete">删除</el-button>
      </div>
      <div ref="containerRef">
        <el-tree
            ref="tree"
            v-loading="treeLoad"
            :data="list"
            @node-click="handleNodeClick"
            :expand-on-click-node="false"
            default-expand-all
            :default-expanded-keys="expandedKeys"
            :draggable="true"
            :filter-node-method="filterNode"
            :props="{ children: 'children', label: 'label' }"
            highlight-current
            node-key="id"
            style="
            height: calc(100vh - 190px);
            overflow-y: scroll;
            scrollbar-width: none;
          "
        >
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span class="tree-node-content">
                <el-icon class="orange-icon">
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                {{ data.label }}
              </span>
            </div>
          </template>
        </el-tree>
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="openModelDia('add')">
          æ–°å¢žæ£€æµ‹æŒ‡æ ‡
        </el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button
            type="danger"
            @click="handleDelete"
            style="margin-left: 10px"
            plain
        >
          åˆ é™¤
        </el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
        :column="standardColumns"
        :tableData="standardTableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
        :rowClassName="rowClassNameCenter"
        :rowClick="handleTableRowClick"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
          :total="page.total"
      ></PIMTable>
    </div>
    <el-dialog
        v-model="modelDia"
        title="检测指标"
        width="400px"
        @close="closeModelDia"
    >
      <el-form
          :model="modelForm"
          label-width="140px"
          label-position="top"
          :rules="modelRules"
          ref="modelFormRef"
      >
        <el-row>
          <el-col :span="24">
            <el-form-item label="指标:" prop="parameterItem">
              <el-input
                  v-model="modelForm.parameterItem"
                  placeholder="请输入指标"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="单位:" prop="unit">
              <el-input
                  v-model="modelForm.unit"
                  placeholder="请输入单位"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="标准值:" prop="standardValue">
              <el-input
                  v-model="modelForm.standardValue"
                  placeholder="请输入标准值"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="内控值:" prop="controlValue">
              <el-input
                  v-model="modelForm.controlValue"
                  placeholder="请输入内控值"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitModelForm">确认</el-button>
          <el-button @click="closeModelDia">取消</el-button>
        </div>
        <template #standardNoCell="{ row }">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
            {{ row.standardNo }}
          </span>
      </template>
    </el-dialog>
        <!-- è¡¨å¤´æœç´¢æ’æ§½ -->
        <template #standardNoHeader>
          <el-input
            v-model="searchForm.standardNo"
            placeholder="标准编号"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #standardNameHeader>
          <el-input
            v-model="searchForm.standardName"
            placeholder="标准名称"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #inspectTypeHeader>
          <el-select
            v-model="searchForm.inspectType"
            placeholder="类别"
            clearable
            size="small"
            style="width: 120px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="原材料检验" value="0" />
            <el-option label="过程检验" value="1" />
            <el-option label="出厂检验" value="2" />
          </el-select>
        </template>
        <template #stateHeader>
          <el-select
            v-model="searchForm.state"
            placeholder="状态"
            clearable
            size="small"
            style="width: 110px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
          </el-select>
        </template>
      </PIMTable>
    </div>
    <!-- å³ä¾§ï¼šæ ‡å‡†å‚数列表 -->
    <div class="right-panel">
      <div class="right-header">
        <div class="title">标准参数</div>
        <div class="desc" v-if="currentStandard">
          æ‚¨å½“前选择的检测标准编号是:
          <span class="link-text">{{ currentStandard.standardNo }}</span>
        </div>
        <div class="desc" v-else>请先在左侧选择一个检测标准</div>
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard || isStandardReadonly" @click="openParamDialog('add')">
          æ–°å¢ž
        </el-button>
        <el-button type="danger" plain :disabled="!currentStandard || isStandardReadonly" @click="handleParamBatchDelete">
          åˆ é™¤
        </el-button>
      </div>
      <el-table
        v-loading="detailLoading"
        :data="detailTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleParamSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="parameterItem" label="参数项" min-width="120" />
        <el-table-column prop="unit" label="单位" width="80" />
        <el-table-column prop="standardValue" label="标准值" min-width="120" />
        <el-table-column prop="controlValue" label="内控值" min-width="120" />
        <el-table-column prop="defaultValue" label="默认值" min-width="120" />
        <el-table-column label="操作" width="140" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="primary" size="small" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
              ç¼–辑
            </el-button>
            <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- æ–°å¢ž / ç¼–辑检测标准 -->
    <StandardFormDialog
      ref="standardFormDialogRef"
      v-model="standardDialogVisible"
      :operation-type="standardOperationType"
      :form="standardForm"
      :rules="standardRules"
      :process-options="processOptions"
      @confirm="submitStandardForm"
      @close="closeStandardDialog"
      @cancel="closeStandardDialog"
    />
    <ParamFormDialog
      ref="paramFormDialogRef"
      v-model="paramDialogVisible"
      :operation-type="paramOperationType"
      :form="paramForm"
      @confirm="submitParamForm"
      @close="closeParamDialog"
      @cancel="closeParamDialog"
    />
  </div>
</template>
<script setup>
import {ref} from "vue";
import {addOrEditProductModel, delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
import {ElMessageBox} from "element-plus";
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import {
  qualityTestStandardAdd, qualityTestStandardDel,
  qualityTestStandardListPage,
  qualityTestStandardUpdate
} from "@/api/qualityManagement/metricMaintenance.js";
const { proxy } = getCurrentInstance();
// æ ‘
const search = ref("");
const treeLoad = ref(false);
const list = ref([]);
const expandedKeys = ref([]);
const currentId = ref("");
const currentParentId = ref("");
// æŒ‡æ ‡è¡¨æ ¼
const tableData = ref([]);
const tableLoading = ref(false);
  qualityTestStandardAdd,
  qualityTestStandardUpdate,
  qualityTestStandardDel,
  qualityTestStandardCopyParam,
  qualityTestStandardAudit,
  qualityTestStandardParamList,
  qualityTestStandardParamAdd,
  qualityTestStandardParamUpdate,
  qualityTestStandardParamDel
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
import StandardFormDialog from './StandardFormDialog.vue'
import ParamFormDialog from './ParamFormDialog.vue'
const { proxy } = getCurrentInstance()
// å·¦ä¾§æ ‡å‡†åˆ—表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
// æ ‡å‡†çŠ¶æ€ä¸ºâ€œé€šè¿‡(1)”时,右侧参数禁止增删改
const isStandardReadonly = computed(() => {
  const state = currentStandard.value?.state
  return state === 1 || state === '1'
})
// æœç´¢æ¡ä»¶
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    remark: '',
    state: '',
    inspectType: '',
    processId: ''
  },
  standardForm: {
    id: undefined,
    standardNo: '',
    standardName: '',
    remark: '',
    state: '0',
    inspectType: '',
    processId: ''
  },
  standardRules: {
    standardNo: [{ required: true, message: '请输入标准编号', trigger: 'blur' }],
    standardName: [{ required: true, message: '请输入标准名称', trigger: 'blur' }],
    inspectType: [{ required: true, message: '请选择检测类型', trigger: 'change' }],
    processId: [{ required: false, message: '请选择工序', trigger: 'change' }]
  }
})
const { searchForm, standardForm, standardRules } = toRefs(data)
// å·¦ä¾§è¡¨æ ¼
const standardTableData = ref([])
const selectedRows = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 10,
});
const tableColumn = ref([
  total: 0
})
// å·¥åºä¸‹æ‹‰
const processOptions = ref([])
// èŽ·å–å·¥åºåˆ—è¡¨
const getProcessList = async () => {
  try {
    const res = await productProcessListPage({ current: 1, size: 1000 })
    if (res?.code === 200) {
      const records = res?.data?.records || []
      processOptions.value = records.map(item => ({
        label: item.processName || item.name || item.label,
        value: item.id || item.processId || item.value
      }))
    }
  } catch (error) {
    console.error('获取工序列表失败:', error)
  }
}
// å½“前选中的标准 & å³ä¾§è¯¦æƒ…
const currentStandard = ref(null)
const detailTableData = ref([])
const detailLoading = ref(false)
const paramSelectedRows = ref([])
const paramDialogVisible = ref(false)
const paramOperationType = ref('add') // add | edit
const paramFormDialogRef = ref(null)
const paramForm = reactive({
  id: undefined,
  parameterItem: '',
  unit: '',
  standardValue: '',
  controlValue: '',
  defaultValue: ''
})
// å¼¹çª—
const standardDialogVisible = ref(false)
const standardOperationType = ref('add') // add | edit | copy
const standardFormDialogRef = ref(null)
// åˆ—定义
const standardColumns = ref([
  {
    label: "指标",
    prop: "parameterItem",
    label: '标准编号',
    prop: 'standardNo',
    dataType: 'slot',
    slot: 'standardNoCell',
    minWidth: 160,
    align: 'center',
    headerSlot: 'standardNoHeader'
  },
  {
    label: "单位",
    prop: "unit",
    label: '标准名称',
    prop: 'standardName',
    minWidth: 180,
    align: 'center',
    headerSlot: 'standardNameHeader'
  },
  {
    label: "标准值",
    prop: "standardValue",
    label: '类别',
    prop: 'inspectType',
    headerSlot: 'inspectTypeHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = {
        0: '原材料检验',
        1: '过程检验',
        2: '出厂检验'
      }
      return map[val] || val
    }
  },
  {
    label: "内控值",
    prop: "controlValue",
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    label: '状态',
    prop: 'state',
    headerSlot: 'stateHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = {
        0: '草稿',
        1: '通过',
        2: '撤销'
      }
      return map[val] || val
    },
    formatType: (val) => {
      if (val === '1' || val === 1) return 'success'
      if (val === '2' || val === 2) return 'warning'
      return 'info'
    }
  },
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160,
    align: 'center'
  },
  {
    dataType: 'action',
    label: '操作',
    align: 'center',
    fixed: 'right',
    width: 220,
    operation: [
      {
        name: "编辑",
        type: "text",
        name: '编辑',
        type: 'text',
        clickFun: (row) => {
          openModelDia("edit", row);
          openStandardDialog('edit', row)
        }
        },
      {
        name: '复制',
        type: 'text',
        clickFun: async (row) => {
          if (!row?.id) return
          try {
            await ElMessageBox.confirm('确认复制该标准参数?', '提示', { type: 'warning' })
          } catch {
            return
          }
          await qualityTestStandardCopyParam(row.id)
          proxy.$message.success('复制成功')
          getStandardList()
          if (currentStandard.value?.id === row.id) {
            loadDetail(row.id)
          }
        }
      },
    ],
  },
]);
const selectedRows = ref([]);
// æŒ‡æ ‡å¼¹æ¡†
const modelDia = ref(false);
const modelOperationType = ref("");
const data = reactive({
  modelForm: {
    parameterItem: "",
    unit: "",
    standardValue: "",
    controlValue: "",
  },
  modelRules: {
    parameterItem: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    standardValue: [{ required: true, message: "请输入", trigger: "blur" }],
    // controlValue: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
const { modelForm, modelRules } = toRefs(data);
      {
        name: '删除',
        type: 'text',
        clickFun: (row) => {
          handleDelete(row)
        }
      }
    ]
  }
])
// æŸ¥è¯¢äº§å“æ ‘
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList().then((res) => {
    list.value = res;
    list.value.forEach((a) => {
      expandedKeys.value.push(a.label);
    });
    treeLoad.value = false;
  }).catch((err) => {
    treeLoad.value = false;
  });
};
// è¿‡æ»¤äº§å“æ ‘
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
};
// é€‰æ‹©äº§å“
const handleNodeClick = (val, node, el) => {
  // åªæœ‰å¶å­èŠ‚ç‚¹æ‰æ‰§è¡Œä»¥ä¸‹é€»è¾‘
  currentId.value = val.id;
  currentParentId.value = val.parentId;
  getModelList();
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æŸ¥è¯¢æŒ‡æ ‡æ•°æ®
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
};
const getModelList = () => {
  tableLoading.value = true;
  qualityTestStandardListPage({
    productId: currentId.value,
// æŸ¥è¯¢åˆ—表
const getStandardList = () => {
  tableLoading.value = true
  const params = {
    ...searchForm.value,
    current: page.current,
    size: page.size,
  }).then((res) => {
    tableData.value = res.data.records;
    page.total = res.data.total;
    tableLoading.value = false;
  });
};
// è°ƒç”¨tree过滤方法 ä¸­æ–‡è‹±è¿‡æ»¤
const filterNode = (value, data, node) => {
  if (!value) {
    //如果数据为空,则返回true,显示所有的数据项
    return true;
    size: page.size
  }
  // æŸ¥è¯¢åˆ—表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase();
  return chooseNode(val, data, node); // è°ƒç”¨è¿‡æ»¤äºŒå±‚方法
};
// è¿‡æ»¤çˆ¶èŠ‚ç‚¹ / å­èŠ‚ç‚¹ (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true;
  qualityTestStandardListPage(params)
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
    })
    .finally(() => {
      tableLoading.value = false
    })
  }
  const level = node.level;
  // å¦‚果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false;
  }
  // å…ˆå–当前节点的父节点
  let parentData = node.parent;
  // éåŽ†å½“å‰èŠ‚ç‚¹çš„çˆ¶èŠ‚ç‚¹
  let index = 0;
  while (index < level - 1) {
    // å¦‚果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
      return true;
    }
    // å¦åˆ™çš„话再往上一层做匹配
    parentData = parentData.parent;
    index++;
  }
  // æ²¡åŒ¹é…åˆ°è¿”回false
  return false;
};
// æ‰“开指标弹框
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true;
  modelForm.value.model = "";
  modelForm.value.model = "";
  modelForm.value.id = "";
  if (type === "edit") {
    modelForm.value = { ...data };
  }
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    proxy.download("/quality/qualityTestStandard/export", {}, "检测指标.xlsx");
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
// åˆ é™¤æŒ‡æ ‡
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
const handleQuery = () => {
  page.current = 1
  getStandardList()
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    tableLoading.value = true;
    qualityTestStandardDel(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      getModelList();
    }).finally(() => {
      tableLoading.value = false;
    });
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
// æäº¤è§„格型号修改
const submitModelForm = () => {
  proxy.$refs.modelFormRef.validate((valid) => {
    if (valid) {
      modelForm.value.productId = Number(currentId.value);
      if(modelOperationType.value === 'add') {
        qualityTestStandardAdd(modelForm.value).then((res) => {
          proxy.$modal.msgSuccess("提交成功");
          closeModelDia();
          getModelList();
        });
const resetQuery = () => {
  searchForm.value.standardNo = ''
  searchForm.value.standardName = ''
  searchForm.value.remark = ''
  searchForm.value.state = ''
  searchForm.value.inspectType = ''
  searchForm.value.processId = ''
  handleQuery()
}
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// æ‰¹é‡å®¡æ ¸ï¼šçŠ¶æ€ 1=批准,2=撤销
const handleBatchAudit = async (state) => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const text = state === 1 ? '批准' : '撤销'
  const payload = selectedRows.value
    .filter(i => i?.id)
    .map((item) => ({ id: item.id, state }))
  if (!payload.length) {
    proxy.$message.warning('请选择有效数据')
    return
  }
  try {
    await ElMessageBox.confirm(`确认${text}选中的标准?`, '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardAudit(payload)
  proxy.$message.success(`${text}成功`)
  getStandardList()
}
// è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§å‚æ•°
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
}
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§å‚æ•°ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
}
const loadDetail = (standardId) => {
  if (!standardId) {
    detailTableData.value = []
    return
  }
  detailLoading.value = true
  qualityTestStandardParamList({ testStandardId: standardId }).then((res) => {
    detailTableData.value = res?.data || []
  })
    .finally(() => {
      detailLoading.value = false
    })
}
const handleParamSelectionChange = (selection) => {
  paramSelectedRows.value = selection
}
const openParamDialog = (type, row) => {
  if (!currentStandard.value?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  paramOperationType.value = type
  if (type === 'add') {
    Object.assign(paramForm, {
      id: undefined,
      parameterItem: '',
      unit: '',
      standardValue: '',
      controlValue: '',
      defaultValue: ''
    })
  } else if (type === 'edit' && row) {
    Object.assign(paramForm, row)
  }
  paramDialogVisible.value = true
}
const closeParamDialog = () => {
  paramDialogVisible.value = false
  paramFormDialogRef.value?.resetFields?.()
}
const submitParamForm = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  const payload = { ...paramForm, testStandardId }
  if (paramOperationType.value === 'edit') {
    await qualityTestStandardParamUpdate(payload)
    proxy.$message.success('提交成功')
      } else {
        qualityTestStandardUpdate(modelForm.value).then((res) => {
          proxy.$modal.msgSuccess("提交成功");
          closeModelDia();
          getModelList();
        });
    await qualityTestStandardParamAdd(payload)
    proxy.$message.success('提交成功')
  }
  closeParamDialog()
  loadDetail(testStandardId)
}
const handleParamDelete = async (row) => {
  if (!row?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除该参数?', '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardParamDel([row.id])
  proxy.$message.success('删除成功')
  loadDetail(currentStandard.value?.id)
}
const handleParamBatchDelete = async () => {
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  if (!paramSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = paramSelectedRows.value.map((i) => i.id)
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardParamDel(ids)
  proxy.$message.success('删除成功')
  loadDetail(currentStandard.value?.id)
}
// æ–°å¢ž / ç¼–辑 / å¤åˆ¶
const openStandardDialog = (type, row) => {
  standardOperationType.value = type
  if (type === 'add') {
    Object.assign(standardForm.value, {
      id: undefined,
      standardNo: '',
      standardName: '',
      remark: '',
      state: '0',
      inspectType: '',
      processId: ''
    })
  } else if (type === 'edit' && row) {
    Object.assign(standardForm.value, {
      ...row,
      // ç¡®ä¿ inspectType å’Œ state è½¬æ¢ä¸ºå­—符串,以匹配 el-select çš„ value ç±»åž‹
      inspectType: row.inspectType !== null && row.inspectType !== undefined ? String(row.inspectType) : '',
      state: row.state !== null && row.state !== undefined ? String(row.state) : '0',
      // ç¡®ä¿ processId è½¬æ¢ä¸ºå­—符串或数字(根据实际需要)
      processId: row.processId !== null && row.processId !== undefined ? row.processId : ''
    })
  } else if (type === 'copy' && row) {
    const { id, ...rest } = row
    Object.assign(standardForm.value, {
      ...rest,
      id: undefined,
      standardNo: '',
      state: '0',
      // ç¡®ä¿ inspectType è½¬æ¢ä¸ºå­—符串
      inspectType: rest.inspectType !== null && rest.inspectType !== undefined ? String(rest.inspectType) : ''
    })
  }
  standardDialogVisible.value = true
}
const closeStandardDialog = () => {
  standardDialogVisible.value = false
  standardFormDialogRef.value?.resetFields?.()
}
const submitStandardForm = () => {
  const payload = { ...standardForm.value }
  const isEdit = standardOperationType.value === 'edit'
  if (isEdit) {
    qualityTestStandardUpdate(payload).then(() => {
      proxy.$message.success('提交成功')
      standardDialogVisible.value = false
      getStandardList()
    })
  } else {
    qualityTestStandardAdd(payload).then(() => {
       proxy.$message.success('提交成功')
      standardDialogVisible.value = false
      getStandardList()
    })
      }
    }
  });
};
// å…³é—­åž‹å·å¼¹æ¡†
const closeModelDia = () => {
  proxy.$refs.modelFormRef.resetFields();
  modelDia.value = false;
};
getProductTreeList();
// åˆ é™¤ï¼ˆå•条)
const handleDelete = (row) => {
  const ids = [row.id]
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      tableLoading.value = true
      qualityTestStandardDel(ids)
        .then(() => {
          proxy.$message.success('删除成功')
          getStandardList()
          if (currentStandard.value && currentStandard.value.id === row.id) {
            currentStandard.value = null
            detailTableData.value = []
          }
        })
        .finally(() => {
          tableLoading.value = false
        })
    })
    .catch(() => {
      proxy.$modal?.msg('已取消')
    })
}
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = selectedRows.value.map((item) => item.id)
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      tableLoading.value = true
      qualityTestStandardDel(ids)
        .then(() => {
           proxy.$message.success('删除成功')
          getStandardList()
          if (currentStandard.value && ids.includes(currentStandard.value.id)) {
            currentStandard.value = null
            detailTableData.value = []
          }
        })
        .finally(() => {
          tableLoading.value = false
        })
    })
    .catch(() => {
      proxy.$modal?.msg('已取消')
    })
}
onMounted(() => {
  getProcessList()
  getStandardList()
})
</script>
<style scoped>
.product-view {
.metric-maintenance {
  display: flex;
  gap: 16px;
  min-width: 0; /* å…è®¸ flex å­å…ƒç´ æ”¶ç¼© */
}
.left {
  width: 380px;
  padding: 16px;
  background: #ffffff;
}
.right {
  width: calc(100% - 380px);
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
.left-panel,
.right-panel {
  flex: 1;
  min-width: 0; /* å…è®¸ flex å­å…ƒç´ æ”¶ç¼© */
  background: #ffffff;
  padding: 16px;
  box-sizing: border-box;
  overflow: hidden; /* é˜²æ­¢å†…容溢出 */
}
/* ä½Žåˆ†è¾¨çŽ‡é€‚é… */
@media (max-width: 1400px) {
  .metric-maintenance {
    flex-direction: column;
  }
  .left-panel,
  .right-panel {
    width: 100%;
    min-width: 0;
  }
}
@media (max-width: 768px) {
  .metric-maintenance {
    gap: 12px;
  }
  .left-panel,
  .right-panel {
    padding: 12px;
  }
}
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  flex-wrap: wrap;
  gap: 8px;
}
.toolbar-left {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
  flex-wrap: wrap;
  gap: 4px;
}
.tree-node-content {
.toolbar-right {
  flex-shrink: 0;
  display: flex;
  align-items: center; /* åž‚直居中 */
  height: 100%;
  flex-wrap: wrap;
  gap: 8px;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px; /* å›¾æ ‡ä¸Žæ–‡å­—之间加点间距 */
.search-label {
  margin: 0 4px 0 12px;
}
.search-label:first-of-type {
  margin-left: 0;
}
.right-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
.right-header .title {
  font-size: 16px;
  font-weight: 600;
}
.right-header .desc {
  font-size: 13px;
  color: #666;
}
.right-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 10px;
}
.link-text {
  color: #409eff;
  cursor: default;
}
.clickable-link {
  color: #409eff;
  cursor: pointer;
}
.clickable-link:hover {
  text-decoration: underline;
}
:deep(.row-center td) {
  text-align: center !important;
}
/* el-table è¡¨å¤´/内容统一居中(row-class-name ä¸ä½œç”¨äºŽè¡¨å¤´ï¼‰ */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
/* PIMTable è¡¨å¤´å±…中 */
:deep(.lims-table .pim-table-header-cell) {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
:deep(.lims-table .pim-table-header-title) {
  text-align: center;
  width: 100%;
}
:deep(.lims-table .pim-table-header-extra) {
  width: 100%;
  margin-top: 4px;
}
</style>
src/views/qualityManagement/metricMaintenance/index0.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,415 @@
<template>
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
            v-model="search"
            style="width: 210px"
            placeholder="输入关键字进行搜索"
            @change="searchFilter"
            @clear="searchFilter"
            clearable
            prefix-icon="Search"
        />
      </div>
      <div ref="containerRef">
        <el-tree
            ref="tree"
            v-loading="treeLoad"
            :data="list"
            @node-click="handleNodeClick"
            :expand-on-click-node="false"
            default-expand-all
            :default-expanded-keys="expandedKeys"
            :draggable="true"
            :filter-node-method="filterNode"
            :props="{ children: 'children', label: 'label' }"
            highlight-current
            node-key="id"
            style="
            height: calc(100vh - 190px);
            overflow-y: scroll;
            scrollbar-width: none;
          "
        >
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span class="tree-node-content">
                <el-icon class="orange-icon">
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                {{ data.label }}
              </span>
            </div>
          </template>
        </el-tree>
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="openModelDia('add')">
          æ–°å¢žæ£€æµ‹æŒ‡æ ‡
        </el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button
            type="danger"
            @click="handleDelete"
            style="margin-left: 10px"
            plain
        >
          åˆ é™¤
        </el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      ></PIMTable>
    </div>
    <el-dialog
        v-model="modelDia"
        title="检测指标"
        width="400px"
        @close="closeModelDia"
    >
      <el-form
          :model="modelForm"
          label-width="140px"
          label-position="top"
          :rules="modelRules"
          ref="modelFormRef"
      >
        <el-row>
          <el-col :span="24">
            <el-form-item label="指标:" prop="parameterItem">
              <el-input
                  v-model="modelForm.parameterItem"
                  placeholder="请输入指标"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="单位:" prop="unit">
              <el-input
                  v-model="modelForm.unit"
                  placeholder="请输入单位"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="标准值:" prop="standardValue">
              <el-input
                  v-model="modelForm.standardValue"
                  placeholder="请输入标准值"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="内控值:" prop="controlValue">
              <el-input
                  v-model="modelForm.controlValue"
                  placeholder="请输入内控值"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitModelForm">确认</el-button>
          <el-button @click="closeModelDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {addOrEditProductModel, delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
import {ElMessageBox} from "element-plus";
import {
  qualityTestStandardAdd, qualityTestStandardDel,
  qualityTestStandardListPage,
  qualityTestStandardUpdate
} from "@/api/qualityManagement/metricMaintenance.js";
const { proxy } = getCurrentInstance();
// æ ‘
const search = ref("");
const treeLoad = ref(false);
const list = ref([]);
const expandedKeys = ref([]);
const currentId = ref("");
const currentParentId = ref("");
// æŒ‡æ ‡è¡¨æ ¼
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 10,
});
const tableColumn = ref([
  {
    label: "指标",
    prop: "parameterItem",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "标准值",
    prop: "standardValue",
  },
  {
    label: "内控值",
    prop: "controlValue",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openModelDia("edit", row);
        },
      },
    ],
  },
]);
const selectedRows = ref([]);
// æŒ‡æ ‡å¼¹æ¡†
const modelDia = ref(false);
const modelOperationType = ref("");
const data = reactive({
  modelForm: {
    parameterItem: "",
    unit: "",
    standardValue: "",
    controlValue: "",
  },
  modelRules: {
    parameterItem: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    standardValue: [{ required: true, message: "请输入", trigger: "blur" }],
    controlValue: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
const { modelForm, modelRules } = toRefs(data);
// æŸ¥è¯¢äº§å“æ ‘
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList().then((res) => {
    list.value = res;
    list.value.forEach((a) => {
      expandedKeys.value.push(a.label);
    });
    treeLoad.value = false;
  }).catch((err) => {
    treeLoad.value = false;
  });
};
// è¿‡æ»¤äº§å“æ ‘
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
};
// é€‰æ‹©äº§å“
const handleNodeClick = (val, node, el) => {
  // åªæœ‰å¶å­èŠ‚ç‚¹æ‰æ‰§è¡Œä»¥ä¸‹é€»è¾‘
  currentId.value = val.id;
  currentParentId.value = val.parentId;
  getModelList();
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æŸ¥è¯¢æŒ‡æ ‡æ•°æ®
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
};
const getModelList = () => {
  tableLoading.value = true;
  qualityTestStandardListPage({
    productId: currentId.value,
    current: page.current,
    size: page.size,
  }).then((res) => {
    tableData.value = res.data.records;
    page.total = res.data.total;
    tableLoading.value = false;
  });
};
// è°ƒç”¨tree过滤方法 ä¸­æ–‡è‹±è¿‡æ»¤
const filterNode = (value, data, node) => {
  if (!value) {
    //如果数据为空,则返回true,显示所有的数据项
    return true;
  }
  // æŸ¥è¯¢åˆ—表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase();
  return chooseNode(val, data, node); // è°ƒç”¨è¿‡æ»¤äºŒå±‚方法
};
// è¿‡æ»¤çˆ¶èŠ‚ç‚¹ / å­èŠ‚ç‚¹ (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true;
  }
  const level = node.level;
  // å¦‚果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false;
  }
  // å…ˆå–当前节点的父节点
  let parentData = node.parent;
  // éåŽ†å½“å‰èŠ‚ç‚¹çš„çˆ¶èŠ‚ç‚¹
  let index = 0;
  while (index < level - 1) {
    // å¦‚果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
      return true;
    }
    // å¦åˆ™çš„话再往上一层做匹配
    parentData = parentData.parent;
    index++;
  }
  // æ²¡åŒ¹é…åˆ°è¿”回false
  return false;
};
// æ‰“开指标弹框
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true;
  modelForm.value.model = "";
  modelForm.value.model = "";
  modelForm.value.id = "";
  if (type === "edit") {
    modelForm.value = { ...data };
  }
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    proxy.download("/quality/qualityTestStandard/export", {}, "检测指标.xlsx");
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
// åˆ é™¤æŒ‡æ ‡
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    tableLoading.value = true;
    qualityTestStandardDel(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      getModelList();
    }).finally(() => {
      tableLoading.value = false;
    });
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
// æäº¤è§„格型号修改
const submitModelForm = () => {
  proxy.$refs.modelFormRef.validate((valid) => {
    if (valid) {
      modelForm.value.productId = Number(currentId.value);
      if(modelOperationType.value === 'add') {
        qualityTestStandardAdd(modelForm.value).then((res) => {
          proxy.$modal.msgSuccess("提交成功");
          closeModelDia();
          getModelList();
        });
      } else {
        qualityTestStandardUpdate(modelForm.value).then((res) => {
          proxy.$modal.msgSuccess("提交成功");
          closeModelDia();
          getModelList();
        });
      }
    }
  });
};
// å…³é—­åž‹å·å¼¹æ¡†
const closeModelDia = () => {
  proxy.$refs.modelFormRef.resetFields();
  modelDia.value = false;
};
getProductTreeList();
</script>
<style scoped>
.product-view {
  display: flex;
}
.left {
  width: 380px;
  padding: 16px;
  background: #ffffff;
}
.right {
  width: calc(100% - 380px);
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
.tree-node-content {
  display: flex;
  align-items: center; /* åž‚直居中 */
  height: 100%;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px; /* å›¾æ ‡ä¸Žæ–‡å­—之间加点间距 */
}
</style>
src/views/qualityManagement/nearExpiryReturn/index.vue
@@ -1,6 +1,6 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="产品名称" prop="productName">
        <el-input
          v-model="queryParams.productName"
src/views/qualityManagement/processInspection/components/formDia.vue
@@ -65,7 +65,7 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
                            <el-select v-model="form.checkName" placeholder="请选择" clearable filterable>
                            <el-select v-model="form.checkName" placeholder="请选择" clearable>
                                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                                                     :value="item.nickName"/>
                            </el-select>
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue
@@ -34,7 +34,6 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
  qualityInspectParamDel,
src/views/qualityManagement/processInspection/index.vue
@@ -44,7 +44,7 @@
                             @close="closeDia">
            <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
                <el-form-item label="检验员:" prop="checkName">
                    <el-select v-model="form.checkName" placeholder="请选择" clearable filterable>
                    <el-select v-model="form.checkName" placeholder="请选择" clearable>
                        <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                                             :value="item.nickName"/>
                    </el-select>
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -11,7 +11,6 @@
          <el-col :span="12">
            <el-form-item label="供应商:" prop="supplier">
              <el-select
                                filterable
                  v-model="form.supplier"
                  placeholder="请选择"
                  clearable
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue
@@ -34,7 +34,6 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {Search} from "@element-plus/icons-vue";
import {
  qualityInspectParamDel,
src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -45,7 +45,7 @@
               @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-form-item label="检验员:" prop="checkName">
          <el-select v-model="form.checkName" placeholder="请选择" clearable filterable>
          <el-select v-model="form.checkName" placeholder="请选择" clearable>
            <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                       :value="item.nickName"/>
          </el-select>
src/views/salesManagement/receiptPayment/index.vue
@@ -104,7 +104,6 @@
                    size="small"
                    @click="changeEditType(scope.row)"
                    v-if="!scope.row.editType"
                                        :disabled="scope.row.registrant !== userStore.nickName"
                    >编辑</el-button
                  >
                  <el-button
@@ -113,7 +112,6 @@
                    size="small"
                    @click="saveReceiptPayment(scope.row)"
                    v-if="scope.row.editType"
                                        :disabled="scope.row.registrant !== userStore.nickName"
                    >保存</el-button
                  >
                  <el-button
@@ -121,7 +119,6 @@
                    type="primary"
                    size="small"
                    @click="delReceiptRecord(scope.row)"
                                        :disabled="scope.row.registrant !== userStore.nickName"
                    >删除</el-button
                  >
                </template>
@@ -160,35 +157,28 @@
          width="100"
        />
        <el-table-column
          label="发票号"
          prop="invoiceNo"
          label="规格型号"
          prop="specificationModel"
          show-overflow-tooltip
          width="200"
        />
        <el-table-column
          label="发票金额(元)"
          prop="invoiceTotal"
          show-overflow-tooltip
          :formatter="formattedNumber"
          width="200"
        />
        <el-table-column label="税率(%)" prop="taxRate" show-overflow-tooltip />
        <el-table-column
          label="回款金额(元)"
          prop="receiptPaymentAmountTotal"
          label="已回款金额(元)"
          prop="invoiceTotal"
          show-overflow-tooltip
          :formatter="formattedNumber"
          width="200"
        />
        <el-table-column
          label="待回款金额(元)"
          prop="noReceiptAmount"
          prop="pendingInvoiceTotal"
          show-overflow-tooltip
          width="200"
        >
          <template #default="{ row, column }">
            <el-text type="danger">
              {{ formattedNumber(row, column, row.noReceiptAmount) }}
              {{ formattedNumber(row, column, row.pendingInvoiceTotal) }}
            </el-text>
          </template>
        </el-table-column>
@@ -202,141 +192,105 @@
        @pagination="paginationChange"
      />
    </div>
    <el-dialog
    <FormDialog
      v-model="dialogFormVisible"
      title="新增回款页面"
      width="90%"
      :width="'90%'"
      @close="closeDia"
      @confirm="submitForm"
      @cancel="closeDia"
    >
      <el-table
        :data="dialogTableData"
        v-if="forms.length"
        :data="forms"
        border
        style="width: 100%"
        max-height="500px"
        size="small"
      >
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
          label="销售合同号"
          prop="salesContractNo"
          show-overflow-tooltip
          width="200"
        />
        <el-table-column
          label="客户名称"
          prop="customerName"
          show-overflow-tooltip
          width="200"
        />
        <el-table-column type="index" label="序号" width="50" align="center"/>
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip />
        <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip />
        <el-table-column
          label="产品大类"
          prop="productCategory"
          show-overflow-tooltip
          width="120"
        />
        <el-table-column
          label="规格型号"
          prop="specification"
          show-overflow-tooltip
          width="150"
        />
        <el-table-column
          label="发票号"
          prop="invoiceNo"
          show-overflow-tooltip
          width="180"
        />
        <el-table-column
          label="发票金额(元)"
          prop="invoiceTotal"
          show-overflow-tooltip
          :formatter="formattedNumber"
          width="150"
        />
        <el-table-column
          label="税率(%)"
          prop="taxRate"
          show-overflow-tooltip
          width="100"
        />
        <el-table-column
          label="待回款金额(元)"
          prop="noReceiptAmount"
                    label="规格型号"
                    prop="specificationModel"
          show-overflow-tooltip
          width="150"
        >
                    width="200"
                />
        <el-table-column label="税率(%)" width="110">
          <template #default="{ row }">
            <el-input v-model="row.taxRate" disabled />
          </template>
        </el-table-column>
        <el-table-column
          label="待回款金额(元)"
          prop="pendingInvoiceTotal"
          show-overflow-tooltip
          width="170"
        >
          <template #default="{ row, column }">
            <el-text type="danger">
              {{ formattedNumber(row, null, row.noReceiptAmount) }}
              {{ formattedNumber(row, column, row.pendingInvoiceTotal) }}
            </el-text>
          </template>
        </el-table-column>
        <el-table-column label="本次回款金额(元)" width="180">
          <template #default="scope">
          <template #default="{ row }">
            <el-input-number
              v-model="row.receiptPaymentAmount"
              :step="0.01"
              :min="0"
              :max="scope.row.noReceiptAmount"
              style="width: 100%"
              :max="Number(row.pendingInvoiceTotal || 0)"
              :precision="2"
              v-model="scope.row.receiptPaymentAmount"
              style="width: 100%"
              placeholder="请输入"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="回款方式" width="150">
          <template #default="scope">
            <el-select
              v-model="scope.row.receiptPaymentType"
              placeholder="请选择"
              clearable
              style="width: 100%"
            >
        <el-table-column label="回款形式" width="160">
          <template #default="{ row }">
            <el-select v-model="row.receiptPaymentType" placeholder="请选择" clearable>
              <el-option
                v-for="item in receipt_payment_type"
                :key="item.value"
                :label="item.label"
                :value="item.value"
                v-for="opt in receipt_payment_type"
                :key="opt.value"
                :label="opt.label"
                :value="opt.value"
              />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="回款日期" width="180">
          <template #default="scope">
        <el-table-column label="回款日期" width="170">
          <template #default="{ row }">
            <el-date-picker
              style="width: 100%"
              v-model="scope.row.receiptPaymentDate"
              v-model="row.receiptPaymentDate"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              type="date"
              placeholder="请选择"
              clearable
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="登记人" width="120">
          <template #default="scope">
            <el-input
              v-model="scope.row.registrant"
              placeholder="自动填充"
              disabled
            />
        <el-table-column label="登记人" width="140">
          <template #default="{ row }">
            <el-input v-model="row.registrant" />
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
      <div v-else class="empty-tip">请选择需要回款的记录</div>
    </FormDialog>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { onMounted, ref } from "vue";
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { onMounted, ref, reactive, getCurrentInstance } from "vue";
import {
  receiptPaymentSaveOrUpdate,
  bindInvoiceNoRegPage,
@@ -352,6 +306,7 @@
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const forms = ref([]);
const page = reactive({
  current: 1,
  size: 100,
@@ -361,48 +316,20 @@
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const dialogFormVisible = ref(false);
const dialogTableData = ref([]);
const data = reactive({
  searchForm: {
    searchText: "",
    status: true,
    customerName: "",
  },
  form: {
    salesContractNo: "",
    customerName: "",
    invoiceNo: "",
    invoiceTotal: "",
    taxRate: "",
    receiptPaymentAmount: "",
    receiptPaymentType: "",
    registrant: "",
    receiptPaymentDate: "",
  },
  rules: {
    salesContractNo: [{ required: true, message: "请选择", trigger: "change" }],
    customerName: [{ required: true, message: "请输入", trigger: "blur" }],
    invoiceNo: [{ required: true, message: "请选择", trigger: "change" }],
    invoiceTotal: [{ required: true, message: "请输入", trigger: "blur" }],
    taxRate: [{ required: true, message: "请选择", trigger: "change" }],
    receiptPaymentAmount: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    receiptPaymentType: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    registrant: [{ required: true, message: "请选择", trigger: "change" }],
    receiptPaymentDate: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    specificationModel: "",
  },
});
const { form, rules } = toRefs(data);
const { form: searchForm, resetForm } = useFormData(data.searchForm);
const { form: searchForm } = useFormData(data.searchForm);
const { receipt_payment_type } = proxy.useDict("receipt_payment_type");
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
  const val = Number(cellValue ?? 0);
  return Number.isFinite(val) ? val.toFixed(2) : "0.00";
};
const getStatusTagType = (statusName = '') => {
@@ -477,7 +404,7 @@
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceTotal", "receiptPaymentAmountTotal", "noReceiptAmount"],
    ["receiptPaymentAmountTotal", "noReceiptAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
@@ -490,97 +417,72 @@
};
// æ‰“开弹框
const openForm = () => {
  // è‡³å°‘选择一条数据
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgError("请选择数据");
    proxy.$modal.msgError("请选择至少一条数据");
    return;
  }
  // æ ¡éªŒæ˜¯å¦ä¸ºç›¸åŒé”€å”®åˆåŒå·
  const firstContractNo = selectedRows.value[0].salesContractNo;
  const isSameContract = selectedRows.value.every(
    (item) => item.salesContractNo === firstContractNo
  );
  if (!isSameContract) {
    proxy.$modal.msgError("请选择相同销售合同号的数据进行回款");
    return;
  }
  // è¿‡æ»¤å‡ºæœ‰å¾…回款金额的记录
  const validRows = selectedRows.value.filter(
    (item) => Number(item.noReceiptAmount) > 0
  );
  const validRows = selectedRows.value.filter((item) => item.noReceiptAmount !== 0);
  if (validRows.length === 0) {
    proxy.$modal.msgWarning("所选数据均无需再回款");
    proxy.$modal.msgWarning("所选记录均无需回款");
    return;
  }
  // ç›´æŽ¥ä½¿ç”¨å¤–部表格数据,为每条记录添加回款相关字段
  dialogTableData.value = validRows.map((row) => {
    return {
      ...row,
      invoiceLedgerId: row.id,
      receiptPaymentAmount: row.noReceiptAmount || "",
  forms.value = validRows.map((row) => ({
    salesContractNo: row.salesContractNo || "",
    customerName: row.customerName || "",
    productCategory: row.productCategory || "",
    specificationModel: row.specificationModel || "",
    pendingInvoiceTotal: Number(row.pendingInvoiceTotal || 0),
    taxRate: row.taxRate ?? "",
    receiptPaymentAmount: Number(row.pendingInvoiceTotal || 0),
      receiptPaymentType: "",
      receiptPaymentDate: "",
      registrant: userStore.nickName,
    };
  });
    receiptPaymentDate: "",
    invoiceLedgerId: row.id,
    salesLedgerId: row.salesLedgerId,
    salesLedgerProductId: row.id,
  }));
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  // æ ¡éªŒè¡¨æ ¼æ•°æ®
  const invalidRows = dialogTableData.value.filter((row) => {
    return (
      !row.receiptPaymentAmount ||
      row.receiptPaymentAmount <= 0 ||
      !row.receiptPaymentType ||
      !row.receiptPaymentDate
  if (forms.value.length === 0) {
    proxy.$modal.msgError("请选择回款记录");
    return;
  }
  for (let i = 0; i < forms.value.length; i++) {
    const item = forms.value[i];
    const pendingAmount = Number(item.pendingInvoiceTotal || 0);
    const currentAmount = Number(item.receiptPaymentAmount);
    if (!item.receiptPaymentAmount && item.receiptPaymentAmount !== 0) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·å¡«å†™å›žæ¬¾é‡‘额`);
      return;
    }
    if (currentAmount > pendingAmount) {
      proxy.$modal.msgError(
        `第 ${i + 1} æ¡ï¼šå›žæ¬¾é‡‘额不能超过待回款金额(待回款:${pendingAmount.toFixed(
          2
        )})`
    );
  });
  if (invalidRows.length > 0) {
    proxy.$modal.msgError("请完善所有必填项:回款金额、回款方式、回款日期");
    return;
  }
  // æ ¡éªŒå›žæ¬¾é‡‘额不能超过待回款金额
  const exceedRows = dialogTableData.value.filter((row) => {
    return Number(row.receiptPaymentAmount) > Number(row.noReceiptAmount);
  });
  if (exceedRows.length > 0) {
    proxy.$modal.msgError("回款金额不能超过待回款金额");
    if (!item.receiptPaymentType) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·é€‰æ‹©å›žæ¬¾å½¢å¼`);
    return;
  }
  // ç»„合成数组批量提交
  const submitDataList = dialogTableData.value.map((row) => {
    return {
      invoiceLedgerId: row.invoiceLedgerId,
      receiptPaymentAmount: row.receiptPaymentAmount,
      receiptPaymentType: row.receiptPaymentType,
      receiptPaymentDate: row.receiptPaymentDate,
      registrant: row.registrant,
    };
  });
  receiptPaymentSaveOrUpdate(submitDataList)
    .then(() => {
    if (!item.receiptPaymentDate) {
      proxy.$modal.msgError(`第 ${i + 1} æ¡ï¼šè¯·é€‰æ‹©å›žæ¬¾æ—¥æœŸ`);
      return;
    }
  }
  receiptPaymentSaveOrUpdate(forms.value).then(() => {
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
      getList();
    })
    .catch((error) => {
      console.error("提交失败:", error);
      proxy.$modal.msgError("提交失败,请重试");
    });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogTableData.value = [];
  forms.value = [];
  dialogFormVisible.value = false;
};
@@ -621,7 +523,6 @@
    receiptPaymentType: row.receiptPaymentType,
    receiptPaymentAmount: row.receiptPaymentAmount,
  };
  // å­è¡¨ç¼–辑保存也按数组提交(与批量新增保持一致)
  receiptPaymentSaveOrUpdate([updateData]).then((res) => {
    row.editType = !row.editType;
        getList();
@@ -666,4 +567,9 @@
  justify-content: space-between;
  margin-bottom: 10px;
}
.empty-tip {
  text-align: center;
  padding: 20px 0;
  color: #909399;
}
</style>
src/views/salesManagement/receiptPaymentHistory/index.vue
@@ -27,6 +27,13 @@
      <el-form-item>
        <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
        <el-button @click="handleExport">导出</el-button>
        <el-button
          type="danger"
          :disabled="selectedRows.length === 0"
          @click="handleBatchDelete"
        >
          æ‰¹é‡åˆ é™¤ ({{ selectedRows.length }})
        </el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
@@ -42,15 +49,27 @@
        :total="page.total"
        @pagination="pagination"
        @selection-change="handleSelectionChange"
      ></PIMTable>
      >
        <template #operation="{ row }">
          <el-button
            type="primary"
            link
            size="small"
            @click="handleDelete(row)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </PIMTable>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import { receiptPaymentHistoryListPage } from "@/api/salesManagement/receiptPayment.js";
import { ElMessageBox } from "element-plus";
import { receiptPaymentHistoryListPage, receiptPaymentDel } from "@/api/salesManagement/receiptPayment.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
@@ -84,10 +103,13 @@
    prop: "receiptPaymentType",
    dataType: "tag",
    formatData: (params) => {
      const dictItem = receipt_payment_type.value?.find(
        (item) => item.value == params
      );
      return dictItem ? dictItem.label : null;
      if (params == 0) {
        return "电汇";
      } else if (params == 1) {
        return "承兑";
      } else {
        return null;
      }
    },
    formatType: (params) => {
      return "info";
@@ -101,6 +123,14 @@
    label: "登记日期",
    prop: "createTime",
    width:100
  },
  {
    label: "操作",
    dataType: "slot",
    fixed: "right",
    slot: "operation",
    width: 100,
    align: "center",
  },
]);
const tableData = ref([]);
@@ -172,6 +202,66 @@
  getList();
};
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm("确认删除该记录吗?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(async () => {
      try {
        tableLoading.value = true;
        await receiptPaymentDel([row.id]);
        proxy.$modal.msgSuccess("删除成功");
        getList();
      } catch (error) {
        console.error("删除失败:", error);
        proxy.$modal.msgError("删除失败");
      } finally {
        tableLoading.value = false;
      }
    })
    .catch(() => {
      proxy.$modal.msg("已取消删除");
    });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.value.length} æ¡æ•°æ®å—?`,
    "删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(async () => {
      try {
        tableLoading.value = true;
        const ids = selectedRows.value.map((item) => item.id);
        await receiptPaymentDel(ids);
        proxy.$modal.msgSuccess("删除成功");
        selectedRows.value = [];
        getList();
      } catch (error) {
        console.error("删除失败:", error);
        proxy.$modal.msgError("删除失败");
      } finally {
        tableLoading.value = false;
      }
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// å¯¼å‡º
const handleExport = () => {
  const { receiptPaymentDate, ...rest } = searchForm;
src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -41,7 +41,7 @@
                        width="200"
          />
          <el-table-column
            label="开票金额(元)"
            label="合同金额(元)"
            prop="invoiceTotal"
            show-overflow-tooltip
            :formatter="formattedNumber"
@@ -93,45 +93,43 @@
          />
          <el-table-column
            label="发生日期"
            prop="happenTime"
            prop="receiptPaymentDate"
            show-overflow-tooltip
                        width="110"
          />
          <el-table-column
            label="开票金额(元)"
            prop="invoiceAmount"
            label="销售合同号"
            prop="salesContractNo"
            show-overflow-tooltip
                        width="200"
          />
          <el-table-column
            label="合同金额(元)"
            prop="invoiceTotal"
            show-overflow-tooltip
            :formatter="formattedNumber"
                        width="200"
          />
          <el-table-column
            label="回款金额(元)"
            prop="receiptAmount"
            prop="receiptPaymentAmount"
            show-overflow-tooltip
            :formatter="formattedNumber"
                        width="200"
          />
          <el-table-column
            label="应收金额(元)"
            prop="unReceiptAmount"
            prop="unReceiptPaymentAmount"
            show-overflow-tooltip
                        width="200"
          >
            <template #default="{ row, column }">
              <el-text type="danger">
                {{ formattedNumber(row, column, row.unReceiptAmount) }}
                {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }}
              </el-text>
            </template>
          </el-table-column>
        </el-table>
        <pagination
          v-show="recordTotal > 0"
          :total="recordTotal"
          layout="total, sizes, prev, pager, next, jumper"
          :page="recordPage.current"
          :limit="recordPage.size"
          @pagination="recordPaginationChange"
        />
      </div>
    </div>
  </div>
@@ -172,7 +170,6 @@
  getList();
};
const paginationChange = (obj) => {
  console.log("paginationChange", current, limit);
  page.current = obj.page;
  page.size = obj.limit;
  getList();
src/views/salesManagement/salesLedger/index.vue
@@ -46,11 +46,42 @@
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="产品状态" width="100px" align="center">
                <template #default="scope">
                  <el-tag v-if="scope.row.approveStatus === 0" type="info">未出库</el-tag>
                  <el-tag v-if="scope.row.approveStatus === 1" type="success">已出库</el-tag>
                  <el-tag v-if="scope.row.approveStatus === 2" type="warning">审核中</el-tag>
                  <el-tag v-if="scope.row.approveStatus === 3" type="success">审核成功</el-tag>
                  <el-tag v-if="scope.row.approveStatus === 4" type="danger">审核失败</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="发货车牌" minWidth="100px" align="center">
                <template #default="scope">
                  <div>
                    <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag>
                    <el-tag v-else type="info">未发货</el-tag>
                  </div>
                </template>
              </el-table-column>
              <el-table-column label="发货日期" minWidth="100px" align="center">
                <template #default="scope">
                  <div>
                    <div v-if="scope.row.shippingDate">{{ scope.row.shippingDate }}</div>
                    <el-tag v-else type="info">未发货</el-tag>
                  </div>
                </template>
              </el-table-column>
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" />
              <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" />
              <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" />
            <!--操作-->
              <el-table-column Width="60px" label="操作" align="center">
                <template #default="scope">
                  <el-button :disabled="scope.row.approveStatus!==2 || scope.row.approveStatus!==5" link type="primary" size="small" @click="openDeliveryForm(scope.row)">发货</el-button>
                </template>
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
@@ -64,20 +95,26 @@
        <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip />
        <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip />
        <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="200" align="center">
        <el-table-column fixed="right" label="操作" min-width="100" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" :disabled="scope.row.invoiceTotal>0 || scope.row.entryPersonName !== userStore.nickName" @click="openForm('edit', scope.row)">编辑</el-button>
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">编辑</el-button>
<!--            <el-button link type="primary" size="small" @click="openForm('view', scope.row)">详情</el-button>-->
            <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">附件</el-button>
            <el-button link type="primary" size="small" @click="openDeliveryForm(scope.row)">发货</el-button>
<!--            <el-button link type="primary" size="small" @click="openDeliveryForm(scope.row)">发货</el-button>-->
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
    </div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'" width="70%"
      @close="closeDia">
    <FormDialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'"
      :width="'70%'"
      :operation-type="operationType"
      @close="closeDia"
      @confirm="submitForm"
      @cancel="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
@@ -87,7 +124,9 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="业务员:" prop="salesman">
              <el-select v-model="form.salesman" placeholder="请选择" clearable :disabled="operationType === 'view'" filterable>
              <el-select v-model="form.salesman"
                                                 filterable
                         :reserve-keyword="false" placeholder="请选择" clearable :disabled="operationType === 'view'">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                  :value="item.nickName" />
              </el-select>
@@ -100,7 +139,7 @@
              <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'" filterable>
                <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
                  {{
                    item.customerName+'-'+item.type
                    item.customerName+'-'+item.customerType
                  }}
                </el-option>
              </el-select>
@@ -116,7 +155,10 @@
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="录入人:" prop="entryPerson">
                            <el-select v-model="form.entryPerson" placeholder="请选择" clearable @change="changs" disabled filterable>
                            <el-select v-model="form.entryPerson"
                                                 filterable
                         default-first-option
                         :reserve-keyword="false" placeholder="请选择" clearable @change="changs">
                                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
                            </el-select>
                        </el-form-item>
@@ -132,14 +174,6 @@
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input v-model="form.paymentMethod" placeholder="请输入" clearable :disabled="operationType === 'view'" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否开票" prop="isInvoice">
              <el-select v-model="form.isInvoice" placeholder="请选择" clearable>
                <el-option label="是" :value="1" />
                <el-option label="否" :value="2" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -198,7 +232,7 @@
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    </FormDialog>
    <el-dialog v-model="productFormVisible" :title="productOperationType === 'add' ? '新增产品' : '编辑产品'" width="40%"
      @close="closeProductDia">
      <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef">
@@ -320,12 +354,15 @@
                                        <span class="value">{{ formatDate(item.createTime) }}</span>
                                    </div>
                                    <div>
                                        <span class="label">客户名称:</span>
                                        <span class="value">{{ item.customerName || '张爱有' }}</span>
                                        <span class="label">发货车牌号:</span>
                                        <span class="value">{{ item.shippingCarNumber }}</span>
                                    </div>
                                </div>
                                <div class="info-row">
                                    <div>
                                        <span class="label">客户名称:</span>
                                        <span class="value">{{ item.customerName || '张爱有' }}</span>
                                    </div>
                                    <span class="label">单号:</span>
                                    <span class="value">{{ item.salesContractNo }}</span>
                                </div>
@@ -449,12 +486,15 @@
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import {onMounted, ref} from "vue";
import {onMounted, ref, getCurrentInstance} from "vue";
import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js";
import { ElMessageBox } from "element-plus";
import { ElMessageBox, ElMessage } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
import FileList from "./fileList.vue";
import FileListDialog from '@/components/Dialog/FileListDialog.vue';
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import {
  ledgerListPage,
  productList,
@@ -464,12 +504,13 @@
  delLedger,
  addOrUpdateSalesLedgerProduct,
  delProduct,
  delLedgerFile,
    delLedgerFile, getProductInventory,
} from "@/api/salesManagement/salesLedger.js";
import { getQuotationDetail } from "@/api/salesManagement/salesQuotation.js";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useFormData from "@/hooks/useFormData.js";
import dayjs from "dayjs";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
@@ -511,7 +552,6 @@
    productData: [],
    executionDate: "",
    paymentMethod: "",
    isInvoice:"",
  },
  rules: {
    salesman: [{ required: true, message: "请选择", trigger: "change" }],
@@ -519,7 +559,6 @@
    entryPerson: [{ required: true, message: "请选择", trigger: "change" }],
    entryDate: [{ required: true, message: "请选择", trigger: "change" }],
    executionDate: [{ required: true, message: "请选择", trigger: "change" }],
    isInvoice: [{ required: true, message: "请选择", trigger: "change"}],
    },
});
const { form, rules } = toRefs(data);
@@ -607,7 +646,11 @@
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  // åªæœ‰åœ¨ç‚¹å‡»æœç´¢æŒ‰é’®æ—¶æ‰é‡ç½®é¡µç åˆ°ç¬¬ä¸€é¡µ
  // é¿å…è¡¨å•字段change事件干扰分页
  if (arguments.length === 0) {
  page.current = 1;
  }
    expandedRowKeys.value = [];
  getList();
};
@@ -634,8 +677,12 @@
};
// èŽ·å–äº§å“å¤§ç±»tree数据
const getProductOptions = () => {
  productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
  // è¿”回 Promise,便于在编辑产品时等待加载完成
  return productTreeList().then((res) => {
    // å…¼å®¹æŽ¥å£è¿”回 { data: [] } æˆ–直接返回数组
    const list = Array.isArray(res) ? res : (res?.data ?? []);
    productOptions.value = convertIdToValue(list);
    return productOptions.value;
  });
};
const formattedNumber = (row, column, cellValue) => {
@@ -711,6 +758,19 @@
    return newItem;
  });
}
// æ ¹æ®åç§°åæŸ¥äº§å“å¤§ç±» id,便于仅存名称时的反显
function findNodeIdByLabel(nodes, label) {
  if (!label) return null;
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i];
    if (node.label === label) return node.value;
    if (node.children && node.children.length > 0) {
      const found = findNodeIdByLabel(node.children, label);
      if (found !== null && found !== undefined) return found;
    }
  }
  return null;
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
@@ -767,7 +827,12 @@
    customerOption.value = res;
  });
  form.value.entryPerson = userStore.id;
  if (type !== "add") {
  if (type === "add") {
    // æ–°å¢žæ—¶è®¾ç½®å½•入日期为当天
    form.value.entryDate = getCurrentDate();
    // ç­¾è®¢æ—¥æœŸé»˜è®¤ä¸ºå½“天
    form.value.executionDate = getCurrentDate();
  } else {
    currentId.value = row.id;
    getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
      form.value = { ...res };
@@ -857,13 +922,40 @@
const productIndex = ref(0);
// æ‰“开产品弹框
const openProductForm = (type, row,index) => {
const openProductForm = async (type, row, index) => {
  productOperationType.value = type;
  productForm.value = {};
  proxy.resetForm("productFormRef");
  // æ–°å¢žã€ç¼–辑都需先加载产品树,否则 el-tree-select æ— æ•°æ®
  try {
    await getProductOptions();
  } catch (e) {
    console.error("加载产品树失败", e);
  }
  if (type === "edit") {
    productForm.value = { ...row };
    productIndex.value = index;
    // ç¼–辑时根据产品大类名称反查 tree èŠ‚ç‚¹ id,并加载规格型号列表
    try {
      const options = productOptions.value && productOptions.value.length > 0
        ? productOptions.value
        : await getProductOptions();
      const categoryId = findNodeIdByLabel(options, productForm.value.productCategory);
      if (categoryId) {
        const models = await modelList({ id: categoryId });
        modelOptions.value = models || [];
        // æ ¹æ®å½“前规格型号名称反查并设置 productModelId,便于下拉框显示已选值
        const currentModel = (modelOptions.value || []).find(
          (m) => m.model === productForm.value.specificationModel
        );
        if (currentModel) {
          productForm.value.productModelId = currentModel.id;
        }
      }
    } catch (e) {
      // åŠ è½½å¤±è´¥æ—¶ä¿æŒå¯ç¼–è¾‘ï¼Œä¸ä¸­æ–­å¼¹çª—
      console.error("加载产品规格型号失败", e);
    }
  }
  productFormVisible.value = true;
  getProductOptions();
@@ -1307,15 +1399,6 @@
    const seconds = String(date.getSeconds()).padStart(2, "0");
    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// è®¡ç®—产品总数量
const getTotalQuantity = (products) => {
  if (!products || products.length === 0) return '0';
@@ -1381,6 +1464,7 @@
  const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
  const quantity = parseFloat(productForm.value.quantity);
  const taxRate = Number(productForm.value.taxRate) || 0;
  if (!totalPrice || !quantity || quantity <= 0) {
    return;
@@ -1392,17 +1476,10 @@
  productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
  // å¦‚果有税率,计算不含税总价
  // if (productForm.value.taxRate) {
  //   productForm.value.taxExclusiveTotalPrice =
  //     proxy.calculateTaxExclusiveTotalPrice(
  //       totalPrice,
  //       productForm.value.taxRate
  //     );
  // }
  productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
          totalPrice,
          productForm.value.taxRate
          taxRate
      );
  isCalculating.value = false;
@@ -1418,7 +1495,7 @@
  const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice);
  const quantity = parseFloat(productForm.value.quantity);
  const taxRate = productForm.value.taxRate?parseFloat(productForm.value.taxRate):0;
  const taxRate = Number(productForm.value.taxRate) || 0;
  // if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
  //   return;
@@ -1450,6 +1527,7 @@
  const quantity = parseFloat(productForm.value.quantity);
  const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
  const taxRate = Number(productForm.value.taxRate) || 0;
  if (!quantity || quantity <= 0 || !unitPrice) {
    return;
@@ -1461,17 +1539,10 @@
  productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
  // å¦‚果有税率,计算不含税总价
  // if (productForm.value.taxRate) {
  //   productForm.value.taxExclusiveTotalPrice =
  //     proxy.calculateTaxExclusiveTotalPrice(
  //       productForm.value.taxInclusiveTotalPrice,
  //       productForm.value.taxRate
  //     );
  // }
  productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
          productForm.value.taxInclusiveTotalPrice,
          productForm.value.taxRate
          taxRate
      );
  isCalculating.value = false;
@@ -1487,6 +1558,7 @@
  const quantity = parseFloat(productForm.value.quantity);
  const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
  const taxRate = Number(productForm.value.taxRate) || 0;
  if (!quantity || quantity <= 0 || !unitPrice) {
    return;
@@ -1498,17 +1570,10 @@
  productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
  // å¦‚果有税率,计算不含税总价
  // if (productForm.value.taxRate) {
  //   productForm.value.taxExclusiveTotalPrice =
  //     proxy.calculateTaxExclusiveTotalPrice(
  //       productForm.value.taxInclusiveTotalPrice,
  //       productForm.value.taxRate
  //     );
  // }
  productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
          productForm.value.taxInclusiveTotalPrice,
          productForm.value.taxRate
          taxRate
      );
  isCalculating.value = false;
@@ -1523,7 +1588,7 @@
  if (isCalculating.value) return;
  const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
  const taxRate = parseFloat(productForm.value.taxRate);
  const taxRate = Number(productForm.value.taxRate) || 0;
  // if (!inclusiveTotalPrice || !taxRate) {
  //   return;
@@ -1557,12 +1622,16 @@
// æ‰“开发货弹框
const openDeliveryForm = (row) => {
  getProductInventory({ salesLedgerId: row.id, type:1 }).then((res) => {
  currentDeliveryRow.value = row;
  deliveryForm.value = {
    shippingDate: getCurrentDate(),
    shippingCarNumber: "",
  };
  deliveryFormVisible.value = true;
  }).catch(err => {
    ElMessage.error(err.msg);
  });
};
// æäº¤å‘货表单