bbd880d66e993f80670259882f2feca1b150d1ae..7972a9b96ccec053cb34a7c31008c5c98c87ad9b
2026-01-20 spring
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
7972a9 对比 | 目录
2026-01-20 spring
fix: 质量关联指标
bd6032 对比 | 目录
2026-01-20 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
87d86e 对比 | 目录
2026-01-20 zhangwencui
报表管理页面样式
c68265 对比 | 目录
2026-01-20 huminmin
优化人员数据分析页面,对接接口
b375f0 对比 | 目录
2026-01-20 张诺
销售台账发货接口新增
f4aaa6 对比 | 目录
2026-01-20 张诺
发货审核同步
8e01ae 对比 | 目录
2026-01-20 张诺
销售台账发货同步
0f0138 对比 | 目录
2026-01-20 张诺
Merge remote-tracking branch 'origin/dev_New' into dev_New
2b8c32 对比 | 目录
2026-01-20 张诺
销售台账发货同步
bc38cc 对比 | 目录
2026-01-20 spring
fix: 原材料检、过程检、出厂检关联指标维护、指标绑定
6fdf83 对比 | 目录
2026-01-20 spring
fix: 指标维护、指标绑定样式修正
66f385 对比 | 目录
2026-01-20 zss
质量的指标新增的工序非必填
ab835f 对比 | 目录
2026-01-20 spring
fix: 合并军泰BI大屏
b84381 对比 | 目录
2026-01-19 huminmin
删除StaffJoinLeaveRecord相关代码
ccaebd 对比 | 目录
2026-01-19 huminmin
合同管理详情中上传附件
cbf68c 对比 | 目录
2026-01-19 huminmin
员工台账-续签合同
b48ddb 对比 | 目录
2026-01-19 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
a282a7 对比 | 目录
2026-01-19 huminmin
员工台账增加部门
f8daf1 对比 | 目录
2026-01-19 spring
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
3b8364 对比 | 目录
2026-01-19 spring
fix: 完成生产搬迁
485b7a 对比 | 目录
2026-01-19 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
8f7438 对比 | 目录
2026-01-19 huminmin
重构新增离职
916e11 对比 | 目录
2026-01-19 gaoluyang
进销存-升级 1.销售台账添加导入按钮
1a67b0 对比 | 目录
2026-01-19 gaoluyang
进销存-升级 1.设备保养、设备报修页面逻辑优化
6d4a0d 对比 | 目录
2026-01-17 gaoluyang
进销存-升级 1.财务报表查询接口传参修改 2.会计核算页面修改且查询接口传参修改 3.项目利润、增值税对比页面联调
eeabb2 对比 | 目录
2026-01-17 gaoluyang
Merge remote-tracking branch 'origin/dev_New' into dev_New
df503c 对比 | 目录
2026-01-17 gaoluyang
进销存-升级 1.财务报表查询接口传参修改 2.会计核算页面修改且查询接口传参修改 3.项目利润、增值税对比页面联调
331036 对比 | 目录
2026-01-17 spring
fix: 合并军泰生产模块
30dbb3 对比 | 目录
已添加18个文件
已修改50个文件
已删除11个文件
13354 ■■■■■ 文件已修改
src/api/collaborativeApproval/shipmentReview.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/accounting.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/employeeRecord.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/onboarding.js 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffAnalytics.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffLeave.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffOnJob.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/paymentLedger.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/metricMaintenance.js 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard2.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/chartCard3.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/enterpriseBook/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetApplication/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetExamine/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/meetPublish/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/notificationManagement/summary/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/sealManagement/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/shipmentReview/fileList.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/shipmentReview/index.vue 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/MaintainForm.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Form/RepairForm.vue 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/MaintainModal.vue 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/Modal/RepairModal.vue 190 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/repair/index.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanForm.vue 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/PlanModal.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Form/formDia.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/Modal/formDia.vue 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/upkeep/index.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/accounting/index.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDia.vue 400 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/index.vue 997 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/Form.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/analytics/index.vue 216 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/components/formDia.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/filesDia.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/components/formDia.vue 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/dimission/index.vue 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/RenewContract.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/index.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/components/formDia.vue 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/components/formDiaXJHT.vue 386 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/onboarding/index.vue 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/components/formDia.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/scheduling/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/selfService/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/fileList.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 3098 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/filesDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 596 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/projectProfit/index.vue 186 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 1997 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/fileList.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 592 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/shipmentReview.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
// å‘货审批
import request from "@/utils/request";
// èŽ·å–å‘è´§å®¡æ‰¹åˆ—è¡¨
export function getShipmentApprovalList(query) {
    return request({
        url: '/shipmentApproval/listPage',
        method: 'get',
        params: query,
    })
}
// å‘货申请批准
// /shipmentApproval/update
export function approveShipment(query) {
    return request({
        url: '/shipmentApproval/update',
        method: 'post',
        data: query,
    })
}
src/api/financialManagement/accounting.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// èŽ·å–å›ºå®šèµ„äº§æ±‡æ€»ä¿¡æ¯
export const getAccountingTotal = (params) => {
  return request({
    url: "/accounting/total",
    method: "get",
    params,
  });
};
// èŽ·å–è®¾å¤‡ç±»åž‹åˆ†å¸ƒæ•°æ®ï¼ˆé¥¼å›¾å’ŒæŠ˜çº¿å›¾ï¼‰
export const getDeviceTypeDistribution = (params) => {
  return request({
    url: "/accounting/deviceTypeDistribution",
    method: "get",
    params,
  });
};
// èŽ·å–æŠ˜æ—§è®¡ç®—æ•°æ®ï¼ˆè¡¨æ ¼æ•°æ®ï¼‰
export const getCalculateDepreciation = (params) => {
  return request({
    url: "/accounting/calculateDepreciation",
    method: "get",
    params,
  });
};
src/api/personnelManagement/employeeRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from '@/utils/request'
// æŸ¥è¯¢åœ¨èŒå‘˜å·¥å°è´¦
export function staffOnJobListPage(query) {
    return request({
        url: '/staff/staffOnJob/listPage',
        method: 'get',
        params: query,
    })
}
// æŸ¥è¯¢å‘˜å·¥å…¥èŒä¿¡æ¯
export function staffOnJobInfo(query) {
    return request({
        url: '/staff/staffOnJob/staffNo',
        method: 'get',
        params: query,
    })
}
// å¯¼å‡ºåˆåŒå‰¯æœ¬
export function staffOnJobExportCopy(data) {
    return request({
        url: '/staff/staffOnJob/exportCopy',
        method: 'post',
        data: data,
    })
}
src/api/personnelManagement/onboarding.js
ÎļþÒÑɾ³ý
src/api/personnelManagement/staffAnalytics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from "@/utils/request.js";
// ç¦»èŒåŽŸå› åˆ†æž
export function findStaffLeaveReasonAnalysis() {
    return request({
        url: "/staff/analytics/reason",
        method: "get"
    });
}
// 12个月员工流动流失率分析
export function findStaffAnalysisMonthlyTurnoverRateFor12Months() {
    return request({
        url: "/staff/analytics/monthly_turnover_rate",
        method: "get"
    });
}
export function findStaffAnalysisTotalStatistic() {
    return request({
        url: "/staff/analytics/total_statistic",
        method: "get"
    });
}
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
@@ -42,4 +42,13 @@
        method: "delete",
        data: query,
    });
}
}
// ç»­ç­¾åˆåŒ
export function renewContract(id, params) {
    return request({
        url: "/staff/staffOnJob/renewContract/" + id,
        method: "post",
        data: params,
    });
}
src/api/procurementManagement/paymentLedger.js
@@ -4,17 +4,16 @@
// åˆ†é¡µæŸ¥è¯¢
export function paymentLedgerList(query) {
  return request({
    url: "/purchase/paymentRegistration/supplierNameListPage",
    url: "/purchase/paymentRegistration/paymentLedgerList",
    method: "get",
    params: query,
  });
}
// åˆ†é¡µæŸ¥è¯¢
export function paymentRecordList(query) {
export function paymentRecordList(supplierId) {
  return request({
    url: "/purchase/paymentRegistration/supplierNameListPageDetails",
    url: "/purchase/paymentRegistration/getPaymentRecordList/" + supplierId,
    method: "get",
    params: query,
  });
}
src/api/qualityManagement/metricMaintenance.js
@@ -37,10 +37,11 @@
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityInspectDetailByProductId(productId) {
export function qualityInspectDetailByProductId(params) {
  return request({
    url: "/qualityTestStandard/product/" + productId,
    url: "/qualityTestStandard/getQualityTestStandardByProductId",
    method: "get",
    params: params,
  });
}
@@ -98,3 +99,12 @@
    data: ids,
  });
}
// æ ¹æ®æ ‡å‡†ID获取标准参数
export function getQualityTestStandardParamByTestStandardId(testStandardId) {
  return request({
    url: "/qualityTestStandard/getQualityTestStandardParamByTestStandardId",
    method: "get",
    params: { testStandardId },
  });
}
src/assets/images/chartCard.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#0092FF" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/chartCard2.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#5EB334" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/assets/images/chartCard3.svg
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><mask id="master_svg0_88_35670" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#FFFFFF" fill-opacity="1"/></mask><clipPath id="master_svg1_88_35666"><rect x="7" y="7" width="27" height="27" rx="0"/></clipPath><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg2_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg3_88_26531"><stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"/><stop offset="98.57142567634583%" stop-color="#F0FBFF" stop-opacity="1"/></linearGradient></defs><g mask="url(#master_svg0_88_35670)"><ellipse cx="20" cy="20" rx="20" ry="20" fill="#8000FF" fill-opacity="1"/><g clip-path="url(#master_svg1_88_35666)"><path d="M21.175671875,27.58515925L14.263672875000001,27.58515925C13.750673275,27.58515925,13.426672974999999,27.24765625,13.426672974999999,26.74815725C13.426672974999999,26.23515525,13.764173075,25.911160250000002,14.263672875000001,25.911160250000002L21.351173875,25.911160250000002C21.688676875,24.89865825,22.188173875,23.88615425,22.863174875,23.211156250000002L14.263672875000001,23.211156250000002C13.750673275,23.211156250000002,13.426672974999999,22.87365525,13.426672974999999,22.37415225C13.426672974999999,21.87465325,13.764173075,21.537155249999998,14.263672875000001,21.537155249999998L25.738675875,21.537155249999998C26.251674875,21.37515325,26.751174875,21.37515325,27.088676875,21.37515325C28.438678875,21.37515325,29.626676875,21.88815525,30.625678875,22.549656249999998L30.625678875,13.072656349999999C30.625678875,11.38515625,29.275674875,10.03515625,27.588174875,10.03515625L27.075177875,10.03515625L27.075177875,13.24815675C27.075177875,14.935656550000001,25.725173875,16.285657450000002,24.037676875000002,16.285657450000002L16.113174475,16.285657450000002C14.425674475000001,16.272157149999998,13.075673375000001,14.922158249999999,13.075673375000001,13.23465635L13.075673375000001,10.03515625L12.238672475,10.03515625C10.551171974999999,10.03515625,9.201171875,11.38515625,9.201171875,13.072656349999999L9.201171875,29.94765825C9.201171875,31.63515625,10.551171974999999,32.985161250000004,12.238672475,32.985161250000004L25.576673875,32.985161250000004C23.200674875,32.485662250000004,21.337675875000002,30.28515825,21.175671875,27.58515925Z" fill="url(#master_svg2_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/><path d="M16.1124145625,14.764538762499999L24.0504169625,14.764538762499999C24.8874170625,14.764538762499999,25.5624140625,14.0895385625,25.5624140625,13.252537762500001L25.5624140625,10.0395388625L22.5249171625,10.0395388625C22.3629159625,8.8650390625,21.3369150625,7.8525390625,19.986915562500002,7.8525390625C18.7989153625,7.8525390625,17.7864150625,8.8650390625,17.6244149625,10.0395388625L14.5869140625,10.0395388625L14.5869140625,13.252537762500001C14.5869140625,14.0895385625,15.2619143725,14.764538762499999,16.1124145625,14.764538762499999ZM30.7869150625,24.3900370625C29.9499160625,23.3775360625,28.5999220625,22.7025380625,27.2499170625,22.7025380625L26.412916062500003,22.7025380625C25.8999180625,22.7025380625,25.5759160625,22.8780390625,25.0629190625,23.2155400625C24.0504169625,23.7285370625,23.1999158625,24.7275330625,22.700415562499998,25.9155390625C22.5384173625,26.4285390625,22.5384173625,26.9280380625,22.5384173625,27.4275380625L22.5384173625,27.5895390625C22.700415562499998,30.1275410625,24.7254170625,31.9770390625,27.1014200625,31.9770390625C28.4514180625,31.9770390625,29.8014230625,31.3020400625,30.6384180625,30.2895320625C31.3134210625,29.4525340625,31.6509170625,28.4265380625,31.6509170625,27.2520330625C31.8129160625,26.2395310625,31.2999250625,25.2270370625,30.7869150625,24.3900370625ZM29.7879200625,26.5770380625L27.0879160625,29.2770390625C26.7504160625,29.6145400625,26.412916062500003,29.6145400625,26.0754160625,29.2770390625L24.387915562499998,27.5895390625C24.0504169625,27.2520370625,24.0504169625,26.9145390625,24.387915562499998,26.5770380625C24.725415062499998,26.2395380625,25.0629150625,26.2395400625,25.4004160625,26.5770380625L26.2374170625,27.4140400625L26.5749150625,27.7515370625L28.7619150625,25.5645350625C29.0994140625,25.2270370625,29.4369190625,25.2270370625,29.774416062500002,25.5645350625C30.1119160625,25.9020370625,30.1119160625,26.2395380625,29.7879200625,26.5770380625Z" fill="url(#master_svg3_88_26531)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>
src/components/PIMTable/PIMTable.vue
@@ -130,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'"
@@ -145,7 +145,7 @@
                    : o.color,
              }"
              link
              @click="o.clickFun(scope.row)"
              @click.stop="o.clickFun(scope.row)"
              :key="key"
            >
              {{ o.name }}
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/notificationManagement/meetApplication/index.vue
@@ -127,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>
@@ -156,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: é€šçŸ¥å‘布
@@ -302,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/meetExamine/index.vue
@@ -188,8 +188,8 @@
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)
@@ -240,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})`
      }
    })
@@ -342,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/meetPublish/index.vue
@@ -186,8 +186,8 @@
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)
@@ -239,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})`
      }
    })
@@ -340,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/summary/index.vue
@@ -160,8 +160,8 @@
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)
@@ -214,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})`
      }
    })
@@ -337,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/sealManagement/index.vue
@@ -261,9 +261,9 @@
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, userListNoPageByTenantId } 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 {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
// å“åº”式数据
const currentUser = ref(null)
@@ -583,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同名的人员
src/views/collaborativeApproval/shipmentReview/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <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">
        <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>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  open
})
</script>
<style></style>
src/views/collaborativeApproval/shipmentReview/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,340 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">销售合同号:</span>
        <el-input
            v-model="searchForm.salesContractNo"
            style="width: 240px"
            placeholder="请输入销售合同号搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <span class="search_title ml10">审批状态:</span>
        <el-select v-model="searchForm.approveStatus" clearable @change="handleQuery" style="width: 240px">
          <el-option label="待审核" :value="2" />
          <el-option label="审核成功" :value="3" />
          <el-option label="审核失败" :value="4" />
        </el-select>
        <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">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :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>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import FileList from "./fileList.vue";
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {ElMessageBox} from "element-plus";
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
import {getShipmentApprovalList, approveShipment} from "@/api/collaborativeApproval/shipmentReview.js";
// import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
// å®šä¹‰ç»„件接收的props
const props = defineProps({
  approveType: {
    type: [Number, String],
    default: 6
  }
});
const userList = ref([]);
const userStore = useUserStore();
const data = reactive({
  searchForm: {
    approveId: "",
    approveStatus: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  {
    label: "审批状态",
    prop: "approveStatus",
    dataType: "tag",
    width: 100,
    formatData: (params) => {
      if (params === 2) {
        return "待审核";
      } else if (params === 3) {
        return "审核完成";
      } else if (params === 4) {
        return "审核驳回";
      } else {
        return '未知状态';
      }
    },
    formatType: (params) => {
      if (params === 0) {
        return "warning";
      } else if (params === 2) {
        return "info";
      } else if (params === 3) {
        return "success";
      } else if (params === 4) {
        return "danger";
      } else {
        return 'danger';
      }
    },
  },
  {
    label: "销售合同号",
    prop: "salesContractNo",
    width: 170
  },
  {
    label: "客户名称",
    prop: "customerName",
    width: 200
  },
  {
    label: "产品大类",
    prop: "productCategory",
    width: 200
  },
  {
    label: "规格型号",
    prop: "specificationModel",
    width: 220
  },
  {
    label: "申请人",
    prop: "approveUserId",
    width: 120,
    align: "center",
    formatData:(params)=>{
      const user = userList.value.find(item => item.userId === params)
      return user ? user.nickName : '--'
    }
  },
  {
    label: "车牌号",
    prop: "shippingCarNumber",
    width: 120,
  },
  {
    label: "申请人",
    prop: "approveUserId",
    width: 120,
  },
  {
    label: "申请日期",
    prop: "executionDate",
    width: 200
  },
  {
    label: "当前审批人",
    prop: "salesman",
    width: 120
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    operation: [
      {
        name: "通过",
        type: "text",
        clickFun: (row) => {
          handleApproval("通过", row);
        },
        disabled: (row) => row.approveStatus !== 2
      },
      {
        name: "驳回",
        type: "text",
        clickFun: (row) => {
          handleApproval("驳回", row);
        },
        disabled: (row) => row.approveStatus !== 2
      },
      // {
      //   name: "编辑",
      //   type: "text",
      //   clickFun: (row) => {
      //     openForm("edit", row);
      //   },
      //   disabled: (row) => row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4
      // },
      // {
      //   name: "审核",
      //   type: "text",
      //   clickFun: (row) => {
      //     openApprovalDia("approval", row);
      //   },
      //   disabled: (row) => row.approveUserCurrentId == null || row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || row.approveUserCurrentId !== userStore.id
      // },
      // {
      //   name: "详情",
      //   type: "text",
      //   clickFun: (row) => {
      //     openApprovalDia('view', row);
      //   },
      // },
      // {
      //   name: "附件",
      //   type: "text",
      //   clickFun: (row) => {
      //     downLoadFile(row);
      //   },
      // },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const infoFormDia = ref()
const approvalDia = ref()
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const fileListRef = ref(null)
const downLoadFile = (row) => {
  fileListRef.value.open(row.commonFileList)
}
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList =async () => {
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  tableLoading.value = true;
  getShipmentApprovalList({...page, ...searchForm.value,approveType:props.approveType}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
  }).catch(err => {
    tableLoading.value = false;
  })
};
// å¯¼å‡º
const handleOut = () => {
  const type = Number(props.approveType || 6)
  const urlMap = {
    0: "/shipmentApproval/export",
  }
  const url = urlMap[type] || urlMap[0]
  const nameMap = {
    0: "发货审核表",
  }
  const fileName = nameMap[type] || nameMap[0]
  proxy.download(url, {}, `${fileName}.xlsx`)
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开新增、编辑弹框
const openForm = (type, row) => {
  nextTick(() => {
    infoFormDia.value?.openDialog(type, row)
  })
};
// æ‰“开新增检验弹框
const openApprovalDia = (type, row) => {
  nextTick(() => {
    approvalDia.value?.openDialog(type, row)
  })
};
// å®¡æ ¸é€šè¿‡/驳回
const handleApproval = (name = "审核",row) => {
  ElMessageBox.confirm(`选中的内容将被${name},是否确认${name}?`, "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async()=>{
    let res = await approveShipment({
      id: row.id,
      approveStatus: name === "通过" ? 3 : 4
    });
    if(res.code === 200){
      proxy.$modal.msgSuccess(`${name}成功`);
    }else{
      proxy.$modal.msgError(`${name}失败`);
    }
    await getList()
  }).catch(err=>{
    proxy.$modal.msgError(`未知错误,请联系管理员`);
  })
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.approveId);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        approveProcessDelete(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>
src/views/equipmentManagement/ledger/Form.vue
@@ -1,5 +1,5 @@
<template>
  <el-form :model="form" label-width="100px" :rules="formRules" ref="formRef">
  <el-form :model="form" label-width="120px" :rules="formRules" ref="formRef">
    <el-row :gutter="20">
      <el-col :span="12">
        <el-form-item label="设备名称" prop="deviceName">
@@ -14,6 +14,27 @@
      <el-col :span="12">
        <el-form-item label="设备品牌" prop="deviceBrand">
          <el-input v-model="form.deviceBrand" placeholder="请输入设备品牌" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="设备类型" prop="type">
          <el-select
            v-model="form.type"
            placeholder="请选择或输入设备类型"
            clearable
            filterable
            allow-create
            default-first-option
            style="width: 100%"
            @change="handleDeviceTypeChange"
          >
            <el-option
              v-for="item in deviceTypeOptions"
              :key="item"
              :label="item"
              :value="item"
            />
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -32,8 +53,19 @@
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="启用折旧" prop="enableDepreciation">
          <el-switch v-model="form.enableDepreciation" :active-value="true" :inactive-value="false" />
        <el-form-item label="启用折旧" prop="isDepr">
          <el-switch v-model="form.isDepr" :active-value="1" :inactive-value="2" />
        </el-form-item>
      </el-col>
      <el-col :span="12" v-if="form.isDepr === 1">
        <el-form-item label="每年折旧金额" prop="annualDepreciationAmount">
          <el-input-number
            :step="0.01"
            :min="0"
            style="width: 100%"
            v-model="form.annualDepreciationAmount"
            placeholder="请输入每年折旧金额"
          />
        </el-form-item>
      </el-col>
      <el-col :span="12">
@@ -149,24 +181,47 @@
});
const formRef = ref(null);
const operationType = ref('');
// è®¾å¤‡ç±»åž‹å›ºå®šé€‰é¡¹
const deviceTypeOptions = ref([
  '生产设备',
  '办公设备',
  '检测设备',
  '运输设备',
  '其他设备'
]);
const formRules = {
    deviceName: [{ required: true, trigger: "blur", message: "请输入" }],
    deviceModel: [{ required: true, trigger: "blur", message: "请输入" }],
    type: [{ required: true, trigger: "change", message: "请选择或输入设备类型" }],
    supplierName: [{ required: true, trigger: "blur", message: "请输入" }],
    unit: [{ required: true, trigger: "blur", message: "请输入" }],
    number: [{ required: true, trigger: "blur", message: "请输入" }],
    taxIncludingPriceUnit: [{ required: true, trigger: "blur", message: "请输入" }],
    taxRate: [{ required: true, trigger: "change", message: "请输入" }],
    planRuntimeTime: [{ required: true, trigger: "change", message: "请选择" }],
    annualDepreciationAmount: [
        {
            validator: (rule, value, callback) => {
                if (form.isDepr === 1 && (value === undefined || value === null || value === '')) {
                    callback(new Error('启用折旧时,请输入每年折旧金额'));
                } else {
                    callback();
                }
            },
            trigger: "blur"
        }
    ],
}
const { form, resetForm } = useFormData({
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  deviceBrand: undefined, // è®¾å¤‡å“ç‰Œ
  type: undefined, // è®¾å¤‡ç±»åž‹
  supplierName: undefined, // ä¾›åº”商
  storageLocation: undefined, // å­˜æ”¾ä½ç½®
  enableDepreciation: false, // æ˜¯å¦å¯ç”¨æŠ˜æ—§
  isDepr: 2, // æ˜¯å¦å¯ç”¨æŠ˜æ—§ 1-是 2-否
  annualDepreciationAmount: undefined, // æ¯å¹´æŠ˜æ—§é‡‘额
  unit: undefined, // å•位
  number: 1, // æ•°é‡
  taxIncludingPriceUnit: undefined, // å«ç¨Žå•ä»·
@@ -187,9 +242,11 @@
    form.deviceName = data.deviceName;
    form.deviceModel = data.deviceModel;
    form.deviceBrand = data.deviceBrand;
    form.type = data.type;
    form.supplierName = data.supplierName;
    form.storageLocation = data.storageLocation;
    form.enableDepreciation = data.enableDepreciation;
    form.isDepr = data.isDepr;
    form.annualDepreciationAmount = data.annualDepreciationAmount;
    form.unit = data.unit;
    form.number = 1;
    form.taxIncludingPriceUnit = data.taxIncludingPriceUnit;
@@ -200,6 +257,13 @@
  }
};
const handleDeviceTypeChange = (value) => {
  // å¦‚果输入的新值不在固定选项中,则添加到选项列表
  if (value && !deviceTypeOptions.value.includes(value)) {
    deviceTypeOptions.value.push(value);
  }
};
const mathNum = () => {
  if (!form.taxIncludingPriceUnit) {
    ElMessage.error("请输入单价");
src/views/equipmentManagement/ledger/index.vue
@@ -7,7 +7,6 @@
          style="width: 240px"
          placeholder="请输入设备名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
@@ -17,7 +16,6 @@
            style="width: 240px"
            placeholder="请输入规格型号"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
@@ -27,17 +25,6 @@
            style="width: 240px"
            placeholder="请输入供应商"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="单位">
        <el-input
            v-model="filters.unit"
            style="width: 240px"
            placeholder="请输入单位"
            clearable
            :prefix-icon="Search"
            @change="getTableData"
        />
      </el-form-item>
@@ -130,81 +117,53 @@
    deviceName: undefined,
    deviceModel: undefined,
    supplierName: undefined,
    unit: undefined,
    entryDateStart: undefined,
    entryDateEnd: undefined,
  },
  [
    {
      label: "设备名称",
      align: "center",
      prop: "deviceName",
    },
    {
      label: "规格型号",
      align: "center",
      prop: "deviceModel",
    },
    {
      label: "设备品牌",
      align: "center",
      prop: "deviceBrand",
    },
    {
      label: "设备类型",
      prop: "type",
    },
    {
      label: "供应商",
      align: "center",
      prop: "supplierName",
    },
    {
      label: "单位",
      align: "center",
      prop: "unit",
    },
    {
      label: "存放位置",
      align: "center",
      prop: "storageLocation",
    },
    {
      label: "数量",
      align: "center",
      prop: "number",
    },
    {
      label: "含税单价",
      align: "center",
      prop: "taxIncludingPriceUnit",
    },
    {
      label: "含税总价",
      align: "center",
      prop: "taxIncludingPriceTotal",
    },
    {
      label: "税率",
      align: "center",
      prop: "taxRate",
    },
    {
      label: "不含税总价",
      align: "center",
      prop: "unTaxIncludingPriceTotal",
    },
    {
      label: "启用折旧",
      align: "center",
      prop: "enableDepreciation",
      formatData: (v) => (v ? "是" : "否"),
    },
    {
      label: "录入人",
      align: "center",
      prop: "createUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "createTime",
      formatData: (v) => {
        if (!v) return '';
        // å¦‚果包含时分秒,只取日期部分
        if (v.includes(' ')) {
          return v.split(' ')[0];
        }
        return v;
      },
    },
        {
            dataType: "action",
@@ -215,14 +174,12 @@
            operation: [
                {
                    name: "编辑",
                    type: "text",
                    clickFun: (row) => {
                        edit(row.id)
                    },
                },
                {
                    name: "生成二维码",
                    type: "text",
                    clickFun: (row) => {
                        showQRCode(row)
                    },
src/views/equipmentManagement/repair/Form/MaintainForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Form/RepairForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/repair/Modal/MaintainModal.vue
@@ -1,53 +1,108 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" direction="ltr" draggable>
    <MaintainForm ref="maintainFormRef" />
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="'设备维修'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="80px">
      <el-form-item label="维修人">
        <el-input v-model="form.maintenanceName" placeholder="请输入维修人" />
      </el-form-item>
      <el-form-item label="维修结果">
        <el-input v-model="form.maintenanceResult" placeholder="请输入维修结果" />
      </el-form-item>
      <el-form-item label="维修状态">
        <el-select v-model="form.status">
          <el-option label="待报修" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="维修日期">
        <el-date-picker
          v-model="form.maintenanceTime"
          placeholder="请选择维修日期"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetime"
          clearable
          style="width: 100%"
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import MaintainForm from "../Form/MaintainForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintain } from "@/api/equipmentManagement/repair";
import useFormData from "@/hooks/useFormData";
import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
import { ElMessage } from "element-plus";
defineOptions({
  name: "维修模态框",
});
const maintainFormRef = ref();
const emits = defineEmits(["ok"]);
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备维修" });
// ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
const repairId = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const { form, resetForm } = useFormData({
  maintenanceName: undefined, // ç»´ä¿®åç§°
  maintenanceResult: undefined, // ç»´ä¿®ç»“æžœ
  maintenanceTime: undefined, // ç»´ä¿®æ—¥æœŸ
  status: 0,
});
const setForm = (data) => {
  form.maintenanceName = data.maintenanceName ?? userStore.nickName;
  form.maintenanceResult = data.maintenanceResult;
  form.maintenanceTime =
    data.maintenanceTime
      ? dayjs(data.maintenanceTime).format("YYYY-MM-DD HH:mm:ss")
      : dayjs().format("YYYY-MM-DD HH:mm:ss");
  form.status = 1; // é»˜è®¤çŠ¶æ€ä¸ºå®Œç»“
};
const sendForm = async () => {
  loading.value = true;
  const form = await maintainFormRef.value.getForm();
  const { code } = await addMaintain({ id: id.value, ...form });
  if (code == 200) {
    emits("ok");
    maintainFormRef.value.resetForm();
    closeModal();
  try {
    const { code } = await addMaintain({ id: repairId.value, ...form });
    if (code == 200) {
      ElMessage.success("维修成功");
      emits("ok");
      resetForm();
      visible.value = false;
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const open = async (id, row) => {
  openModal(id);
  repairId.value = id; // ä¿å­˜æŠ¥ä¿®è®°å½•çš„id
  visible.value = true;
  await nextTick();
  maintainFormRef.value.setForm(row);
  setForm(row);
};
defineExpose({
src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -1,24 +1,93 @@
<template>
  <el-dialog v-model="visible" :title="modalOptions.title" @close="close" draggable>
    <RepairForm ref="repairFormRef" :id="id" />
    <template #footer>
            <el-button type="primary" @click="sendForm" :loading="loading">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备报修' : '新增设备报修'"
    width="800px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-row>
        <el-col :span="12">
          <el-form-item label="设备名称">
            <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
              <el-option
                v-for="(item, index) in deviceOptions"
                :key="index"
                :label="item.deviceName"
                :value="item.id"
              ></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号">
            <el-input
              v-model="form.deviceModel"
              placeholder="请输入规格型号"
              disabled
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修日期">
            <el-date-picker
              v-model="form.repairTime"
              placeholder="请选择报修日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              type="date"
              clearable
              style="width: 100%"
            />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="报修人">
            <el-input v-model="form.repairName" placeholder="请输入报修人" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row v-if="id">
        <el-col :span="12">
          <el-form-item label="报修状态">
            <el-select v-model="form.status">
              <el-option label="待维修" :value="0"></el-option>
              <el-option label="完结" :value="1"></el-option>
              <el-option label="失败" :value="2"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="故障现象">
            <el-input
              v-model="form.remark"
              :rows="2"
              type="textarea"
              placeholder="请输入故障现象"
            />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import RepairForm from "../Form/RepairForm.vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addRepair,
  editRepair,
  getRepairById,
} from "@/api/equipmentManagement/repair";
import { ElMessage } from "element-plus";
import dayjs from "dayjs";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import useUserStore from "@/store/modules/user";
defineOptions({
  name: "设备报修弹窗",
@@ -26,48 +95,83 @@
const emits = defineEmits(["ok"]);
const repairFormRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "设备报修" });
const id = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const deviceOptions = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  repairTime: dayjs().format("YYYY-MM-DD"), // æŠ¥ä¿®æ—¥æœŸï¼Œé»˜è®¤å½“天
  repairName: userStore.nickName, // æŠ¥ä¿®äºº
  remark: undefined, // æ•…障现象
  status: 0, // æŠ¥ä¿®çŠ¶æ€
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.repairTime = data.repairTime;
  form.repairName = data.repairName;
  form.remark = data.remark;
  form.status = data.status;
};
const sendForm = async () => {
  loading.value = true;
  const form = await repairFormRef.value.getForm();
  const { code } = id.value
    ? await editRepair({ id: unref(id), ...form })
    : await addRepair(form);
  if (code == 200) {
    ElMessage.success(`${id ? "编辑" : "新增"}报修成功`);
    closeModal();
    emits("ok");
  try {
    const { code } = id.value
      ? await editRepair({ id: unref(id), ...form })
      : await addRepair(form);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}报修成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
  loading.value = false;
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const openAdd = async () => {
  openModal();
  id.value = undefined;
  visible.value = true;
  await nextTick();
  await repairFormRef.value.loadDeviceName();
  await loadDeviceName();
};
const openEdit = async (id) => {
  const { data } = await getRepairById(id);
  openModal(id);
const openEdit = async (editId) => {
  const { data } = await getRepairById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  await repairFormRef.value.loadDeviceName();
  await repairFormRef.value.setForm(data);
};
const close = () => {
  repairFormRef.value.resetForm();
  closeModal();
  await loadDeviceName();
  setForm(data);
};
defineExpose({
@@ -75,3 +179,5 @@
  openEdit,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/repair/index.vue
@@ -68,14 +68,6 @@
      <div class="actions">
        <el-text class="mx-1" size="large">设备报修</el-text>
        <div>
          <el-button
            type="primary"
            icon="Plus"
            :disabled="multipleList.length !== 1"
            @click="addMaintain"
          >
            æ–°å¢žç»´ä¿®
          </el-button>
          <el-button type="success" icon="Van" @click="addRepair">
            æ–°å¢žæŠ¥ä¿®
          </el-button>
@@ -85,7 +77,7 @@
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            :disabled="multipleList.length <= 0 || hasFinishedStatus"
            @click="delRepairByIds(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
@@ -113,16 +105,24 @@
        <template #operation="{ row }">
          <el-button
            type="primary"
            text
            icon="editPen"
            link
            :disabled="row.status === 1"
            @click="editRepair(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="success"
            link
            :disabled="row.status === 1"
            @click="addMaintain(row)"
          >
            ç»´ä¿®
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
@@ -138,7 +138,7 @@
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
import { onMounted, getCurrentInstance } from "vue";
import { onMounted, getCurrentInstance, computed } from "vue";
import RepairModal from "./Modal/RepairModal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
@@ -258,6 +258,11 @@
  multipleList.value = selectionList;
};
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
// æ–°å¢žæŠ¥ä¿®
const addRepair = () => {
  repairModalRef.value.openAdd();
@@ -269,8 +274,7 @@
};
// æ–°å¢žç»´ä¿®
const addMaintain = () => {
  const row = multipleList.value[0];
const addMaintain = (row) => {
  maintainModalRef.value.open(row.id, row);
};
@@ -282,6 +286,18 @@
// å•行删除
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const idsArray = Array.isArray(ids) ? ids : [ids];
  const hasFinished = idsArray.some(id => {
    const record = dataList.value.find(item => item.id === id);
    return record && record.status === 1;
  });
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录');
    return;
  }
  ElMessageBox.confirm("确认删除报修数据, æ­¤æ“ä½œä¸å¯é€†?", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
src/views/equipmentManagement/upkeep/Form/MaintenanceForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Form/MaintenanceModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<template>
  <FormDialog
    v-model="visible"
    :title="'设备保养'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="实际保养人">
        <el-input
          v-model="form.maintenanceActuallyName"
          placeholder="请输入实际保养人"
        ></el-input>
      </el-form-item>
      <el-form-item label="实际保养日期">
        <el-date-picker
          v-model="form.maintenanceActuallyTime"
          placeholder="请选择实际保养日期"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetime"
          clearable
          style="width: 100%"
        />
      </el-form-item>
      <el-form-item label="保养状态">
        <el-select v-model="form.status">
          <el-option label="待保养" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="保养结果">
        <el-input
          v-model="form.maintenanceResult"
          placeholder="请输入保养结果"
          type="text" />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { addMaintenance } from "@/api/equipmentManagement/upkeep";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
import useUserStore from "@/store/modules/user";
import { ElMessage } from "element-plus";
defineOptions({
  name: "保养模态框",
});
const emits = defineEmits(["ok"]);
// ä¿å­˜è®¡åˆ’保养记录的id
const planId = ref();
const visible = ref(false);
const loading = ref(false);
const userStore = useUserStore();
const { form, resetForm } = useFormData({
  maintenanceActuallyName: undefined, // å®žé™…保养人
  maintenanceActuallyTime: undefined, // å®žé™…保养日期
  maintenanceResult: undefined, // ä¿å…»ç»“æžœ
  status: 0, // ä¿å…»çŠ¶æ€
});
const setForm = (data) => {
  form.maintenanceActuallyName =
    data.maintenanceActuallyName ?? userStore.nickName;
  form.maintenanceActuallyTime =
    data.maintenanceActuallyTime
      ? dayjs(data.maintenanceActuallyTime).format("YYYY-MM-DD HH:mm:ss")
      : dayjs().format("YYYY-MM-DD HH:mm:ss");
  form.maintenanceResult = data.maintenanceResult;
  form.status = 1; // é»˜è®¤çŠ¶æ€ä¸ºå®Œç»“
};
/**
 * @desc ä¿å­˜ä¿å…»
 */
const sendForm = async () => {
  loading.value = true;
  try {
    const { code } = await addMaintenance({ id: planId.value, ...form });
    if (code == 200) {
      ElMessage.success("保养成功");
      emits("ok");
      resetForm();
      visible.value = false;
    }
  } finally {
    loading.value = false;
  }
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const open = async (id, row) => {
  planId.value = id; // ä¿å­˜è®¡åˆ’保养记录的id
  visible.value = true;
  await nextTick();
  setForm(row);
};
defineExpose({
  open,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/PlanForm.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Form/PlanModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,188 @@
<template>
  <FormDialog
    v-model="visible"
    :title="id ? '编辑设备保养计划' : '新增设备保养计划'"
    width="500px"
    @confirm="sendForm"
    @cancel="handleCancel"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="设备名称">
        <el-select
          v-model="form.deviceLedgerId"
          @change="setDeviceModel"
          placeholder="请选择设备"
          filterable
          default-first-option
          :reserve-keyword="false"
        >
          <el-option
            v-for="(item, index) in deviceOptions"
            :key="index"
            :label="item.deviceName"
            :value="item.id"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="规格型号">
        <el-input
          v-model="form.deviceModel"
          placeholder="请输入规格型号"
          disabled
        />
      </el-form-item>
      <el-form-item label="录入人">
        <el-select
          v-model="form.createUser"
          placeholder="请选择"
          filterable
          default-first-option
          :reserve-keyword="false"
          clearable
        >
          <el-option
            v-for="item in userList"
            :key="item.userId"
            :label="item.nickName"
            :value="item.userId"
          />
        </el-select>
      </el-form-item>
      <el-form-item v-if="id" label="保修状态">
        <el-select v-model="form.status">
          <el-option label="待保修" :value="0"></el-option>
          <el-option label="完结" :value="1"></el-option>
          <el-option label="失败" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="计划保养日期">
        <el-date-picker
          style="width: 100%"
          v-model="form.maintenancePlanTime"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="date"
          placeholder="请选择计划保养日期日期"
          clearable
        />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addUpkeep,
  editUpkeep,
  getUpkeepById,
} from "@/api/equipmentManagement/upkeep";
import { ElMessage } from "element-plus";
import useFormData from "@/hooks/useFormData";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { onMounted } from "vue";
import dayjs from "dayjs";
import { userListNoPage } from "@/api/system/user.js";
defineOptions({
  name: "设备保养新增计划",
});
const emits = defineEmits(["ok"]);
const id = ref();
const visible = ref(false);
const loading = ref(false);
const deviceOptions = ref([]);
const loadDeviceName = async () => {
  const { data } = await getDeviceLedger();
  deviceOptions.value = data;
};
const { form, resetForm } = useFormData({
  deviceLedgerId: undefined, // è®¾å¤‡Id
  deviceName: undefined, // è®¾å¤‡åç§°
  deviceModel: undefined, // è§„格型号
  maintenancePlanTime: undefined, // è®¡åˆ’保养日期
  createUser: undefined, // å½•入人
  status: 0, //保修状态
});
const setDeviceModel = (deviceId) => {
  const option = deviceOptions.value.find((item) => item.id === deviceId);
  form.deviceModel = option.deviceModel;
};
/**
 * @desc è®¾ç½®è¡¨å•内容
 * @param data è®¾å¤‡ä¿¡æ¯
 */
const setForm = (data) => {
  form.deviceLedgerId = data.deviceLedgerId;
  form.deviceName = data.deviceName;
  form.deviceModel = data.deviceModel;
  form.createUser = Number(data.createUser);
  form.status = data.status;
  form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
    "YYYY-MM-DD HH:mm:ss"
  );
};
// ç”¨æˆ·åˆ—表
const userList = ref([]);
onMounted(() => {
  loadDeviceName();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
});
const openEdit = async (editId) => {
  const { data } = await getUpkeepById(editId);
  id.value = editId;
  visible.value = true;
  await nextTick();
  setForm(data);
};
const sendForm = async () => {
  loading.value = true;
  try {
    const { code } = id.value
      ? await editUpkeep({ id: unref(id), ...form })
      : await addUpkeep(form);
    if (code == 200) {
      ElMessage.success(`${id.value ? "编辑" : "新增"}计划成功`);
      visible.value = false;
      emits("ok");
    }
  } finally {
    loading.value = false;
  }
};
const handleCancel = () => {
  resetForm();
  visible.value = false;
};
const handleClose = () => {
  resetForm();
  visible.value = false;
};
const openModal = () => {
  id.value = undefined;
  visible.value = true;
};
defineExpose({
  openModal,
  openEdit,
});
</script>
<style lang="scss" scoped></style>
src/views/equipmentManagement/upkeep/Form/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,304 @@
<template>
    <FormDialog
        v-model="dialogVisitable"
        :title="operationType === 'add' ? '新增保养任务' : '编辑保养任务'"
        width="800px"
        :operation-type="operationType"
        @confirm="submitForm"
        @cancel="cancel"
        @close="cancel"
    >
        <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
            <el-row>
                <el-col :span="12">
                    <el-form-item label="设备名称" prop="taskId">
                        <el-select v-model="form.taskId" @change="setDeviceModel" filterable>
                            <el-option
                                v-for="(item, index) in deviceOptions"
                                :key="index"
                                :label="item.deviceName"
                                :value="item.id"
                            ></el-option>
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="规格型号">
                        <el-input
                            v-model="form.deviceModel"
                            placeholder="请输入规格型号"
                            disabled
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="录入人" prop="inspector">
                        <el-select
                            v-model="form.inspector"
                            filterable
                            default-first-option
                            :reserve-keyword="false"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in userList"
                                :label="item.nickName"
                                :value="item.userId"
                                :key="item.userId"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="登记时间" prop="registrationDate">
                        <el-date-picker
                            v-model="form.registrationDate"
                            type="date"
                            placeholder="选择登记日期"
                            format="YYYY-MM-DD"
                            value-format="YYYY-MM-DD"
                            style="width: 100%"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="任务频率" prop="frequencyType">
                        <el-select v-model="form.frequencyType" placeholder="请选择" clearable>
                            <el-option label="每日" value="DAILY"/>
                            <el-option label="每周" value="WEEKLY"/>
                            <el-option label="每月" value="MONTHLY"/>
                            <el-option label="季度" value="QUARTERLY"/>
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm" />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-select v-model="form.week" placeholder="请选择" clearable style="width: 50%">
                            <el-option label="周一" value="MON"/>
                            <el-option label="周二" value="TUE"/>
                            <el-option label="周三" value="WED"/>
                            <el-option label="周四" value="THU"/>
                            <el-option label="周五" value="FRI"/>
                            <el-option label="周六" value="SAT"/>
                            <el-option label="周日" value="SUN"/>
                        </el-select>
                        <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm"
                                                        value-format="HH:mm"  style="width: 50%"/>
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-date-picker
                            v-model="form.frequencyDetail"
                            type="datetime"
                            clearable
                            placeholder="选择开始日期"
                            format="DD,HH:mm"
                            value-format="DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
                    <el-form-item label="日期" prop="frequencyDetail">
                        <el-date-picker
                            v-model="form.frequencyDetail"
                            type="datetime"
                            clearable
                            placeholder="选择开始日期"
                            format="MM,DD,HH:mm"
                            value-format="MM,DD,HH:mm"
                        />
                    </el-form-item>
                </el-col>
            </el-row>
            <el-row>
                <el-col :span="12">
                    <el-form-item label="备注" prop="remarks">
                        <el-input v-model="form.remarks" placeholder="请输入备注" type="textarea" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
    </FormDialog>
</template>
<script setup>
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { reactive, ref, getCurrentInstance, toRefs } from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { deviceMaintenanceTaskAdd, deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
import { getCurrentDate } from "@/utils/index.js";
import useUserStore from "@/store/modules/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const dialogVisitable = ref(false);
const operationType = ref('add');
const deviceOptions = ref([]);
const userStore = useUserStore();
const data = reactive({
    form: {
        taskId: undefined,
        taskName: undefined,
        // å½•入人:单选一个用户 id
        inspector: undefined,
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        deviceModel: undefined, // è§„格型号
        registrationDate: ''
    },
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请选择录入人", trigger: "blur" },],
        registrationDate: [{ required: true, message: "请选择登记时间", trigger: "change" }]
    }
})
const { form, rules } = toRefs(data)
const userList = ref([])
const loadDeviceName = async () => {
    const { data } = await getDeviceLedger();
    deviceOptions.value = data;
};
// é€‰æ‹©è®¾å¤‡æ—¶ï¼Œå›žå¡«è®¾å¤‡åç§°(taskName)和规格型号(deviceModel)
const setDeviceModel = (id) => {
    const option = deviceOptions.value.find((item) => item.id === id);
    if (option) {
        form.value.taskId = option.id;
        form.value.taskName = option.deviceName;
        form.value.deviceModel = option.deviceModel;
    }
}
// æ‰“开弹框
const openDialog = async (type, row) => {
    dialogVisitable.value = true
    operationType.value = type
    // é‡ç½®è¡¨å•
    resetForm();
    // åŠ è½½ç”¨æˆ·åˆ—è¡¨
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    // åŠ è½½è®¾å¤‡åˆ—è¡¨
    await loadDeviceName();
    if (type === 'edit' && row) {
        form.value = { ...row }
        // ç¼–辑时用接口返回的 registrantId å›žæ˜¾å½•入人
        if (row.registrantId) {
            form.value.inspector = row.registrantId
        }
        // å¦‚果有设备ID,自动设置设备信息
        if (form.value.taskId) {
            setDeviceModel(form.value.taskId);
        }
    } else if (type === 'add') {
        // æ–°å¢žæ—¶è®¾ç½®ç™»è®°æ—¥æœŸä¸ºå½“天
        form.value.registrationDate = getCurrentDate();
        // æ–°å¢žæ—¶è®¾ç½®å½•入人为当前登录账户
        form.value.inspector = userStore.id;
    }
}
// å…³é—­å¯¹è¯æ¡†
const cancel = () => {
    resetForm()
    dialogVisitable.value = false
    emit('closeDia')
}
// é‡ç½®è¡¨å•函数
const resetForm = () => {
    if (proxy.$refs.formRef) {
        proxy.$refs.formRef.resetFields()
    }
    // é‡ç½®è¡¨å•数据确保设备信息正确重置
    form.value = {
        taskId: undefined,
        taskName: undefined,
        inspector: undefined,
        inspector: undefined,
        remarks: '',
        frequencyType: '',
        frequencyDetail: '',
        week: '',
        time: '',
        deviceModel: undefined,
        registrationDate: ''
    }
}
// æäº¤è¡¨å•
const submitForm = () => {
    proxy.$refs["formRef"].validate(async valid => {
        if (valid) {
            try {
                const payload = { ...form.value }
                // ä¸å†å‘后端传保养人字段,仅使用接口要求的 registrant / registrantId
                // æ ¹æ®é€‰æ‹©çš„"录入人"设置 registrant / registrantId
                if (payload.inspector) {
                    const selectedUser = userList.value.find(
                        (u) => String(u.userId) === String(payload.inspector)
                    )
                    if (selectedUser) {
                        payload.registrantId = selectedUser.userId
                        payload.registrant = selectedUser.nickName
                    }
                }
                delete payload.inspector
                delete payload.inspectorIds
                if (payload.frequencyType === 'WEEKLY') {
                    let frequencyDetail = ''
                    frequencyDetail = payload.week + ',' + payload.time
                    payload.frequencyDetail = frequencyDetail
                }
                // å½•入日期:直接使用表单里的 registrationDate å­—段
                // ä¸€äº›é»˜è®¤çŠ¶æ€å­—æ®µ
                if (payload.status === undefined || payload.status === null || payload.status === '') {
                    payload.status = '0' // é»˜è®¤çŠ¶æ€ï¼Œå¯æŒ‰å®žé™…æžšä¸¾è°ƒæ•´
                }
                payload.active = true
                payload.deleted = 0
                if (operationType.value === 'edit') {
                    await deviceMaintenanceTaskEdit(payload)
                } else {
                    await deviceMaintenanceTaskAdd(payload)
                }
                cancel()
                proxy.$modal.msgSuccess('提交成功')
            } catch (error) {
                proxy.$modal.msgError('提交失败,请重试')
            }
        }
    })
}
defineExpose({ openDialog })
</script>
<style scoped>
</style>
src/views/equipmentManagement/upkeep/Modal/MaintenanceModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/PlanModal.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/Modal/formDia.vue
ÎļþÒÑɾ³ý
src/views/equipmentManagement/upkeep/index.vue
@@ -64,16 +64,14 @@
            <template #operation="{ row }">
              <el-button
                type="primary"
                text
                icon="editPen"
                link
                @click="editScheduledTask(row)"
              >
                ç¼–辑
              </el-button>
              <el-button
                type="danger"
                text
                icon="delete"
                link
                @click="delScheduledTaskByIds(row.id)"
              >
                åˆ é™¤
@@ -135,14 +133,6 @@
          <div class="actions">
            <el-text class="mx-1" size="large">任务记录</el-text>
            <div>
              <el-button
                type="primary"
                icon="Plus"
                :disabled="multipleList.length !== 1"
                @click="addMaintain"
              >
                æ–°å¢žä¿å…»
              </el-button>
              <el-button type="success" icon="Van" @click="addPlan">
                æ–°å¢žè®¡åˆ’
              </el-button>
@@ -152,7 +142,7 @@
              <el-button
                type="danger"
                icon="Delete"
                :disabled="multipleList.length <= 0"
                :disabled="multipleList.length <= 0 || hasFinishedStatus"
                @click="delRepairByIds(multipleList.map((item) => item.id))"
              >
                æ‰¹é‡åˆ é™¤
@@ -183,16 +173,24 @@
        <template #operation="{ row }">
          <el-button
            type="primary"
            text
            icon="editPen"
            link
            :disabled="row.status === 1"
            @click="editPlan(row.id)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="success"
            link
            :disabled="row.status === 1"
            @click="addMaintain(row)"
          >
            ä¿å…»
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            link
            :disabled="row.status === 1"
            @click="delRepairByIds(row.id)"
          >
            åˆ é™¤
@@ -209,12 +207,12 @@
</template>
<script setup>
import { ref, onMounted, reactive, getCurrentInstance, nextTick } from 'vue'
import { ref, onMounted, reactive, getCurrentInstance, nextTick, computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import PlanModal from './Modal/PlanModal.vue'
import MaintenanceModal from './Modal/MaintenanceModal.vue'
import FormDia from './Modal/formDia.vue'
import PlanModal from './Form/PlanModal.vue'
import MaintenanceModal from './Form/MaintenanceModal.vue'
import FormDia from './Form/formDia.vue'
import {
  getUpkeepPage,
  delUpkeep,
@@ -494,14 +492,18 @@
  multipleList.value = selection
}
// æ£€æŸ¥é€‰ä¸­çš„记录中是否有完结状态的
const hasFinishedStatus = computed(() => {
  return multipleList.value.some(item => item.status === 1)
})
const changePage = (page) => {
  pagination.value.currentPage = page.page
  pagination.value.pageSize = page.limit
  getTableData()
}
const addMaintain = () => {
  const row = multipleList.value[0]
const addMaintain = (row) => {
  maintainModalRef.value.open(row.id, row)
}
@@ -514,6 +516,13 @@
}
const delRepairByIds = async (ids) => {
  // æ£€æŸ¥æ˜¯å¦æœ‰å®Œç»“状态的记录
  const hasFinished = multipleList.value.some(item => item.status === 1)
  if (hasFinished) {
    ElMessage.warning('不能删除状态为完结的记录')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?', '警告', {
      confirmButtonText: '确定',
src/views/financialManagement/accounting/index.vue
@@ -2,14 +2,31 @@
  <div style="padding: 20px;">
    <!-- é¡µé¢æ ‡é¢˜å’Œç­›é€‰æ¡ä»¶ -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-button
        type="primary"
        icon="Refresh"
        @click="resetFilters"
        size="default"
      >
        æŸ¥è¯¢
      </el-button>
      <el-form :inline="true">
        <el-form-item label="年份">
          <el-date-picker
            v-model="selectedYear"
            type="year"
            placeholder="请选择年份"
            format="YYYY"
            value-format="YYYY"
            clearable
            @change="fetchData()"
            style="width: 200px"
            :disabled-date="(date) => date.getFullYear() > new Date().getFullYear()"
          />
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            icon="Refresh"
            @click="resetFilters"
            size="default"
          >
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </div>
    <main class="container mx-auto px-4 pb-10">
@@ -27,7 +44,7 @@
        <el-card class="bg3">
          <p>资产原值</p>
          <h3>
            Â¥{{ assetInfo.totalOriginalValue }}
            Â¥{{ formatCurrency(assetInfo.totalOriginalValue) }}
          </h3>
        </el-card>
@@ -35,7 +52,7 @@
        <el-card class="bg4">
          <p>累计折旧</p>
          <h3>
            Â¥{{ assetInfo.totalDepreciation }}
            Â¥{{ formatCurrency(assetInfo.totalDepreciation) }}
          </h3>
        </el-card>
@@ -43,7 +60,21 @@
        <el-card class="bg5">
          <p>净值</p>
          <h3>
            Â¥{{ assetInfo.totalNetValue }}
            Â¥{{ formatCurrency(assetInfo.totalNetValue) }}
          </h3>
        </el-card>
        <!-- è´Ÿå€º -->
        <el-card class="bg2">
          <p>负债</p>
          <h3>
            Â¥{{ formatCurrency(assetInfo.debt) }}
          </h3>
        </el-card>
        <!-- åº“存资产 -->
        <el-card class="bg3">
          <p>库存资产</p>
          <h3>
            Â¥{{ formatCurrency(assetInfo.inventoryValue) }}
          </h3>
        </el-card>
      </div>
@@ -62,7 +93,7 @@
                style="height: 260px; width: 35%;">
              <div class="chart-num">
                <span style="font-size: 22px;">设备类型</span>
                <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span>
                <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ deviceTypeTotalCount }}</span>
              </div>
            </Echarts>
            <Echarts
@@ -86,7 +117,6 @@
          style="width: 100%"
          :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
        >
          <el-table-column prop="id" label="资产编号" width="120" />
          <el-table-column prop="deviceName" label="设备名称" width="250" />
          <el-table-column prop="deviceModel" label="型号规格" min-width="150" />
          <el-table-column prop="supplierName" label="供应商" min-width="120" />
@@ -94,27 +124,17 @@
          <el-table-column prop="number" label="数量" width="120" />
          <el-table-column prop="originalValue" label="原值(元)" width="120">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.taxIncludingPriceTotal) }}
              {{ formatCurrency(row.taxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="depreciation" label="累计折旧(元)" width="140">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }}
              {{ formatCurrency(row.deprAmount) }}
            </template>
          </el-table-column>
          <el-table-column prop="netValue" label="净值(元)" width="120">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.unTaxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag
                :type="getStatusTagType(row.status)"
                size="small"
              >
                {{ row.status }}
              </el-tag>
              {{ formatCurrency(row.netValue) }}
            </template>
          </el-table-column>
        </el-table>
@@ -142,20 +162,27 @@
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { getLedgerPage } from "@/api/equipmentManagement/ledger";
import { getAccountingTotal, getDeviceTypeDistribution, getCalculateDepreciation } from "@/api/financialManagement/accounting";
import dayjs from "dayjs";
// ç­›é€‰æ¡ä»¶
const dateRange = ref(null);
const equipmentType = ref('');
const selectedYear = ref(dayjs().format('YYYY')); // é»˜è®¤å½“前年份
// å›ºå®šèµ„产信息
const assetInfo = ref({
  totalEquipment: 0,
  totalOriginalValue: 0,
  totalDepreciation: 0,
  totalNetValue: 0
  totalEquipment: 0, // deviceTotal
  totalOriginalValue: 0, // deviceAmount
  totalDepreciation: 0, // deprAmount
  totalNetValue: 0, // netValue
  debt: 0, // è´Ÿå€º
  inventoryValue: 0 // åº“存资产
});
// è®¾å¤‡ç±»åž‹æ€»æ•°ï¼ˆç”¨äºŽå›¾è¡¨æ˜¾ç¤ºï¼‰
const deviceTypeTotalCount = ref(0);
// è®¾å¤‡åˆ—表
const equipmentList = ref([]);
@@ -306,63 +333,96 @@
const fetchData = async () => {
  try {
    // èŽ·å–å›ºå®šèµ„äº§æ±‡æ€»ä¿¡æ¯
    const assetInfoRes = await getAssetInfo({
    const assetInfoRes = await getAccountingTotal({
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
      equipmentType: equipmentType.value,
      year: selectedYear.value
    });
    if (assetInfoRes.code === 200) {
      assetInfo.value = assetInfoRes.data;
      // æ˜ å°„后端字段到前端字段
      const data = assetInfoRes.data;
      assetInfo.value = {
        totalEquipment: data.deviceTotal || 0, // è®¾å¤‡æ€»æ•°
        totalOriginalValue: data.deviceAmount || 0, // èµ„产原值
        totalDepreciation: data.deprAmount || 0, // ç´¯è®¡æŠ˜æ—§
        totalNetValue: data.netValue || 0, // å‡€å€¼
        debt: data.debt || 0, // è´Ÿå€º
        inventoryValue: data.inventoryValue || 0 // åº“存资产
      };
    }
    // èŽ·å–è®¾å¤‡åˆ—è¡¨
    const equipmentListRes = await getLedgerPage({
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
    // èŽ·å–è®¾å¤‡ç±»åž‹åˆ†å¸ƒæ•°æ®ï¼ˆé¥¼å›¾å’ŒæŠ˜çº¿å›¾ï¼‰
    const distributionRes = await getDeviceTypeDistribution({
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
      equipmentType: equipmentType.value,
      year: selectedYear.value
    });
    if (equipmentListRes.code === 200) {
      equipmentList.value = equipmentListRes.data.records;
      pagination.value.total = equipmentListRes.data.total;
      // æ ¹æ® equipmentList æŒ‰ deviceName è¿›è¡Œåˆ†ç±»ç»Ÿè®¡
      const deviceNameMap = {};
      equipmentList.value.forEach(item => {
        const deviceName = item.deviceName;
        if (!deviceNameMap[deviceName]) {
          deviceNameMap[deviceName] = {
            name: deviceName,
            count: 0,
            totalValue: 0
          };
        }
        deviceNameMap[deviceName].count += item.number || 1; // å‡è®¾ number ä¸ºè®¾å¤‡æ•°é‡
        deviceNameMap[deviceName].totalValue += item.taxIncludingPriceTotal || 0; // ç´¯åŠ å«ç¨Žæ€»ä»·
      });
      // è½¬æ¢ä¸º typeDistributionData æ ¼å¼
      typeDistributionData.value = Object.values(deviceNameMap).map(item => ({
        name: item.name,
        value: item.count,
        count: item.count,
        amount: `Â¥${formatCurrency(item.totalValue)}`
      }));
    if (distributionRes.code === 200) {
      const data = distributionRes.data;
      // æ›´æ–°è®¾å¤‡ç±»åž‹æ€»æ•°
      deviceTypeTotalCount.value = data.totalCount || 0;
      // è½¬æ¢é¥¼å›¾æ•°æ®æ ¼å¼
      if (data.details && data.details.length > 0) {
        typeDistributionData.value = data.details.map(item => ({
          name: item.type || '',
          value: Number(item.count || 0),
          count: Number(item.count || 0),
          amount: `Â¥${formatCurrency(item.amount || 0)}`
        }));
      } else if (data.categories && data.categories.length > 0) {
        // å¦‚果没有 details,使用 categories、countData å’Œ amountData æž„建
        typeDistributionData.value = data.categories.map((category, index) => ({
          name: category,
          value: Number(data.countData[index] || 0),
          count: Number(data.countData[index] || 0),
          amount: `Â¥${formatCurrency(data.amountData[index] || 0)}`
        }));
      } else {
        typeDistributionData.value = [];
      }
      // æ›´æ–°x轴数据
      xAxis.value[0].data = typeDistributionData.value.map(item => item.name);
      xAxis.value[0].data = data.categories || typeDistributionData.value.map(item => item.name);
      // æž„建折线图数据
      typeDistributionLineSeries.value = [
        {
          name: '设备数量',
          type: 'line',
          data: typeDistributionData.value.map(item => item.count)
          data: data.countData || typeDistributionData.value.map(item => item.count)
        }
      ];
    }
    // èŽ·å–è®¾å¤‡åˆ—è¡¨ï¼ˆæŠ˜æ—§è®¡ç®—æ•°æ®ï¼‰
    const equipmentListRes = await getCalculateDepreciation({
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value,
      year: selectedYear.value
    });
    if (equipmentListRes.code === 200) {
      // å¦‚果返回的是分页数据
      if (equipmentListRes.data.records) {
        equipmentList.value = equipmentListRes.data.records;
        pagination.value.total = equipmentListRes.data.total;
      } else if (Array.isArray(equipmentListRes.data)) {
        // å¦‚果返回的是数组
        equipmentList.value = equipmentListRes.data;
        pagination.value.total = equipmentListRes.data.length;
      } else {
        equipmentList.value = [];
        pagination.value.total = 0;
      }
    }
  } catch (error) {
    console.error('获取固定资产数据失败:', error);
@@ -401,6 +461,7 @@
const resetFilters = () => {
  dateRange.value = null;
  equipmentType.value = '';
  selectedYear.value = dayjs().format('YYYY'); // é‡ç½®ä¸ºå½“前年份
  fetchData();
};
@@ -486,10 +547,10 @@
  }
}
/* å¤§å±å¹•及以上 (lg:grid-cols-5) */
/* å¤§å±å¹•及以上 (lg:grid-cols-6) */
@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}
src/views/inventoryManagement/receiptManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,400 @@
<template>
  <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="70%"
    @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-form-item label="采购订单号" prop="purchaseContractNumber">
        <el-select
          v-model="form.purchaseContractNumber"
          placeholder="请选择采购订单号"
          clearable
          filterable
          :loading="loadingPurchaseOptions"
          @change="handlePurchaseChange"
          :disabled="operationType === 'edit'"
          style="width: 100%"
        >
          <el-option
            v-for="item in purchaseOptions"
            :key="item.purchaseContractNumber"
            :label="formatPurchaseOption(item)"
            :value="item.purchaseContractNumber"
          />
        </el-select>
      </el-form-item>
      <el-table
        :data="productList"
        border
        v-loading="loadingProducts"
        @selection-change="handleSelectionChange"
      >
        <el-table-column align="center" type="selection" width="55" />
        <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" width="70" />
        <!-- <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" @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="总价(元)"
                    :formatter="formattedNumber"
          prop="taxInclusiveTotalPrice"
          width="150"
        >
        </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>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
  updateStockIn,
  addSutockIn,
  selectProductRecordListByPuechaserId
} from "@/api/inventoryManagement/stockIn.js";
import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close', 'success'])
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const selectedRows = ref([]) // äº§å“è¡¨æ ¼é€‰ä¸­è¡Œ
const purchaseOptions = ref([])
const loadingPurchaseOptions = ref(false)
const loading = ref(false);
const data = reactive({
  form: {
    id: null,
    purchaseContractNumber: '', // é‡‡è´­è®¢å•号
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    inboundTime: '',        // å…¥åº“æ—¶é—´
    inboundBatch: '',       // å…¥åº“批次
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
    supplierId: [{ required: true, message: "请选择供应商", trigger: "change" }],
    inboundTime: [{ required: true, message: "请选择入库时间", trigger: "change" }],
    inboundBatch: [{ required: true, message: "请输入入库批次", trigger: "blur" }]
  }
})
const { form, rules } = toRefs(data)
// åŠ¨æ€è®¡ç®—å¯¹è¯æ¡†æ ‡é¢˜
const getDialogTitle = () => {
  return operationType.value === 'add' ? '新增入库' : '编辑入库'
}
const formatPurchaseOption = (item = {}) => {
  const contract = item.purchaseContractNumber || '--';
  const supplier = item.supplierName ? ` Â· ${item.supplierName}` : '';
  return `${contract}${supplier}`;
};
const loadPurchaseOptions = async (keyword = '') => {
  try {
    loadingPurchaseOptions.value = true;
    const res = await purchaseListPage({
      current: -1,
      size: -1,
      purchaseContractNumber: keyword,
    });
    const records = res.data?.records || [];
    purchaseOptions.value = records;
    if (
      form.value.purchaseContractNumber &&
      !purchaseOptions.value.find(
        (item) => item.purchaseContractNumber === form.value.purchaseContractNumber
      )
    ) {
      purchaseOptions.value.push({
        purchaseContractNumber: form.value.purchaseContractNumber,
        supplierName: form.value.supplierName,
        supplierId: form.value.supplierId,
      });
    }
  } finally {
    loadingPurchaseOptions.value = false;
  }
};
const handlePurchaseChange = (value) => {
  form.value.purchaseContractNumber = value || '';
  const matched = purchaseOptions.value.find(
    (item) => item.purchaseContractNumber === value
  );
  if (matched) {
    form.value.supplierName = matched.supplierName || form.value.supplierName;
    form.value.supplierId = matched.supplierId || form.value.supplierId;
  }
  if (!value) {
    productList.value = [];
    return;
  }
  fetchProductsByContract();
};
const exceedsAddLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting)) {
    return false;
  }
  return stock > waiting;
};
const exceedsEditLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  const original = Number(product?.originalQuantityStock ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting) || !Number.isFinite(original)) {
    return false;
  }
  return stock > waiting + original;
};
const formattedNumber = (row, column, cellValue) => {
  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('请选择合同号')
    return
  }
  try {
    loadingProducts.value = true
    const productRes = await selectProductRecordListByPuechaserId({
      purchaseContractNumber: form.value.purchaseContractNumber
    });
    if (!productRes.data || productRes.data.length === 0) {
      proxy.$modal.msgWarning('该合同下没有产品记录')
      productList.value = [];
      return
    }
    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) {
    console.error('查询产品记录失败:', error)
    proxy.$modal.msgError('查询产品记录失败')
    productList.value = [];
  } finally {
    loadingProducts.value = false
  }
}
const updatePro = async () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning('请先选择产品');
    return;
  }
  const target = selectedRows.value[0];
  const stock = Number(target?.quantityStock ?? 0);
  if (!Number.isFinite(stock) || stock <= 0) {
    proxy.$modal.msgWarning('请填写有效的入库数量');
    return;
  }
  if (exceedsEditLimit(target)) {
    proxy.$modal.msgError('本次入库数量不能超过原入库数量与待入库数量之和');
    return;
  }
  const stockInData = {
    id: selectedRows.value[0].recordId,
    quantityStock: Number(selectedRows.value[0].quantityStock),
  };
  await updateStockIn(stockInData)
  proxy.$modal.msgSuccess('修改入库成功')
  closeDia()
  emit('success')
}
const submitForm = async () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning('请先选择采购合同并选择产品')
    return
  }
  if(operationType.value !== 'add'){
    await updatePro()
    return
  }
  try {
    await proxy.$refs.formRef.validate()
    const invalidProducts = selectedRows.value.filter((product) => {
        const stock = Number(product?.quantityStock ?? 0);
        if (!Number.isFinite(stock) || stock <= 0) {
          return true;
        }
        return exceedsAddLimit(product);
    })
    if (invalidProducts.length > 0) {
      proxy.$modal.msgError('本次入库数量需大于0,且不能超过待入库数量')
      return
    }
    const stockInData = {
      ...form.value,
      inboundTime: formatDateTime(form.value.inboundTime),
      nickName: userStore.nickName,
      details: selectedRows.value.map(product => ({
        id: product.id,
        inboundQuantity: Number(product.quantityStock),
                unitPrice: Number(product.taxInclusiveUnitPrice),
        taxRate: Number(product.taxRate),
                taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice)
      })),
    };
    loading.value = true
    await addSutockIn(stockInData)
    proxy.$modal.msgSuccess('新增入库成功')
    closeDia()
    emit('success')
  } catch (error) {
    console.error('提交失败:', error)
    if (!error.errors) {
      proxy.$modal.msgError('操作失败,请重试')
    }
  } finally {
    loading.value = false
  }
}
const closeDia = () => {
  proxy.$refs.formRef.resetFields()
  dialogFormVisible.value = false
  emit('close')
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection.filter(item => item.id);
}
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`;
  }
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
function getCurrentDate() {
  return formatDateTime(new Date(), false);
}
const openDialog = async (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  selectedRows.value = []
  await loadPurchaseOptions();
  if (type === 'add') {
    form.value = {
      id: null,
      purchaseContractNumber: '',
      supplierId: null,
      supplierName: '',
      inboundTime: '',
      inboundBatch: '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: ''
    }
    productList.value = []
  } else {
    form.value = JSON.parse(JSON.stringify(row))
    try {
      loadingProducts.value = true
      const res = await selectProductRecordListByPuechaserId({
        purchaseContractNumber: form.value.purchaseContractNumber,
        id: row.id
      });
      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
    } catch (error) {
      console.error('加载产品失败:', error)
      proxy.$modal.msgError('加载产品失败')
      productList.value = []
    } finally {
      loadingProducts.value = false
    }
  }
}
defineExpose({
  openDialog,
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
<template>
  <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增自定义入库' : '编辑自定义入库'" width="70%"
    @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <div style="margin-bottom: 10px;" v-if="operationType === 'add'">
        <el-button type="primary" @click="addProductRow">新增</el-button>
      </div>
      <el-table
        :data="productList"
        border
        v-loading="loadingProducts"
      >
        <el-table-column
          align="center"
          label="序号"
          type="index"
          width="60"
        />
        <el-table-column label="产品大类" prop="productCategory" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.productCategory" placeholder="请输入产品大类" />
          </template>
        </el-table-column>
        <el-table-column label="规格型号" prop="specificationModel" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.specificationModel" placeholder="请输入规格型号" />
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="unit" width="100">
          <template #default="scope">
            <el-input v-model="scope.row.unit" placeholder="请输入单位" />
          </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)" />
          </template>
        </el-table-column>
        <el-table-column label="入库日期" prop="inboundDate" width="180">
          <template #default="scope">
            <el-date-picker
              v-model="scope.row.inboundDate"
              type="date"
              placeholder="请选择入库日期"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="单价(元)" prop="unitPrice" width="150">
          <template #default="scope">
            <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="totalPrice"
           width="150"
         >
        </el-table-column>
        <el-table-column label="操作" width="80" v-if="operationType === 'add'">
          <template #default="scope">
            <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">删除</el-button>
          </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>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
    addStockInCustom, updateProduct
} from "@/api/inventoryManagement/stockIn.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close', 'success'])
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const loading = ref(false);
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`;
  }
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
function getCurrentDate() {
  return formatDateTime(new Date(), false);
}
const itemTypeOptions = [
  { label: '物料', value: '物料' },
  { label: '原料', value: '原料' },
  { label: '成品', value: '成品' },
  { label: '其他', value: '其他' },
]
const taxRateOptions = [
  { label: '1', value: 1 },
  { label: '6', value: 6 },
  { label: '13', value: 13 },
]
const data = reactive({
  form: {
    id: null,
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }]
  }
})
const { form, rules } = toRefs(data)
// æ–°å¢žäº§å“è¡Œ
const addProductRow = () => {
  productList.value.push({
    id: null,
    productCategory: '',
    specificationModel: '',
    unit: '',
    supplierName: form.value.supplierName || '',
    itemType: '',
    inboundNum: 0,
    inboundDate: '',
    quantityStock: 0,
    unitPrice: 0,
    totalPrice: 0,
    taxRate: null,
    taxExclusiveTotalPrice: 0,
  });
};
// åˆ é™¤äº§å“è¡Œ
const removeProductRow = (index) => {
  productList.value.splice(index, 1);
};
// è®¡ç®—总价(根据数量、单价和含税单价)
const calculateTotalPrice = (row) => {
  // è®¡ç®—普通总价:inboundNum * unitPrice
  const quantity = Number(row.inboundNum || 0);
  const unitPrice = Number(row.unitPrice || 0);
  row.totalPrice = quantity * unitPrice;
  calculateExclusivePrice(row);
};
// è®¡ç®—不含税总价(根据含税总价和税率)
const calculateExclusivePrice = (row) => {
  const totalPrice = Number(row.totalPrice || 0);
  const taxRate = Number(row.taxRate || 0);
  row.taxExclusiveTotalPrice = totalPrice / (1 + taxRate / 100);
};
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    if (!productList.value.length) {
      proxy.$modal.msgError('请至少添加一条产品数据')
      return
    }
    // éªŒè¯è‡ªå®šä¹‰æ·»åŠ çš„æ•°æ®å¿…å¡«å­—æ®µ
    for (let i = 0; i < productList.value.length; i++) {
      const product = productList.value[i];
      if (!product.productCategory || !product.specificationModel || !product.unit) {
        proxy.$modal.msgError(`第${i + 1}行产品数据未填写完整(产品大类、规格型号、单位为必填)`)
        return
      }
      if (!product.inboundDate) {
        proxy.$modal.msgError(`第${i + 1}行请选择入库日期`)
        return
      }
      const stock = Number(product?.inboundNum ?? 0);
      if (!Number.isFinite(stock) || stock <= 0) {
        proxy.$modal.msgError(`第${i + 1}行本次入库数量需大于0`)
        return
      }
    }
    const payloadList = productList.value.map(product => ({
      id: product.id ?? null,
            inboundNum: Number(product.inboundNum),
      productCategory: product.productCategory,
      specificationModel: product.specificationModel,
      unit: product.unit,
      supplierName: product.supplierName || form.value.supplierName,
      itemType: product.itemType,
      inboundDate: formatDateTime(product.inboundDate, false),
      taxRate: Number(product.taxRate || 0),
      taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
            unitPrice: Number(product.unitPrice || 0),
    }));
    loading.value = true
    if (operationType.value === 'edit') {
      const editPayload = payloadList[0]
      await updateProduct(editPayload)
    } else {
      await addStockInCustom(payloadList)
    }
    proxy.$modal.msgSuccess(operationType.value === 'edit' ? '编辑自定义入库成功' : '新增自定义入库成功')
    closeDia()
    emit('success')
  } catch (error) {
    console.error('提交失败:', error)
    if (!error.errors) {
      proxy.$modal.msgError('操作失败,请重试')
    }
  } finally {
    loading.value = false
  }
}
const closeDia = () => {
  proxy.$refs.formRef.resetFields()
  dialogFormVisible.value = false
  productList.value = []
  emit('close')
}
const openDialog = async (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  if (type === 'add') {
    form.value = {
      id: null,
      supplierId: null,
      supplierName: '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: ''
    }
    productList.value = []
  } else {
    // ç¼–辑模式:将行数据填充到表格中以支持修改
    form.value = {
      id: row?.id ?? null,
      supplierId: row?.supplierId ?? null,
      supplierName: row?.supplierName ?? '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: row?.remark ?? ''
    }
    productList.value = [{
      id: row?.id ?? null,
      productCategory: row?.productCategory ?? '',
      specificationModel: row?.specificationModel ?? '',
      unit: row?.unit ?? '',
      supplierName: row?.supplierName ?? '',
      itemType: row?.itemType ?? '',
      inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
      inboundDate: row?.inboundDate ?? row?.createTime ?? '',
      taxRate: Number(row?.taxRate ?? 0),
      unitPrice: Number(row?.unitPrice ?? 0),
      taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
    }]
  }
}
defineExpose({
  openDialog,
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/index.vue
@@ -1,551 +1,538 @@
<template>
  <div class="app-container">
    <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"
                    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>
      </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">
      <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">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', 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">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-form-item label="采购订单号" prop="purchaseContractNumber">
              <el-select
                  v-model="form.purchaseContractNumber"
                  placeholder="请选择采购订单号"
                  clearable
                  filterable
                  remote
                  :remote-method="loadPurchaseOptions"
                  :loading="loadingPurchaseOptions"
                  @change="handlePurchaseChange"
                  :disabled="operationType === 'edit'"
                  style="width: 100%"
              >
                <el-option
                    v-for="item in purchaseOptions"
                    :key="item.purchaseContractNumber"
                    :label="formatPurchaseOption(item)"
                    :value="item.purchaseContractNumber"
                />
              </el-select>
            </el-form-item>
        <el-table
          :data="productList"
          border
          v-loading="loadingProducts"
          @selection-change="handleSelectionChange"
        >
          <el-table-column align="center" type="selection" width="55" />
          <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" width="70" />
          <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" />
            </template>
          </el-table-column>
          <el-table-column label="税率(%)" prop="taxRate" width="120" />
          <el-table-column
            label="含税单价(元)"
            prop="taxInclusiveUnitPrice"
            :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>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
    <el-tabs v-model="activeTab"
             @tab-change="handleTabChange">
      <el-tab-pane label="成品入库"
                   name="production">
        <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 @click="handleOut">导出</el-button>
            <el-button type="danger"
                       plain
                       @click="handleDelete">删除
            </el-button>
          </div>
        </div>
      </template>
    </el-dialog>
        <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"
                             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, '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"/>
        </div>
      </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 } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
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 dayjs from "dayjs";
import {
  getStockInPage,
  updateStockIn,
  addSutockIn,
  getStockInPageByProduction,
  getStockInPageByProductProduction,
  delStockIn,
  selectProductRecordListByPuechaserId
} from "@/api/inventoryManagement/stockIn.js";
import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js";
import { getCurrentDate } from "@/utils/index.js";
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 userList = ref([])
const {proxy} = getCurrentInstance();
const purchaseOptions = ref([])
const loadingPurchaseOptions = ref(false)
const loading = ref(false);
const tableLoading = ref(false)
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 operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const productSelectedRows = ref([]) // äº§å“è¡¨æ ¼é€‰ä¸­è¡Œ
const data = reactive({
  searchForm: {
    supplierName: '',
        timeStr: '',
    supplierName: "",
    customerName: "",
    productCategory: "",
    timeStr: "",
  },
  form: {
    id: null,
    purchaseContractNumber: '', // é‡‡è´­è®¢å•号
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    inboundTime: '',        // å…¥åº“æ—¶é—´
    inboundBatch: '',       // å…¥åº“批次
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
    supplierId: [{ required: true, message: "请选择供应商", trigger: "change" }],
    inboundTime: [{ required: true, message: "请选择入库时间", trigger: "change" }],
    inboundBatch: [{ required: true, message: "请输入入库批次", trigger: "blur" }]
  }
})
const { searchForm, form, rules } = toRefs(data)
const formatPurchaseOption = (item = {}) => {
  const contract = item.purchaseContractNumber || '--';
  const supplier = item.supplierName ? ` Â· ${item.supplierName}` : '';
  return `${contract}${supplier}`;
};
const loadPurchaseOptions = async (keyword = '') => {
  try {
    loadingPurchaseOptions.value = true;
    const res = await purchaseListPage({
      current: -1,
      size: -1,
      purchaseContractNumber: keyword,
    });
    const records = res.data?.records || [];
    purchaseOptions.value = records;
    if (
      form.value.purchaseContractNumber &&
      !purchaseOptions.value.find(
        (item) => item.purchaseContractNumber === form.value.purchaseContractNumber
      )
    ) {
      purchaseOptions.value.push({
        purchaseContractNumber: form.value.purchaseContractNumber,
        supplierName: form.value.supplierName,
        supplierId: form.value.supplierId,
      });
    }
  } finally {
    loadingPurchaseOptions.value = false;
  }
};
const handlePurchaseChange = (value) => {
  form.value.purchaseContractNumber = value || '';
  const matched = purchaseOptions.value.find(
    (item) => item.purchaseContractNumber === value
  );
  if (matched) {
    form.value.supplierName = matched.supplierName || form.value.supplierName;
    form.value.supplierId = matched.supplierId || form.value.supplierId;
  }
  if (!value) {
    productList.value = [];
    return;
  }
  fetchProductsByContract();
};
const exceedsAddLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting)) {
    return false;
  }
  return stock > waiting;
};
const exceedsEditLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  const original = Number(product?.originalQuantityStock ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting) || !Number.isFinite(original)) {
    return false;
  }
  return stock > waiting + original;
};
const formattedNumber = (row, column, cellValue) => {
    return parseFloat(cellValue).toFixed(2);
};
});
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
  getStockInPage({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    console.log('tableData:', tableData.value)
  }).catch(() => {
    tableLoading.value = false
  })
}
  tableLoading.value = true;
  const params = {...page};
// è°ƒç”¨selectProductRecordListByPuechaserId这个方法根据合同查询到id,再调用getProductRecordByhetong这个方法根据id查询到产品订单记录
// æ–°å¢žæ ¹æ®åˆåŒå·æŸ¥è¯¢äº§å“è®°å½•的方法
const fetchProductsByContract = async () =>
{
  if (!form.value.purchaseContractNumber) {
    proxy.$modal.msgWarning('请选择合同号')
    return
  // æ ¹æ®ä¸åŒçš„ 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;
  }
  try {
    loadingProducts.value = true
    // æ ¹æ®åˆåŒæŸ¥è¯¢äº§å“è®°å½•
    const productRes = await selectProductRecordListByPuechaserId({
      purchaseContractNumber: form.value.purchaseContractNumber
    });
    console.log('productRes:', productRes)
    if (!productRes.data || productRes.data.length === 0) {
      proxy.$modal.msgWarning('该合同下没有产品记录')
      productList.value = [];
      return
    }
    // å¤„理产品数据,添加本次入库数量字段
    productList.value = productRes.data.map(item => ({
      ...item,
      quantityStock: 0,
      originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? 0),
    }))
  } catch (error) {
    console.error('查询产品记录失败:', error)
    proxy.$modal.msgError('查询产品记录失败')
    productList.value = [];
  } finally {
    loadingProducts.value = false
  }
}
  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);
            }
            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) => {
    operationType.value = type
    dialogFormVisible.value = true
    selectedRows.value = []
        await loadPurchaseOptions();
    if (type === 'add') {
      // æ–°å¢žæ—¶åˆå§‹åŒ–表单
      form.value = {
        id: null,
        purchaseContractNumber: '',
        supplierId: null,
        supplierName: '',
        inboundTime: '',
        inboundBatch: '',
        recorderId: userStore.userId,
        recorderName: userStore.name,
        entryDate: getCurrentDate(),
        remark: ''
      }
      productList.value = [] // æ¸…空产品列表
const openForm = async (type, row, tabType) => {
  const currentTab = tabType || activeTab.value;
  await nextTick(() => {
    if (currentTab === "production") {
      formDiaProduct.value?.openDialog(type, row);
    } else {
      form.value = JSON.parse(JSON.stringify(row))
      try {
        loadingProducts.value = true
        // æ ¹æ®åˆåŒå·åŠ è½½å¯¹åº”çš„äº§å“åˆ—è¡¨ï¼ˆå‡è®¾ getProductByContract æ˜¯å¯ç”¨æŽ¥å£ï¼‰
        const res = await selectProductRecordListByPuechaserId({
          purchaseContractNumber: form.value.purchaseContractNumber,
          id: row.id
        });
                productList.value = res.data.map(item => ({
                    ...item,
                    quantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
                    originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
                }))
        selectedRows.value = productList.value
      } catch (error) {
        console.error('加载产品失败:', error)
        proxy.$modal.msgError('加载产品失败')
        productList.value = []
      } finally {
        loadingProducts.value = false
      }
      formDia.value?.openDialog(type, row);
    }
  }
  });
};
  const updatePro = async () => {
     // å‡†å¤‡æäº¤æ•°æ®
     // å‡†å¤‡æäº¤æ•°æ® - ä¿®æ”¹ä¸ºåŽç«¯éœ€è¦çš„æ ¼å¼
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning('请先选择产品');
      return;
    }
    const target = selectedRows.value[0];
    const stock = Number(target?.quantityStock ?? 0);
    if (!Number.isFinite(stock) || stock <= 0) {
      proxy.$modal.msgWarning('请填写有效的入库数量');
      return;
    }
    if (exceedsEditLimit(target)) {
      proxy.$modal.msgError('本次入库数量不能超过原入库数量与待入库数量之和');
      return;
    }
    const stockInData = {
      id: selectedRows.value[0].recordId,
      quantityStock: Number(selectedRows.value[0].quantityStock),// ä½¿ç”¨æ–°æ ¼å¼åŒ–函数
    };
    await updateStockIn(stockInData)
    proxy.$modal.msgSuccess('修改入库成功')
    closeDia()
    getList() // åˆ·æ–°åˆ—表
  }
// æäº¤è¡¨å•
  const submitForm = async () => {
    // éªŒè¯è‡³å°‘选择了一个产品
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning('请先选择采购合同并选择产品')
      return
    }
    if(operationType.value !== 'add'){
      await updatePro()
      return
    }
    try {
      await proxy.$refs.formRef.validate()
      // éªŒè¯å…¥åº“数量
      const invalidProducts = selectedRows.value.filter((product) => {
          const stock = Number(product?.quantityStock ?? 0);
          if (!Number.isFinite(stock) || stock <= 0) {
            return true;
          }
          return exceedsAddLimit(product);
      })
      if (invalidProducts.length > 0) {
        proxy.$modal.msgError('本次入库数量需大于0,且不能超过待入库数量')
        return
      }
      // å‡†å¤‡æäº¤æ•°æ® - ä¿®æ”¹ä¸ºåŽç«¯éœ€è¦çš„æ ¼å¼
      const stockInData = {
        // å…¥åº“单基本信息
        ...form.value,
        inboundTime: formatDateTime(form.value.inboundTime),
        nickName: userStore.nickName,
        details: selectedRows.value.map(product => ({
          id: product.id,
          // id: product.salesLedgerProductId,
          inboundQuantity: Number(product.quantityStock)
        })),
      };
      // è°ƒç”¨API
      loading.value = true
      await addSutockIn(stockInData)
      proxy.$modal.msgSuccess('新增入库成功')
      closeDia()
      getList() // åˆ·æ–°åˆ—表
    } catch (error) {
      console.error('提交失败:', error)
      if (!error.errors) {
        proxy.$modal.msgError('操作失败,请重试')
      }
    } finally {
      loading.value = false
    }
  }
// å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.$refs.formRef.resetFields()
    dialogFormVisible.value = false
  }
// è¡¨æ ¼é€‰æ‹©æ•°æ®
  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("已取消")
    })
  }
// åˆ é™¤
  const handleDelete = () => {
    let ids = []
    if (selectedRows.value.length > 0) {
            // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
            const unauthorizedData = selectedRows.value.filter(item => item.createUser !== userStore.id);
            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(() => {
      delStockIn({ids:ids}).then(res => {
        proxy.$modal.msgSuccess("删除成功")
        getList()
      })
    }).catch(() => {
      proxy.$modal.msg("已取消")
    })
  }
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
// ä¿®æ”¹ä¸ºæ›´é€šç”¨çš„æ—¥æœŸæ—¶é—´æ ¼å¼åŒ–函数
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`; // ä¿æŒåŽŸæœ‰ getCurrentDate åŠŸèƒ½
  }
  // æ–°å¢žæ—¶é—´éƒ¨åˆ†æ ¼å¼åŒ–
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
  onMounted(() => {
    getList()
const handleOut = () => {
  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;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .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();
});
</script>
<style scoped lang="scss"></style>
src/views/lavorissue/ledger/Form.vue
@@ -73,12 +73,12 @@
import useFormData from "@/hooks/useFormData";
import {ref,onMounted} from "vue";
import useUserStore from "@/store/modules/user";
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import {deepCopySameProperties} from '@/utils/util'
const userStore = useUserStore();
import {
  getDept
} from "@/api/collaborativeApproval/approvalProcess.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance();
@@ -112,8 +112,12 @@
  issueDate: undefined,
});
const getPersonList = () => {
  getStaffOnJob().then(res => {
    personList.value = res.data
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    personList.value = res.data.records
  })
};
const loadForm = (data) => {
src/views/personnelManagement/analytics/index.vue
@@ -1,5 +1,5 @@
<template>
  <div class="app-container analytics-container">
  <div class="app-container analytics-container" v-loading="loading">
    <!-- å…³é”®æŒ‡æ ‡å¡ç‰‡ -->
    <el-row :gutter="20" class="metrics-cards">
@@ -11,7 +11,7 @@
                <component :is="item.icon" />
              </el-icon>
            </div>
              <div class="card-info">
            <div class="card-info">
              <div class="card-number">
                <el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" />
                <span v-else>{{ item.value }}{{ item.unit }}</span>
@@ -64,21 +64,6 @@
    <!-- ç¬¬äºŒè¡Œå›¾è¡¨ -->
    <el-row :gutter="20" class="charts-section">
      <!-- ç¼–制达成率 -->
      <el-col :span="12">
        <el-card class="chart-card">
          <template #header>
            <div class="card-header">
              <span>编制达成率</span>
              <el-tag type="warning">各部门对比</el-tag>
            </div>
          </template>
          <div class="chart-container">
            <div ref="staffingChartRef" class="chart"></div>
          </div>
        </el-card>
      </el-col>
      <!-- å‘˜å·¥æµå¤±åŽŸå› åˆ†æž -->
      <el-col :span="12">
        <el-card class="chart-card">
@@ -98,19 +83,15 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import {
  Refresh,
  User,
  TrendCharts,
  DataAnalysis,
  PieChart,
  ArrowUp,
  ArrowDown
} from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { staffOnJobListPage } from '@/api/personnelManagement/staffOnJob.js'
import {listDept} from "@/api/system/dept.js";
import {
  findStaffAnalysisMonthlyTurnoverRateFor12Months,
  findStaffLeaveReasonAnalysis,
  findStaffAnalysisTotalStatistic
} from "@/api/personnelManagement/staffAnalytics.js";
// å“åº”式数据
const loading = ref(false)
@@ -151,14 +132,6 @@
    trend: 0
  },
  {
    label: '编制达成率',
    value: 0,
    unit: '%',
    icon: 'DataAnalysis',
    type: 'success',
    trend: 0
  },
  {
    label: '在职员工数',
    value: 0,
    unit: '人',
@@ -171,16 +144,56 @@
// éƒ¨é—¨æ•°æ®
const departmentData = ref([])
// å‘˜å·¥æµå¤±åŽŸå› åˆ†æžæ•°æ®
const staffLeaveReasons = ref([])
// 12个月员工流动流失率分析数据
const turnoverRateStatistics = ref([])
// èŽ·å–åœ¨èŒå‘˜å·¥æ•°
const getStaffCount = async () => {
// èŽ·å–éƒ¨é—¨æ•°æ®
const getDepartmentData = async () => {
  try {
    const res = await staffOnJobListPage({ staffState: 1, current: 1, size: 1 })
    const res = await listDept()
    if (res && res.data) {
      keyMetrics.value[3].value = res.data.total || 0
      departmentData.value = res.data
    }
  } catch (error) {
    console.error('获取在职员工数失败:', error)
    console.error('获取部门数据失败:', error)
  }
}
const getStaffLeaveReasonAnalysis = async () => {
  try {
    const res = await findStaffLeaveReasonAnalysis()
    if (res && res.data) {
      staffLeaveReasons.value = res.data || []
    }
  } catch (error) {
    console.error('获取员工流失原因分析失败:', error)
  }
}
// ä¿®æ”¹ä¸ºè¿”回Promise的异步函数
const getMonthlyTurnoverRateFor12Months = async () => {
  try {
    const res = await findStaffAnalysisMonthlyTurnoverRateFor12Months()
    if (res && res.data) {
      turnoverRateStatistics.value = res.data || []
    }
  } catch (error) {
    console.error('获取12个月员工流动流失率分析数据失败:', error)
  }
}
const getStaffAnalysisTotalStatistic = async () => {
  try {
    const res = await findStaffAnalysisTotalStatistic()
    if (res && res.data) {
      keyMetrics.value[0].value = res.data.totalFlowRate || 0
      keyMetrics.value[1].value = res.data.totalTurnoverRate || 0
      keyMetrics.value[2].value = res.data.currentOnJobCount || 0
    }
  } catch (error) {
    console.error('获取员工分析总统计数据失败:', error)
  }
}
@@ -213,49 +226,28 @@
  }
}
// ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®
const generateMockData = () => {
  // ç”Ÿæˆå…³é”®æŒ‡æ ‡æ•°æ®
  keyMetrics.value[0].value = (Math.random() * 5 + 2).toFixed(1)
  keyMetrics.value[0].trend = (Math.random() * 3 - 1.5).toFixed(1)
  keyMetrics.value[1].value = (Math.random() * 3 + 1).toFixed(1)
  keyMetrics.value[1].trend = (Math.random() * 2 - 1).toFixed(1)
  keyMetrics.value[2].value = (Math.random() * 15 + 85).toFixed(1)
  keyMetrics.value[2].trend = (Math.random() * 3 - 1.5).toFixed(1)
  // ç”Ÿæˆéƒ¨é—¨æ•°æ®
  const departments = ['技术部', '销售部', '人事部', '财务部', '生产部', '市场部']
  departmentData.value = departments.map(dept => ({
    department: dept,
    currentStaff: Math.floor(Math.random() * 30 + 20),
    plannedStaff: Math.floor(Math.random() * 10 + 35),
    staffingRate: Math.floor(Math.random() * 20 + 80),
    turnoverRate: (Math.random() * 4 + 1).toFixed(1),
    attritionRate: (Math.random() * 2 + 0.5).toFixed(1),
    newHires: Math.floor(Math.random() * 5 + 1),
    resignations: Math.floor(Math.random() * 3 + 1),
    status: Math.random() > 0.7 ? '异常' : '正常'
  }))
}
// åˆ·æ–°æ•°æ®
// ä¿®æ”¹ä¸ºå¼‚步函数,确保数据加载完成后再渲染图表
const refreshData = async () => {
  loading.value = true
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    generateMockData()
    loading.value = true
    // ç­‰å¾…所有数据加载完成
    await Promise.all([
      getDepartmentData(),
      getStaffLeaveReasonAnalysis(),
      getMonthlyTurnoverRateFor12Months(),
      getStaffAnalysisTotalStatistic()
    ])
    await nextTick()
    renderAllCharts()
    if (!autoRefreshEnabled.value) {
      ElMessage.success('数据刷新成功')
    }
  } catch (error) {
    console.error('刷新数据失败:', error)
    ElMessage.error('刷新数据失败')
    console.error('数据刷新失败:', error)
    ElMessage.error('数据刷新失败')
  } finally {
    loading.value = false
  }
@@ -276,8 +268,9 @@
    if (attritionChartRef.value) {
      attritionChart = echarts.init(attritionChartRef.value)
    }
    renderAllCharts()
    // åˆå§‹åŒ–时也先加载数据再渲染图表
    refreshData()
  }, 300)
}
@@ -289,14 +282,15 @@
  renderAttritionChart()
}
// æ¸²æŸ“员工流动率趋势图
// ä¿®æ”¹ä¸ºä½¿ç”¨API返回的实际数据
const renderTurnoverChart = () => {
  if (!turnoverChart) return
  const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  const turnoverData = months.map(() => (Math.random() * 5 + 2).toFixed(1))
  const attritionData = months.map(() => (Math.random() * 3 + 1).toFixed(1))
  // ä½¿ç”¨API返回的实际数据
  const months = turnoverRateStatistics.value.map(item => item.month)
  const turnoverData = turnoverRateStatistics.value.map(item => item.flowRate || 0)
  const attritionData = turnoverRateStatistics.value.map(item => item.turnoverRate || 0)
  const option = {
    title: {
      text: '员工流动率趋势',
@@ -346,19 +340,19 @@
      }
    ]
  }
  turnoverChart.setOption(option)
}
// æ¸²æŸ“部门人员分布图
const renderDepartmentChart = () => {
  if (!departmentChart) return
  const data = departmentData.value.map(item => ({
    name: item.department,
    value: item.currentStaff
    name: item.deptName,
    value: item.staffCount
  }))
  const option = {
    title: {
      text: '部门人员分布',
@@ -391,17 +385,17 @@
      }
    ]
  }
  departmentChart.setOption(option)
}
// æ¸²æŸ“编制达成率图
const renderStaffingChart = () => {
  if (!staffingChart) return
  const departments = departmentData.value.map(item => item.department)
  const departments = departmentData.value.map(item => item.deptName)
  const rates = departmentData.value.map(item => item.staffingRate)
  const option = {
    title: {
      text: '编制达成率',
@@ -445,17 +439,17 @@
      }
    ]
  }
  staffingChart.setOption(option)
}
// æ¸²æŸ“员工流失原因分析图
const renderAttritionChart = () => {
  if (!attritionChart) return
  const reasons = ['薪资待遇', '职业发展', '工作环境', '个人原因', '其他']
  const data = reasons.map(() => Math.floor(Math.random() * 20 + 5))
  const reasons = staffLeaveReasons.value.map(item => item.reasonText)
  const data = staffLeaveReasons.value.map(item => item.count)
  const option = {
    title: {
      text: '员工流失原因分析',
@@ -491,14 +485,12 @@
      }
    ]
  }
  attritionChart.setOption(option)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  generateMockData()
  getStaffCount()
  initCharts()
  startAutoRefresh()
})
@@ -663,32 +655,32 @@
  .analytics-container {
    padding: 10px;
  }
  .page-header {
    padding: 15px;
  }
  .page-header h2 {
    font-size: 24px;
  }
  .header-controls {
    flex-direction: column;
    gap: 15px;
  }
  .refresh-btn {
    margin-left: 0;
  }
  .metrics-cards .el-col {
    margin-bottom: 15px;
  }
  .charts-section .el-col {
    margin-bottom: 20px;
  }
  .chart-container {
    height: 300px;
  }
@@ -698,11 +690,11 @@
  .page-header h2 {
    font-size: 20px;
  }
  .card-number {
    font-size: 24px;
  }
  .chart-container {
    height: 250px;
  }
src/views/personnelManagement/contractManagement/components/formDia.vue
@@ -19,23 +19,24 @@
        </div>
      </template>
    </el-dialog>
    <Files ref="filesDia"></Files>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {staffOnJobInfo} from "@/api/personnelManagement/staffOnJob.js";
import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js";
const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue"));
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const filesDia = ref()
const dialogFormVisible = ref(false);
const operationType = ref('')
const tableColumn = ref([
  // {
  //   label: "合同年限",
  //   prop: "contractTerm",
  // },
  {
    label: "合同年限",
    prop: "contractTerm",
  },
  {
    label: "合同开始日期",
    prop: "contractStartTime",
@@ -43,6 +44,22 @@
  {
    label: "合同结束日期",
    prop: "contractEndTime",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: 120,
    operation: [
      {
        name: "上传附件",
        type: "text",
        clickFun: (row) => {
          filesDia.value.openDialog( row,'合同')
        },
      }
    ],
  },
]);
const tableData = ref([]);
@@ -59,6 +76,11 @@
  }
}
const openUploadFile = (row) => {
  filesDia.value.open = true
  filesDia.value.row = row
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
src/views/personnelManagement/contractManagement/filesDia.vue
@@ -30,16 +30,10 @@
          :isSelection="true"
          @selection-change="handleSelectionChange"
          height="500"
          @pagination="paginationSearch"
          :total="page.total"
      >
      </PIMTable>
            <pagination
                style="margin: 10px 0"
                v-show="total > 0"
                @pagination="paginationSearch"
                :total="total"
                :page="page.current"
                :limit="page.size"
            />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">取消</el-button>
src/views/personnelManagement/contractManagement/index.vue
@@ -266,7 +266,7 @@
    type: "warning",
  })
    .then(() => {
      proxy.download("/staff/staffOnJob/export", {}, "合同管理.xlsx");
      proxy.download("/staff/staffOnJob/export", {staffState: 1}, "合同管理.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
src/views/personnelManagement/dimission/components/formDia.vue
@@ -12,101 +12,93 @@
        <el-form :model="form" label-width="200px" label-position="left" :rules="rules" ref="formRef" style="margin-top: 20px">
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="姓名:" prop="staffName">
                <el-select v-model="form.staffName" placeholder="请选择人员" style="width: 100%" @change="handleSelect">
              <el-form-item label="姓名:" prop="staffOnJobId">
                <el-select v-model="form.staffOnJobId"
                           placeholder="请选择人员"
                           style="width: 100%"
                           :disabled="operationType === 'edit'"
                           @change="handleSelect">
                  <el-option
                      v-for="item in personList"
                      :key="item.id"
                      :label="item.staffName"
                      :value="item.staffName"
                      :value="item.id"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="员工编号:">
                {{ form.staffNo || '-' }}
                {{ currentStaffRecord.staffNo || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="性别:">
                {{ form.sex || '-' }}
                {{ currentStaffRecord.sex || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="户籍住址:">
                {{ form.nativePlace || '-' }}
                {{ currentStaffRecord.nativePlace || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="岗位:">
                {{ form.postName || '-' }}
                {{ currentStaffRecord.postName || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="现住址:">
                {{ form.adress || '-' }}
                {{ currentStaffRecord.adress || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="第一学历:">
                {{ form.firstStudy || '-' }}
                {{ currentStaffRecord.firstStudy || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="专业:">
                {{ form.profession || '-' }}
                {{ currentStaffRecord.profession || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="年龄:">
                {{ form.age || '-' }}
                {{ currentStaffRecord.age || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="联系电话:">
                {{ form.phone || '-' }}
                {{ currentStaffRecord.phone || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="紧急联系人:">
                {{ form.emergencyContact || '-' }}
                {{ currentStaffRecord.emergencyContact || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="紧急联系人联系电话:">
                {{ form.emergencyContactPhone || '-' }}
                {{ currentStaffRecord.emergencyContactPhone || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="合同开始日期:">
                {{ form.contractStartTime || '-' }}
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="合同结束日期:">
                {{ form.contractEndTime || '-' }}
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="离职原因:" prop="dimissionReason">
                <el-select v-model="form.dimissionReason" placeholder="请选择离职原因" style="width: 100%" @change="handleSelectDimissionReason">
              <el-form-item label="离职原因:" prop="reason">
                <el-select v-model="form.reason" placeholder="请选择离职原因" style="width: 100%" @change="handleSelectDimissionReason">
                  <el-option
                      v-for="(item, index) in dimissionReasonOptions"
                      :key="index"
@@ -117,11 +109,10 @@
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="备注:" prop="dimissionRemark" v-show="form.dimissionReason === 'other'">
              <el-form-item label="备注:" prop="remark" v-if="form.reason === 'other'">
                <el-input
                    v-model="form.dimissionRemark"
                    v-model="form.remark"
                    type="textarea"
                    v-show="form.dimissionReason === 'other'"
                    :rows="3"
                    placeholder="备注"
                    maxlength="500"
@@ -136,7 +127,7 @@
<!--          <el-col :span="12">-->
<!--            <div class="info-item">-->
<!--              <span class="info-label">离职原因:</span>-->
<!--              <el-select v-model="form.dimissionReason" placeholder="请选择人员" style="width: 100%" @change="handleSelect">-->
<!--              <el-select v-model="form.reason" placeholder="请选择人员" style="width: 100%" @change="handleSelect">-->
<!--                <el-option-->
<!--                    v-for="(item, index) in dimissionReasonOptions"-->
<!--                    :key="index"-->
@@ -167,8 +158,8 @@
<script setup>
import {ref, reactive, toRefs, getCurrentInstance} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
import {createStaffLeave, updateStaffLeave} from "@/api/personnelManagement/staffLeave.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -176,30 +167,13 @@
const operationType = ref('')
const data = reactive({
  form: {
    staffNo: "",
    staffName: "",
    sex: "",
    nativePlace: "",
    postName: "",
    sysPostId: 0,
    adress: "",
    firstStudy: "",
    profession: "",
    age: 0,
    phone: "",
    emergencyContact: "",
    emergencyContactPhone: "",
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
    dimissionDate: "",
    dimissionReason: "",
    dimissionRemark: "",
    staffState: "",
    staffOnJobId: undefined,
    reason: "",
    remark: "",
  },
  rules: {
    staffName: [{ required: true, message: "请选择人员" }],
    dimissionReason: [{ required: true, message: "请选择离职原因"}],
    reason: [{ required: true, message: "请选择离职原因"}],
  },
  dimissionReasonOptions: [
      {label: '薪资待遇', value: 'salary'},
@@ -207,45 +181,51 @@
      {label: '工作环境', value: 'work_environment'},
      {label: '个人原因', value: 'personal_reason'},
      {label: '其他', value: 'other'},
  ]
  ],
  currentStaffRecord: {},
});
const { form, rules, dimissionReasonOptions } = toRefs(data);
const { form, rules, dimissionReasonOptions, currentStaffRecord } = toRefs(data);
// æ‰“开弹框
const openDialog = (type, row) => {
  getList()
  operationType.value = type;
  dialogFormVisible.value = true;
  if (operationType.value === 'edit') {
    getStaffJoinInfo(row.id).then(res => {
      form.value = {...res.data}
    })
    currentStaffRecord.value = row
    form.value.staffOnJobId = row.staffOnJobId
    form.value.reason = row.reason
    form.value.remark = row.remark
    personList.value = [
      {
        staffName: row.staffName,
        id: row.staffOnJobId,
      }
    ]
  } else {
    getList()
  }
}
const handleSelectDimissionReason = (val) => {
  if (val === 'other') {
    form.value.dimissionRemark = ''
    form.value.remark = ''
  }
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  form.value.staffState = 0
  if (form.value.dimissionReason !== 'other') {
    form.value.dimissionRemark = ''
  }
  if (!form.value.sysPostId) {
    form.value.sysPostId = 0;
  if (form.value.reason !== 'other') {
    form.value.remark = ''
  }
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (operationType.value === "add") {
        staffJoinAdd(form.value).then(res => {
        createStaffLeave(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
        staffJoinUpdate(form.value).then(res => {
        updateStaffLeave(currentStaffRecord.value.id, form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
@@ -258,25 +238,9 @@
const closeDia = () => {
  // è¡¨å•已注释,手动重置表单数据
  form.value = {
    staffNo: "",
    staffName: "",
    sex: "",
    nativePlace: "",
    postName: "",
    sysPostId: 0,
    adress: "",
    firstStudy: "",
    profession: "",
    age: 0,
    phone: "",
    emergencyContact: "",
    emergencyContactPhone: "",
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
    dimissionDate: "",
    dimissionReason: "",
    staffState: "",
    staffOnJobId: undefined,
    reason: "",
    remark: "",
  };
  dialogFormVisible.value = false;
  emit('close')
@@ -298,46 +262,11 @@
};
const handleSelect = (val) => {
  let obj = personList.value.find(item => item.staffName === val)
  let obj = personList.value.find(item => item.id === val)
  currentStaffRecord.value = {}
  if (obj) {
    let {
      sex,
      phone,
      staffNo,
      nativePlace,
      postName,
      sysPostId,
      adress,
      firstStudy,
      profession,
      age,
      emergencyContact,
      emergencyContactPhone,
      contractTerm,
      contractStartTime,
      contractEndTime,
      staffName
    } = obj
    // ä¿ç•™ç¦»èŒæ—¥æœŸå’Œç¦»èŒåŽŸå› ï¼Œåªæ›´æ–°å‘˜å·¥ä¿¡æ¯
    form.value = {
      ...form.value,
      sex,
      phone,
      staffNo,
      nativePlace,
      postName,
      sysPostId,
      adress,
      firstStudy,
      profession,
      age,
      emergencyContact,
      emergencyContactPhone,
      contractTerm,
      contractStartTime,
      contractEndTime,
      staffName
    }
    currentStaffRecord.value = obj
  }
}
defineExpose({
src/views/personnelManagement/dimission/index.vue
@@ -11,22 +11,6 @@
            clearable
            :prefix-icon="Search"
        />
        <span style="margin-left: 10px;"  class="search_title">合同开始日期:</span>
        <el-date-picker
            v-model="searchForm.entryDateStart"
            type="date"
            placeholder="请选择合同开始日期"
            size="default"
            @change="(date) => handleDateChange(date,1)"
        />
        <span style="margin-left: 10px;" class="search_title">合同结束日期:</span>
        <el-date-picker
            v-model="searchForm.entryDateEnd"
            type="date"
            placeholder="请选择合同结束日期"
            size="default"
            @change="(date) => handleDateChange(date,2)"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
@@ -58,9 +42,8 @@
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import FormDia from "@/views/personnelManagement/dimission/components/formDia.vue";
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {findStaffLeaveListPage, batchDeleteStaffLeaves} from "@/api/personnelManagement/staffLeave.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
const data = reactive({
  searchForm: {
@@ -109,6 +92,10 @@
    prop: "nativePlace",
  },
  {
    label: "部门",
    prop: "deptName",
  },
  {
    label: "岗位",
    prop: "postName",
  },
@@ -145,24 +132,11 @@
    prop: "emergencyContactPhone",
    width:150
  },
  // {
  //   label: "合同年限",
  //   prop: "contractTerm",
  // },
  {
    label: "合同开始日期",
    prop: "contractStartTime",
    width: 120
  },
  {
    label: "合同结束日期",
    prop: "contractEndTime",
    width: 120
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    operation: [
      {
        name: "编辑",
@@ -186,20 +160,6 @@
const { proxy } = getCurrentInstance()
const handleDateChange = (value,type) => {
  searchForm.value.entryDateEnd = null
  searchForm.value.entryDateStart = null
  if(type === 1){
    if (value) {
      searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD");
    }
  }else{
    if (value) {
      searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD");
    }
  }
  getList();
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
@@ -213,7 +173,7 @@
};
const getList = () => {
  tableLoading.value = true;
  staffJoinListPage({...page, ...searchForm.value, staffState: 0}).then(res => {
  findStaffLeaveListPage({...page, ...searchForm.value}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
@@ -248,7 +208,7 @@
    type: "warning",
  })
      .then(() => {
        staffJoinDel(ids).then((res) => {
        batchDeleteStaffLeaves(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
@@ -265,7 +225,7 @@
    type: "warning",
  })
      .then(() => {
        proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 0}, "人员离职.xlsx");
        proxy.download("/staff/staffLeave/export", {}, "人员离职.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
@@ -56,6 +56,25 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="部门:" prop="sysDeptId">
              <el-tree-select
                  v-model="form.sysDeptId"
                  :data="deptOptions"
                  :props="{ value: 'id', label: 'label', children: 'children' }"
                  value-key="id"
                  placeholder="请选择部门"
                  check-strictly
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="年龄:" prop="age">
              <el-input-number v-model="form.age" :precision="0" :step="1" style="width: 100%"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="第一学历:" prop="firstStudy">
              <el-input v-model="form.firstStudy" placeholder="请输入" clearable/>
            </el-form-item>
@@ -63,13 +82,6 @@
          <el-col :span="12">
            <el-form-item label="专业:" prop="profession">
              <el-input v-model="form.profession" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="年龄:" prop="age">
              <el-input-number v-model="form.age" :precision="0" :step="1" style="width: 100%"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -140,9 +152,10 @@
<script setup>
import {ref, onMounted} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {findPostOptions} from "@/api/system/post.js";
import {listDept} from "@/api/system/dept.js";
import {staffOnJobInfo, createStaffOnJob, updateStaffOnJob} from "@/api/personnelManagement/staffOnJob.js";
import {deptTreeSelect} from "@/api/system/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -168,6 +181,7 @@
    contractEndTime: "",
    staffState: "",
    sysPostId: undefined,
    sysDeptId: undefined,
  },
  rules: {
    staffNo: [{ required: true, message: "请输入", trigger: "blur" },],
@@ -187,8 +201,9 @@
    contractEndTime: [{ required: true, message: "请输入", trigger: "blur" }],
  },
  postOptions: [], // å²—位选项
  deptOptions: [], // éƒ¨é—¨é€‰é¡¹
});
const { form, rules, postOptions } = toRefs(data);
const { form, rules, postOptions, deptOptions } = toRefs(data);
// æ‰“开弹框
const openDialog = (type, row) => {
@@ -211,6 +226,7 @@
}
onMounted(() => {
  fetchPostOptions()
  fetchDeptOptions()
})
const fetchPostOptions = () => {
@@ -218,6 +234,27 @@
    postOptions.value = res.data
  })
}
// æŸ¥è¯¢éƒ¨é—¨åˆ—表
const fetchDeptOptions = () => {
  deptTreeSelect().then(response => {
    deptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
  })
}
/** è¿‡æ»¤ç¦ç”¨çš„部门 */
function filterDisabledDept(deptList) {
  return deptList.filter(dept => {
    if (dept.disabled) {
      return false
    }
    if (dept.children && dept.children.length) {
      dept.children = filterDisabledDept(dept.children)
    }
    return true
  })
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  if (!form.value.sysPostId) {
src/views/personnelManagement/employeeRecord/components/RenewContract.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
<template>
  <el-dialog
      v-model="isShow"
      title="续签合同"
      width="800px"
      @close="closeModal"
  >
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-form-item label="合同开始日期:" prop="contractStartTime">
        <el-date-picker
            v-model="form.contractStartTime"
            type="date"
            placeholder="请选择日期"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            clearable
            style="width: 100%"
            @change="calculateContractTerm"
        />
      </el-form-item>
      <el-form-item label="合同结束日期:" prop="contractEndTime">
        <el-date-picker
            v-model="form.contractEndTime"
            type="date"
            placeholder="请选择日期"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            clearable
            style="width: 100%"
            @change="calculateContractTerm"
        />
      </el-form-item>
      <el-form-item label="合同年限:" prop="contractTerm">
        <el-input-number v-model="form.contractTerm" :precision="0" :step="1" style="width: 100%" :disabled="true"/>
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitForm">确认</el-button>
        <el-button @click="closeModal">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
// ç»­ç­¾åˆåŒ
import { renewContract } from "@/api/personnelManagement/staffOnJob.js";
import {computed, getCurrentInstance,} from "vue";
const emit = defineEmits(['update:visible', 'completed']);
const data = reactive({
  form: {
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
  },
  rules: {
    contractTerm: [{ required: true, message: "请输入", trigger: "blur" }],
    contractStartTime: [{ required: true, message: "请输入", trigger: "blur" }],
    contractEndTime: [{ required: true, message: "请输入", trigger: "blur" }],
  }
});
const { form, rules } = toRefs(data);
let { proxy } = getCurrentInstance()
const props = defineProps({
  id: {
    type: Number,
    default: 0,
  },
  visible: {
    type: Boolean,
    required: true,
  },
})
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
// è®¡ç®—合同年限
const calculateContractTerm = () => {
  if (form.value.contractStartTime && form.value.contractEndTime) {
    const startDate = new Date(form.value.contractStartTime);
    const endDate = new Date(form.value.contractEndTime);
    if (endDate > startDate) {
      // è®¡ç®—年份差
      const yearDiff = endDate.getFullYear() - startDate.getFullYear();
      const monthDiff = endDate.getMonth() - startDate.getMonth();
      const dayDiff = endDate.getDate() - startDate.getDate();
      let years = yearDiff;
      // å¦‚果结束日期的月日小于开始日期的月日,则减去1å¹´
      if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
        years = yearDiff - 1;
      }
      form.value.contractTerm = Math.max(0, years);
    } else {
      form.value.contractTerm = 0;
    }
  } else {
    form.value.contractTerm = 0;
  }
};
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      renewContract(props.id, form.value).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("续签合同成功");
          emit('completed');
          closeModal();
        }
      })
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  form.value = {
    contractTerm: 0,
    contractStartTime: "",
    contractEndTime: "",
  };
  isShow.value = false;
};
</script>
src/views/personnelManagement/employeeRecord/index.vue
@@ -39,6 +39,12 @@
    </div>
    <show-form-dia ref="formDia" @close="handleQuery"></show-form-dia>
    <new-or-edit-form-dia ref="formDiaNewOrEditFormDia" @close="handleQuery"></new-or-edit-form-dia>
    <renew-contract
        v-if="isShowRenewContractModal"
        v-model:visible="isShowRenewContractModal"
        :id="id"
        @completed="handleQuery"
    />
  </div>
</template>
@@ -48,9 +54,9 @@
import {ElMessageBox} from "element-plus";
import {batchDeleteStaffOnJobs, staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
import dayjs from "dayjs";
const NewOrEditFormDia = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue"));
const ShowFormDia = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/Show.vue"));
const RenewContract = defineAsyncComponent(() => import( "@/views/personnelManagement/employeeRecord/components/RenewContract.vue"));
const data = reactive({
  searchForm: {
@@ -61,6 +67,8 @@
  },
});
const { searchForm } = toRefs(data);
const isShowRenewContractModal = ref(false);
const id = ref(0);
const tableColumn = ref([
  {
    label: "状态",
@@ -100,6 +108,10 @@
  {
    label: "户籍住址",
    prop: "nativePlace",
  },
  {
    label: "部门",
    prop: "deptName",
  },
  {
    label: "岗位",
@@ -166,6 +178,15 @@
          openFormNewOrEditFormDia("edit", row);
        },
      },
      {
        name: "续签合同",
        type: "text",
        showHide: row => row.staffState === 1,
        clickFun: (row) => {
          isShowRenewContractModal.value = true;
          id.value = row.id;
        },
      },
      // {
      //   name: "详情",
      //   type: "text",
@@ -212,7 +233,7 @@
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  params.entryDate = undefined
  staffOnJobListPage({...params, staffState: 1}).then(res => {
  staffOnJobListPage({...params}).then(res => {
    tableLoading.value = false;
    tableData.value = res.data.records
    page.total = res.data.total;
src/views/personnelManagement/onboarding/components/formDia.vue
ÎļþÒÑɾ³ý
src/views/personnelManagement/onboarding/components/formDiaXJHT.vue
ÎļþÒÑɾ³ý
src/views/personnelManagement/onboarding/index.vue
ÎļþÒÑɾ³ý
src/views/personnelManagement/payrollManagement/components/formDia.vue
@@ -168,8 +168,8 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, getStaffOnJob, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {compensationAdd, compensationUpdate} from "@/api/personnelManagement/payrollManagement.js";
import {staffOnJobInfo, staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -234,12 +234,16 @@
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    getStaffOnJob().then(res => {
        personList.value = res.data
    })
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    personList.value = res.data.records || []
  })
    form.value = {}
  if (operationType.value === 'edit') {
    getStaffJoinInfo(row.id).then(res => {
    staffOnJobInfo(row.staffId).then(res => {
            form.value = {...row}
            form.value.payDate = form.value.payDate + '-01'
    })
src/views/personnelManagement/payrollManagement/index.vue
@@ -53,7 +53,6 @@
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/personnelManagement/payrollManagement/components/formDia.vue";
import {staffJoinDel} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {compensationDelete, compensationListPage} from "@/api/personnelManagement/payrollManagement.js";
src/views/personnelManagement/scheduling/index.vue
@@ -253,9 +253,9 @@
import {useDict} from "@/utils/dict.js"
import {Plus, Download, Search, Refresh} from '@element-plus/icons-vue'
import {save, del, delByIds, listPage} from "@/api/personnelManagement/scheduling.js"
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import pagination from "@/components/PIMTable/Pagination.vue";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
const { proxy } = getCurrentInstance();
@@ -317,8 +317,12 @@
 * èŽ·å–å½“å‰åœ¨èŒäººå‘˜åˆ—è¡¨
 */
const getPersonList = () => {
  getStaffOnJob().then(res => {
    personList.value = res.data
  staffOnJobListPage({
    current: -1,
    size: -1,
    staffState: 1
  }).then(res => {
    personList.value = res.data.records || []
  })
};
const paginationChange = (obj) => {
src/views/personnelManagement/selfService/index.vue
@@ -221,8 +221,7 @@
const { proxy } = getCurrentInstance()
import { getUserProfile } from '@/api/system/user.js'
import {staffJoinUpdate, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import { fa, id } from 'element-plus/es/locales.mjs'
import {staffOnJobListPage, updateStaffOnJob} from "@/api/personnelManagement/staffOnJob.js";
const tableLoading = ref(false)
// åˆ†é¡µå‚æ•°
@@ -572,7 +571,7 @@
      currentUser.value = res.data
      // console.log("----",currentUser.value)
        //得到人员列表
        staffJoinListPage({staffState: 1}).then(res => {
      staffOnJobListPage({staffState: 1}).then(res => {
          //筛选出和currentUser同名的人员
          // let tableData = res.data.records
          user.value = res.data.records.find(item => item.staffName === currentUser.value.userName)
@@ -604,18 +603,15 @@
    const userRes = await getUserProfile();
    if (userRes.code === 200) {
      currentUser.value = userRes.data;
      const staffListRes = await staffJoinListPage({ staffState: 1 });
      const staffListRes = await staffOnJobListPage({ staffState: 1 });
      user.value = staffListRes.data.records.find(item => item.staffName === currentUser.value.userName);
      // console.log("++++", user.value);
      Object.assign(joinForm, user.value);
      joinForm.staffName = profileForm.name;
      joinForm.phone = profileForm.phone;
      joinForm.email = profileForm.email;
      joinForm.adress = profileForm.adress; 
      console.log(joinForm)
      // è°ƒç”¨æ›´æ–°ä¸ªäººä¿¡æ¯çš„æŽ¥å£
      staffJoinUpdate(joinForm).then(res => {
      updateStaffOnJob(user.value.id, joinForm).then(res => {
        if (res.code === 200) {
          ElMessage.success('个人信息保存成功');
          getProfile();
src/views/procurementManagement/procurementLedger/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <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="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>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import filePreview from '@/components/filePreview/index.vue'
import { delCommonFile } from '@/api/publicApi/commonFile.js'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
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(() => {
    proxy.$modal.msg('已取消删除')
  })
}
defineExpose({
  open
})
</script>
<style></style>
src/views/procurementManagement/procurementLedger/index.vue
@@ -2,461 +2,442 @@
  <div class="app-container">
    <div class="search_form">
      <div>
        <el-form :model="searchForm" :inline="true">
        <el-form :model="searchForm"
                 :inline="true">
          <el-form-item label="供应商名称:">
            <el-input v-model="searchForm.supplierName" placeholder="请输入" clearable prefix-icon="Search"
            <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-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"
            <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"
            <el-input v-model="searchForm.projectName"
                      placeholder="请输入"
                      clearable
                      prefix-icon="Search"
                      @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-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>
            <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
            <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 type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="success"
                   @click="openScanAddDialog">扫码新增</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>
      <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 - 22em)"
          :row-class-name="tableRowClassName"
      >
        <el-table-column align="center" type="selection" width="55" />
      <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 - 19em)"
                :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 :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">
        <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"
                         show-overflow-tooltip />
        <el-table-column label="供应商名称"
                         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>
            <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
        >
        <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"
            >
            <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="操作"
            width="100"
            align="center"
        >
        <el-table-column label="签订日期"
                         prop="executionDate"
                         width="100"
                         show-overflow-tooltip />
        <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="120"
                         show-overflow-tooltip />
        <el-table-column label="录入日期"
                         prop="entryDate"
                         width="100"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
                         label="操作"
                         width="180"
                         align="center">
          <template #default="scope">
            <el-button
                link
                type="primary"
                size="small"
                @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="success"
                       size="small"
                       @click="showQRCode(scope.row)">生成二维码</el-button>
            <el-button link
                       type="primary"
                       size="small"
                       @click="downLoadFile(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"
      />
      <pagination v-show="total > 0"
                  :total="total"
                  layout="total, sizes, prev, pager, next, jumper"
                  :page="page.current"
                  :limit="page.size"
                  @pagination="paginationChange" />
    </div>
    <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-dialog v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
               width="70%"
               @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="purchaseContractNumber">
              <el-input
                  v-model="form.purchaseContractNumber"
                  placeholder="请输入"
                  clearable
              />
            <el-form-item label="采购合同号:"
                          prop="purchaseContractNumber">
              <el-input v-model="form.purchaseContractNumber"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="销售合同号:" prop="salesLedgerId">
              <el-select
                  v-model="form.salesLedgerId"
                  placeholder="请选择"
                  clearable
                  @change="salesLedgerChange"
              >
                <el-option
                    v-for="item in salesContractList"
                    :key="item.id"
                    :label="item.salesContractNo"
                    :value="item.id"
                />
            <el-form-item label="销售合同号:"
                          prop="salesLedgerId">
              <el-select v-model="form.salesLedgerId"
                         placeholder="请选择"
                         filterable
                         clearable
                         @change="salesLedgerChange">
                <el-option v-for="item in salesContractList"
                           :key="item.id"
                           :label="item.salesContractNo"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierId">
              <el-select
                  v-model="form.supplierId"
                  placeholder="请选择"
                  clearable
                  filterable
                  allow-create
              >
                <el-option
                    v-for="item in supplierList"
                    :key="item.id"
                    :label="item.supplierName"
                    :value="item.id"
                />
            <el-form-item label="供应商名称:"
                          prop="supplierId">
              <el-select v-model="form.supplierId"
                         placeholder="请选择"
                         filterable
                         clearable>
                <el-option v-for="item in supplierList"
                           :key="item.id"
                           :label="item.supplierName"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input
                  v-model="form.projectName"
                  placeholder="请输入"
                  clearable
              />
            <el-form-item label="项目名称"
                          prop="projectName">
              <el-input v-model="form.projectName"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input
                  v-model="form.paymentMethod"
                  placeholder="请输入"
                  clearable
              />
              <el-input v-model="form.paymentMethod"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="签订日期:" prop="executionDate">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.executionDate"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            <el-form-item label="签订日期:"
                          prop="executionDate">
              <el-date-picker style="width: 100%"
                              v-model="form.executionDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="审批人:" prop="approverId">
              <el-select
                  v-model="form.approverId"
                  placeholder="请选择审批人"
                  clearable
              >
                <el-option
                    v-for="item in userList"
                    :key="item.userId"
                    :label="item.nickName"
                    :value="item.userId"
                />
            <el-form-item label="审批人:"
                          prop="approverId">
              <el-select v-model="form.approverId"
                         placeholder="请选择审批人"
                         clearable>
                <el-option v-for="item in userList"
                           :key="item.userId"
                           :label="item.nickName"
                           :value="item.userId" />
              </el-select>
            </el-form-item>
            <el-form-item label="录入人:" prop="recorderId" v-show="false">
              <el-select
                  v-model="form.recorderId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                    :key="item.userId"
                    :label="item.nickName"
                    :value="item.userId"
                />
            <el-form-item label="录入人:"
                          prop="recorderId"
                          v-show="false">
              <el-select v-model="form.recorderId"
                         placeholder="请选择"
                         clearable
                         disabled>
                <el-option v-for="item in userList"
                           :key="item.userId"
                           :label="item.nickName"
                           :value="item.userId" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期:" prop="entryDate">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.entryDate"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            <el-form-item label="录入日期:"
                          prop="entryDate">
              <el-date-picker style="width: 100%"
                              v-model="form.entryDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-form-item label="产品信息:" prop="entryDate">
            <el-button type="primary" @click="openProductForm('add')"
            >添加</el-button
            >
            <el-button plain type="danger" @click="deleteProduct"
            >删除</el-button
            >
          <el-form-item label="产品信息:"
                        prop="entryDate">
            <el-button type="primary"
                       @click="openProductForm('add')">添加</el-button>
            <el-button plain
                       type="danger"
                       @click="deleteProduct">删除</el-button>
          </el-form-item>
          <div class="select-button-group" style="width: 220px; margin: 20px 0;" v-if="operationType === 'add'">
            <el-select
                filterable
                allow-create
                :reserve-keyword="true"
                :default-first-option="false"
                v-model="templateName"
                :input-value="filterInputValue"
                @filter-change="onTemplateFilterChange"
                @change="onTemplateChange"
                style="width: 180px; border-right: none; border-radius: 4px 0 0 4px;"
                placeholder="请选择"
                class="no-arrow-select"
            >
              <el-option
                  v-for="item in templateList"
                  :key="item.value"
                  :label="item.templateName"
                  :value="item.templateName"
              ></el-option>
          <div class="select-button-group"
               style="width: 220px; margin: 20px 0;"
               v-if="operationType === 'add'">
            <el-select filterable
                       allow-create
                       :reserve-keyword="true"
                       :default-first-option="false"
                       v-model="templateName"
                       :input-value="filterInputValue"
                       @filter-change="onTemplateFilterChange"
                       @change="onTemplateChange"
                       style="width: 180px; border-right: none; border-radius: 4px 0 0 4px;"
                       placeholder="请选择"
                       class="no-arrow-select">
              <el-option v-for="item in templateList"
                         :key="item.value"
                         :label="item.templateName"
                         :value="item.templateName"></el-option>
            </el-select>
            <!-- æŒ‰é’®ï¼šä¸Ž Select é«˜åº¦åŒ¹é…ï¼ŒåŽ»æŽ‰å·¦ä¾§è¾¹æ¡†ï¼Œæ— ç¼è¡”æŽ¥ -->
            <el-button
                size="small"
                style="height: 32px; border-radius: 0 4px 4px 0; margin-left: -1px;"
                @click="handleButtonClick"
                :disabled="!templateName || templateName.trim() === '' || isTemplateNameDuplicate"
            >
            <el-button size="small"
                       style="height: 32px; border-radius: 0 4px 4px 0; margin-left: -1px;"
                       @click="handleButtonClick"
                       :disabled="!templateName || templateName.trim() === '' || isTemplateNameDuplicate">
              ä¿å­˜
            </el-button>
          </div>
        </el-row>
        <el-table
            :data="productData"
            border
            @selection-change="productSelected"
            show-summary
            :summary-method="summarizeProTable"
        >
          <el-table-column align="center" type="selection" width="55" />
          <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" width="70" />
          <el-table-column label="数量" prop="quantity" width="70" />
          <el-table-column label="库存预警数量" prop="warnNum" width="120" show-overflow-tooltip />
          <el-table-column label="税率(%)" prop="taxRate" width="80" />
          <el-table-column
              label="含税单价(元)"
              prop="taxInclusiveUnitPrice"
              :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
              fixed="right"
              label="操作"
              min-width="60"
              align="center"
          >
        <el-table :data="productData"
                  border
                  @selection-change="productSelected"
                  show-summary
                  :summary-method="summarizeProTable">
          <el-table-column align="center"
                           type="selection"
                           width="55" />
          <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"
                           width="70" />
          <el-table-column label="数量"
                           prop="quantity"
                           width="70" />
          <el-table-column label="库存预警数量"
                           prop="warnNum"
                           width="120"
                           show-overflow-tooltip />
          <el-table-column label="税率(%)"
                           prop="taxRate"
                           width="80" />
          <el-table-column label="含税单价(元)"
                           prop="taxInclusiveUnitPrice"
                           :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 label="是否质检"
                           prop="isChecked"
                           width="150">
            <template #default="scope">
              <el-button
                  link
                  type="primary"
                  size="small"
                  @click="openProductForm('edit', scope.row, scope.$index)"
              >编辑</el-button
              >
              <el-tag :type="scope.row.isChecked ? 'success' : 'info'">
                {{ scope.row.isChecked ? '是' : '否' }}
              </el-tag>
            </template>
          </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="openProductForm('edit', scope.row, scope.$index)">编辑</el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:" prop="remark">
              <el-input
                  v-model="form.remark"
                  placeholder="请输入"
                  clearable
                  type="textarea"
                  :rows="2"
              />
            <el-form-item label="备注·:"
                          prop="remark">
              <el-input v-model="form.remark"
                        placeholder="请输入"
                        clearable
                        type="textarea"
                        :rows="2" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:" prop="remark">
              <el-upload
                  v-model:file-list="fileList"
                  :action="upload.url"
                  multiple
                  ref="fileUpload"
                  auto-upload
                  :headers="upload.headers"
                  :before-upload="handleBeforeUpload"
                  :on-error="handleUploadError"
                  :on-success="handleUploadSuccess"
                  :on-remove="handleRemove"
              >
            <el-form-item label="附件材料:"
                          prop="remark">
              <el-upload v-model:file-list="fileList"
                         :action="upload.url"
                         multiple
                         ref="fileUpload"
                         auto-upload
                         :headers="upload.headers"
                         :before-upload="handleBeforeUpload"
                         :on-error="handleUploadError"
                         :on-success="handleUploadSuccess"
                         :on-remove="handleRemove">
                <el-button type="primary">上传</el-button>
                <template #tip>
                  <div class="el-upload__tip">
@@ -469,509 +450,764 @@
          </el-col>
        </el-row>
      </el-form>
    </FormDialog>
    <FormDialog
        v-model="productFormVisible"
        :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
        :width="'40%'"
        :operation-type="productOperationType"
        @close="closeProductDia"
        @confirm="submitProduct"
        @cancel="closeProductDia"
    >
      <el-form
          :model="productForm"
          label-width="140px"
          label-position="top"
          :rules="productRules"
          ref="productFormRef"
      >
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <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">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品大类:" prop="productId">
              <el-tree-select
                  v-model="productForm.productId"
                  placeholder="请选择"
                  clearable
                  check-strictly
                  @change="getModels"
                  :data="productOptions"
                  :render-after-expand="false"
                  style="width: 100%"
              />
            <el-form-item label="产品大类:"
                          prop="productId">
              <el-tree-select v-model="productForm.productId"
                              placeholder="请选择"
                              clearable
                              check-strictly
                              @change="getModels"
                              :data="productOptions"
                              :render-after-expand="false"
                              style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select
                  v-model="productForm.productModelId"
                  placeholder="请选择"
                  clearable
                  @change="getProductModel"
              >
                <el-option
                    v-for="item in modelOptions"
                    :key="item.id"
                    :label="item.model"
                    :value="item.id"
                />
            <el-form-item label="规格型号:"
                          prop="productModelId">
              <el-select v-model="productForm.productModelId"
                         placeholder="请选择"
                         clearable
                         @change="getProductModel">
                <el-option v-for="item in modelOptions"
                           :key="item.id"
                           :label="item.model"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input
                  v-model="productForm.unit"
                  placeholder="请输入"
                  clearable
              />
            <el-form-item label="单位:"
                          prop="unit">
              <el-input v-model="productForm.unit"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="税率(%):" prop="taxRate">
              <el-select
                  v-model="productForm.taxRate"
                  placeholder="请选择"
                  clearable
                  @change="mathNum"
              >
                <el-option label="1" value="1" />
                <el-option label="6" value="6" />
                <el-option label="13" value="13" />
            <el-form-item label="税率(%):"
                          prop="taxRate">
              <el-select v-model="productForm.taxRate"
                         placeholder="请选择"
                         clearable
                         @change="mathNum">
                <el-option label="1"
                           value="1" />
                <el-option label="6"
                           value="6" />
                <el-option label="13"
                           value="13" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
              <el-input-number
                  v-model="productForm.taxInclusiveUnitPrice"
                  :precision="2"
                  :step="0.1"
                  clearable
                  style="width: 100%"
                  @change="mathNum"
              />
            <el-form-item label="含税单价(元):"
                          prop="taxInclusiveUnitPrice">
              <el-input-number v-model="productForm.taxInclusiveUnitPrice"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%"
                               @change="mathNum" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number
                  :step="0.1"
                  clearable
                  :precision="2"
                  style="width: 100%"
                  v-model="productForm.quantity"
                  placeholder="请输入"
                  @change="mathNum"
              />
            <el-form-item label="数量:"
                          prop="quantity">
              <el-input-number :step="0.1"
                               clearable
                               :precision="2"
                               :min="0"
                               style="width: 100%"
                               v-model="productForm.quantity"
                               placeholder="请输入"
                               @change="mathNum" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
              <el-input-number
                  v-model="productForm.taxInclusiveTotalPrice"
                  :precision="2"
                  :step="0.1"
                  clearable
                  style="width: 100%"
                  @change="reverseMathNum('taxInclusiveTotalPrice')"
              />
            <el-form-item label="含税总价(元):"
                          prop="taxInclusiveTotalPrice">
              <el-input-number v-model="productForm.taxInclusiveTotalPrice"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%"
                               @change="reverseMathNum('taxInclusiveTotalPrice')" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
                label="不含税总价(元):"
                prop="taxExclusiveTotalPrice"
            >
              <el-input
                  v-model="productForm.taxExclusiveTotalPrice"
                  @change="reverseMathNum('taxExclusiveTotalPrice')"
              />
            <el-form-item label="不含税总价(元):"
                          prop="taxExclusiveTotalPrice">
              <el-input-number v-model="productForm.taxExclusiveTotalPrice"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%"
                               @change="reverseMathNum('taxExclusiveTotalPrice')" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票类型:" prop="invoiceType">
              <el-select
                  v-model="productForm.invoiceType"
                  placeholder="请选择"
                  clearable
              >
                <el-option label="增普票" value="增普票" />
                <el-option label="增专票" value="增专票" />
            <el-form-item label="发票类型:"
                          prop="invoiceType">
              <el-select v-model="productForm.invoiceType"
                         placeholder="请选择"
                         clearable>
                <el-option label="增普票"
                           value="增普票" />
                <el-option label="增专票"
                           value="增专票" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="库存预警数量:" prop="warnNum">
              <el-input-number
                  v-model="productForm.warnNum"
                  :precision="2"
                  :step="0.1"
                  clearable
                  style="width: 100%"
              />
            <el-form-item label="库存预警数量:"
                          prop="warnNum">
              <el-input-number v-model="productForm.warnNum"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="是否质检:"
                          prop="isChecked">
              <el-radio-group v-model="productForm.isChecked">
                <el-radio label="是"
                          :value="true" />
                <el-radio label="否"
                          :value="false" />
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </FormDialog>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitProduct">确认</el-button>
          <el-button @click="closeProductDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- äºŒç»´ç æ˜¾ç¤ºå¯¹è¯æ¡† -->
    <el-dialog v-model="qrCodeDialogVisible"
               title="采购合同号二维码"
               width="400px"
               center>
      <div style="text-align: center;">
        <img :src="qrCodeUrl"
             alt="二维码"
             style="width:200px;height:200px;" />
        <div style="margin: 20px;">
          <el-button type="primary"
                     @click="downloadQRCode">下载二维码图片</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- æ‰«ç æ–°å¢žå¯¹è¯æ¡† -->
    <el-dialog v-model="scanAddDialogVisible"
               title="扫码新增采购台账"
               width="70%"
               @close="closeScanAddDialog">
      <el-form :model="scanAddForm"
               label-width="140px"
               label-position="top"
               :rules="scanAddRules"
               ref="scanAddFormRef">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码内容:">
              <el-input v-model="scanAddForm.scanContent"
                        type="textarea"
                        :rows="3"
                        placeholder="请扫描二维码或手动输入采购合同信息"
                        @input="parseScanContent" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:"
                          prop="purchaseContractNumber">
              <el-input v-model="scanAddForm.purchaseContractNumber"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:"
                          prop="supplierName">
              <el-input v-model="scanAddForm.supplierName"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:"
                          prop="projectName">
              <el-input v-model="scanAddForm.projectName"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合同金额(元):"
                          prop="contractAmount">
              <el-input-number v-model="scanAddForm.contractAmount"
                               :precision="2"
                               :step="0.1"
                               clearable
                               style="width: 100%"
                               placeholder="请输入" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款方式:">
              <el-input v-model="scanAddForm.paymentMethod"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入人:">
              <el-input v-model="scanAddForm.recorderName"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:">
              <el-input v-model="scanAddForm.remark"
                        type="textarea"
                        :rows="2"
                        placeholder="请输入备注信息"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitScanAdd">确认新增</el-button>
          <el-button @click="closeScanAddDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰«ç ç™»è®°å¯¹è¯æ¡† -->
    <el-dialog v-model="scanDialogVisible"
               title="扫码登记"
               width="60%"
               @close="closeScanDialog">
      <el-form :model="scanForm"
               label-width="120px"
               label-position="left"
               :rules="scanRules"
               ref="scanFormRef">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:">
              <el-input v-model="scanForm.purchaseContractNumber"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:">
              <el-input v-model="scanForm.supplierName"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:">
              <el-input v-model="scanForm.projectName"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码时间:">
              <el-input v-model="scanForm.scanTime"
                        disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="扫码人:">
              <el-input v-model="scanForm.scannerName"
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码状态:">
              <el-tag :type="scanForm.scanStatus === '已扫码' ? 'success' : 'warning'">
                {{ scanForm.scanStatus }}
              </el-tag>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码备注:">
              <el-input v-model="scanForm.scanRemark"
                        type="textarea"
                        :rows="3"
                        placeholder="请输入扫码备注信息" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码记录:">
              <el-table :data="scanRecords"
                        border
                        style="width: 100%">
                <el-table-column label="序号"
                                 type="index"
                                 width="60"
                                 align="center" />
                <el-table-column label="扫码时间"
                                 prop="scanTime"
                                 width="180" />
                <el-table-column label="扫码人"
                                 prop="scannerName"
                                 width="120" />
                <el-table-column label="扫码状态"
                                 prop="scanStatus"
                                 width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.scanStatus === '已扫码' ? 'success' : 'warning'">
                      {{ scope.row.scanStatus }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="备注"
                                 prop="scanRemark" />
              </el-table>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitScan">确认扫码</el-button>
          <el-button @click="closeScanDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import {getToken} from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import FormDialog from '@/components/Dialog/FormDialog.vue';
import {getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs} from "vue";
import {Search} from "@element-plus/icons-vue";
import {ElMessage, ElMessageBox} from "element-plus";
import {userListNoPage} from "@/api/system/user.js";
import {
  addOrUpdateSalesLedgerProduct,
  delLedgerFile,
  delProduct,
  getProductInfoByContractNo,
  getSalesLedgerWithProducts,
} from "@/api/salesManagement/salesLedger.js";
import {
  addOrEditPurchase,
  addPurchaseTemplate,
  createPurchaseNo,
  delPurchase,
  getOptions,
  getPurchaseById,
  getPurchaseTemplateList,
  getSalesNo,
  productList,
  purchaseListPage
} from "@/api/procurementManagement/procurementLedger.js";
import useFormData from "@/hooks/useFormData.js";
import useUserStore from "@/store/modules/user";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import dayjs from "dayjs";
import { getCurrentDate } from "@/utils/index.js";
  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, ElMessage } from "element-plus";
  import { userListNoPage } from "@/api/system/user.js";
  import FileList from "./fileList.vue";
  import {
    getSalesLedgerWithProducts,
    addOrUpdateSalesLedgerProduct,
    delProduct,
    delLedgerFile,
    getProductInfoByContractNo,
  } from "@/api/salesManagement/salesLedger.js";
  import {
    addOrEditPurchase,
    addPurchaseTemplate,
    createPurchaseNo,
    delPurchase,
    getSalesNo,
    purchaseListPage,
    productList,
    getPurchaseById,
    getOptions,
    getPurchaseTemplateList,
  } 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([]);
  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";
const userStore = useUserStore();
  const userStore = useUserStore();
// è®¢å•审批状态显示文本
const approvalStatusText = {
  0: '审批中',
  1: '审批通过',
  2: '审批失败'
};
  // äºŒç»´ç ç›¸å…³å˜é‡
  const qrCodeDialogVisible = ref(false);
  const qrCodeUrl = ref("");
  // è®¢å•审批状态显示文本
  const approvalStatusText = {
    0: "审批中",
    1: "审批通过",
    2: "审批失败",
  };
const templateName = ref('');
const filterInputValue = ref('');
const templateList = ref([]);
const isTemplateNameDuplicate = ref(false); // æ ‡è®°æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
// æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
const checkTemplateNameDuplicate = (name) => {
  if (!name || name.trim() === '') {
    isTemplateNameDuplicate.value = false;
    return false;
  }
  const isDuplicate = templateList.value.some(item => item.templateName === name.trim());
  isTemplateNameDuplicate.value = isDuplicate;
  return isDuplicate;
};
// é˜²æŠ–定时器
let duplicateCheckTimer = null;
const onTemplateFilterChange = (val) => {
  filterInputValue.value = val ?? '';
  // æ¸…除之前的定时器
  if (duplicateCheckTimer) {
    clearTimeout(duplicateCheckTimer);
  }
  // å®žæ—¶æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤ï¼ˆé˜²æŠ–处理,避免频繁提示)
  if (val && val.trim()) {
    duplicateCheckTimer = setTimeout(() => {
      const isDuplicate = checkTemplateNameDuplicate(val);
      if (isDuplicate) {
        ElMessage({
          message: '模板名称已存在,请更换模板名称',
          type: 'warning',
          duration: 2000
        });
      }
    }, 300); // 300ms é˜²æŠ–
  } else {
    isTemplateNameDuplicate.value = false;
  }
};
// allow-create æ—¶ï¼Œè¾“入不存在的内容会作为 string å€¼è¿”回;这里同步回输入框以确保文字不丢
const onTemplateChange = async (val) => {
  if (typeof val === 'string') {
    filterInputValue.value = val;
    // é€‰æ‹©æˆ–输入时检查重复
    checkTemplateNameDuplicate(val);
  }
  // è¿‡æ»¤æ•°æ®ï¼ŒæŸ¥æ‰¾åŒ¹é…çš„æ¨¡æ¿
  const matchedTemplate = templateList.value.find(item => item.templateName === val);
  if (matchedTemplate?.id) {
    // å¦‚果找到模板,加载模板数据
    form.value = {
      ...form.value,
      ...matchedTemplate,
    };
    productData.value = matchedTemplate.productData || [];
    // ç”Ÿæˆæ–°çš„采购合同号
    try {
      const res = await createPurchaseNo();
      if (res?.data) {
        form.value.purchaseContractNumber = res.data;
      }
    } catch (error) {
      console.error('生成采购合同号失败:', error);
    }
  } else {
    // å¦‚果没有找到模板,重置表单(保持当前表单状态)
    const currentFormData = { ...form.value };
    const currentProductData = [...productData.value];
    // å¦‚果对话框未打开,先打开
    if (!dialogFormVisible.value) {
      operationType.value = 'add';
      dialogFormVisible.value = true;
    }
    // ç­‰å¾…下一个 tick åŽæ¢å¤æ•°æ®
    await nextTick();
    form.value = {
      ...form.value,
      ...currentFormData,
    };
    productData.value = currentProductData;
  }
};
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
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: "",
  },
  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" }],
    approverId:[{ 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) => {
  if (cellValue === null || cellValue === undefined || cellValue === '') {
    return '0.00';
  }
  const num = parseFloat(cellValue);
  if (isNaN(num)) {
    return '0.00';
  }
  return num.toFixed(2);
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
// ä¿å­˜æ¨¡æ¿
const handleButtonClick = async () => {
  // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦ä¸ºç©º
  if (!templateName.value || templateName.value.trim() === '') {
    ElMessage({
      message: '请输入模板名称',
      type: 'warning',
    });
    return;
  }
  const templateName = ref("");
  const filterInputValue = ref("");
  const templateList = ref([]);
  const isTemplateNameDuplicate = ref(false); // æ ‡è®°æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
  // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
  const isDuplicate = checkTemplateNameDuplicate(templateName.value);
  if (isDuplicate) {
    ElMessage({
      message: '模板名称已存在,请更换模板名称',
      type: 'warning',
    });
    return;
  }
  // æ£€æŸ¥ä¾›åº”商是否选择
  if (!form.value.supplierId) {
    ElMessage({
      message: '请先选择供应商',
      type: 'warning',
    });
    return;
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰äº§å“æ•°æ®
  // if (!productData.value || productData.value.length === 0) {
  //   ElMessage({
  //     message: '请先添加产品信息',
  //     type: 'warning',
  //   });
  //   return;
  // }
  try {
    let params = {
      productData: proxy.HaveJson(productData.value),
      supplierId: form.value.supplierId,
      paymentMethod: form.value.paymentMethod,
      recorderId: form.value.recorderId,
      approverId: form.value.approverId,
      templateName: templateName.value.trim()
    };
    console.log(params);
    let res = await addPurchaseTemplate(params);
    if (res && res.code === 200) {
      ElMessage({
        message: '模板保存成功',
        type: 'success',
      });
      // ä¿å­˜æˆåŠŸåŽé‡æ–°èŽ·å–æ¨¡æ¿åˆ—è¡¨
      await getTemplateList();
      // æ¸…空模板名称输入
      templateName.value = '';
      filterInputValue.value = '';
  const checkTemplateNameDuplicate = name => {
    if (!name || name.trim() === "") {
      isTemplateNameDuplicate.value = false;
      return false;
    }
    const isDuplicate = templateList.value.some(
      item => item.templateName === name.trim()
    );
    isTemplateNameDuplicate.value = isDuplicate;
    return isDuplicate;
  };
  // é˜²æŠ–定时器
  let duplicateCheckTimer = null;
  const onTemplateFilterChange = val => {
    filterInputValue.value = val ?? "";
    // æ¸…除之前的定时器
    if (duplicateCheckTimer) {
      clearTimeout(duplicateCheckTimer);
    }
    // å®žæ—¶æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤ï¼ˆé˜²æŠ–处理,避免频繁提示)
    if (val && val.trim()) {
      duplicateCheckTimer = setTimeout(() => {
        const isDuplicate = checkTemplateNameDuplicate(val);
        if (isDuplicate) {
          ElMessage({
            message: "模板名称已存在,请更换模板名称",
            type: "warning",
            duration: 2000,
          });
        }
      }, 300); // 300ms é˜²æŠ–
    } else {
      isTemplateNameDuplicate.value = false;
    }
  };
  // allow-create æ—¶ï¼Œè¾“入不存在的内容会作为 string å€¼è¿”回;这里同步回输入框以确保文字不丢
  const onTemplateChange = async val => {
    if (typeof val === "string") {
      filterInputValue.value = val;
      // é€‰æ‹©æˆ–输入时检查重复
      checkTemplateNameDuplicate(val);
    }
    // è¿‡æ»¤æ•°æ®ï¼ŒæŸ¥æ‰¾åŒ¹é…çš„æ¨¡æ¿
    const matchedTemplate = templateList.value.find(
      item => item.templateName === val
    );
    if (matchedTemplate?.id) {
      // å¦‚果找到模板,加载模板数据
      form.value = {
        ...form.value,
        ...matchedTemplate,
      };
      productData.value = matchedTemplate.productData || [];
      // ç”Ÿæˆæ–°çš„采购合同号
      try {
        const res = await createPurchaseNo();
        if (res?.data) {
          form.value.purchaseContractNumber = res.data;
        }
      } catch (error) {
        console.error("生成采购合同号失败:", error);
      }
    } else {
      // å¦‚果没有找到模板,重置表单(保持当前表单状态)
      const currentFormData = { ...form.value };
      const currentProductData = [...productData.value];
      // å¦‚果对话框未打开,先打开
      if (!dialogFormVisible.value) {
        operationType.value = "add";
        dialogFormVisible.value = true;
      }
      // ç­‰å¾…下一个 tick åŽæ¢å¤æ•°æ®
      await nextTick();
      form.value = {
        ...form.value,
        ...currentFormData,
      };
      productData.value = currentProductData;
    }
  };
  // ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
  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: "",
      isChecked: true,
    },
    rules: {
      purchaseContractNumber: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      approverId: [
        { required: true, message: "请选择审批人", trigger: "change" },
      ],
      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,
    // è®¾ç½®å½•入日期范围为当天
    entryDate: [
      dayjs().startOf("day").format("YYYY-MM-DD"),
      dayjs().endOf("day").format("YYYY-MM-DD"),
    ],
    entryDateStart: dayjs().startOf("day").format("YYYY-MM-DD"),
    entryDateEnd: dayjs().endOf("day").format("YYYY-MM-DD"),
  });
  // äº§å“è¡¨å•弹框数据
  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: "",
      isChecked: true,
    },
    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: true, message: "请选择", trigger: "change" }],
      taxInclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      taxExclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
      isChecked: [{ 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 handleButtonClick = async () => {
    // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦ä¸ºç©º
    if (!templateName.value || templateName.value.trim() === "") {
      ElMessage({
        message: res?.msg || '模板保存失败',
        type: 'error',
        message: "请输入模板名称",
        type: "warning",
      });
      return;
    }
    // æ£€æŸ¥æ¨¡æ¿åç§°æ˜¯å¦é‡å¤
    const isDuplicate = checkTemplateNameDuplicate(templateName.value);
    if (isDuplicate) {
      ElMessage({
        message: "模板名称已存在,请更换模板名称",
        type: "warning",
      });
      return;
    }
    // æ£€æŸ¥ä¾›åº”商是否选择
    if (!form.value.supplierId) {
      ElMessage({
        message: "请先选择供应商",
        type: "warning",
      });
      return;
    }
    // æ£€æŸ¥æ˜¯å¦æœ‰äº§å“æ•°æ®
    // if (!productData.value || productData.value.length === 0) {
    //   ElMessage({
    //     message: '请先添加产品信息',
    //     type: 'warning',
    //   });
    //   return;
    // }
    try {
      let params = {
        productData: proxy.HaveJson(productData.value),
        supplierId: form.value.supplierId,
        paymentMethod: form.value.paymentMethod,
        recorderId: form.value.recorderId,
        approverId: form.value.approverId,
        templateName: templateName.value.trim(),
      };
      console.log(params);
      let res = await addPurchaseTemplate(params);
      if (res && res.code === 200) {
        ElMessage({
          message: "模板保存成功",
          type: "success",
        });
        // ä¿å­˜æˆåŠŸåŽé‡æ–°èŽ·å–æ¨¡æ¿åˆ—è¡¨
        await getTemplateList();
        // æ¸…空模板名称输入
        templateName.value = "";
        filterInputValue.value = "";
        isTemplateNameDuplicate.value = false;
      } else {
        ElMessage({
          message: res?.msg || "模板保存失败",
          type: "error",
        });
      }
    } catch (error) {
      console.error("保存模板失败:", error);
      ElMessage({
        message: "模板保存失败,请稍后重试",
        type: "error",
      });
    }
  } catch (error) {
    console.error('保存模板失败:', error);
    ElMessage({
      message: '模板保存失败,请稍后重试',
      type: 'error',
    });
  }
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeChildrenTable = param => {
    return proxy.summarizeTable(
      param,
      [
        "taxInclusiveUnitPrice",
@@ -986,27 +1222,26 @@
        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) => {
    );
  };
  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
          isInvalid: record.isWhite === 1,
        }));
        // åˆå§‹åŒ–子数据数组
        tableData.value.forEach((item) => {
        tableData.value.forEach(item => {
          item.children = [];
        });
        total.value = res.data.total;
@@ -1015,547 +1250,758 @@
      .catch(() => {
        tableLoading.value = false;
      });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const productSelected = (selectedRows) => {
  productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = async (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      const res = await productList({ salesLedgerId: row.id, type: 2 });
      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.error('加载产品列表失败:', error);
      proxy.$modal.msgError('加载产品列表失败');
      // å±•开失败时,移除展开状态
      const index = expandedRows.findIndex(item => item.id === row.id);
      if (index > -1) {
        expandedRows.splice(index, 1);
      }
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ["contractAmount"]);
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeProTable = (param) => {
  return proxy.summarizeTable(param, [
    "taxInclusiveUnitPrice",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// æ‰“开弹框
const openForm = async (type, row) => {
  await getTemplateList()
  operationType.value = type;
  form.value = {};
  productData.value = [];
  fileList.value = [];
  templateName.value = '';
  filterInputValue.value = '';
  isTemplateNameDuplicate.value = false;
  try {
    // å¹¶è¡ŒåŠ è½½åŸºç¡€æ•°æ®
    const [userRes, salesRes, supplierRes] = await Promise.all([
      userListNoPage(),
      getSalesNo(),
      getOptions()
    ]);
    userList.value = userRes.data || [];
    salesContractList.value = salesRes || [];
    // ä¾›åº”商过滤出isWhite=0 çš„æ•°æ®
    supplierList.value = (supplierRes.data || []).filter((item) => item.isWhite === 0);
    // è®¾ç½®é»˜è®¤å€¼
    form.value.recorderId = userStore.id;
    form.value.entryDate = getCurrentDate();
    if (type === "add") {
      // æ–°å¢žæ—¶ç”Ÿæˆé‡‡è´­åˆåŒå·
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const productSelected = selectedRows => {
    productSelectedRows.value = selectedRows;
  };
  const expandedRowKeys = ref([]);
  // å±•开行
  const expandChange = async (row, expandedRows) => {
    if (expandedRows.length > 0) {
      expandedRowKeys.value = [];
      try {
        const purchaseNoRes = await createPurchaseNo();
        if (purchaseNoRes?.data) {
          form.value.purchaseContractNumber = purchaseNoRes.data;
        const res = await productList({ salesLedgerId: row.id, type: 2 });
        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.error('生成采购合同号失败:', error);
        proxy.$modal.msgWarning('生成采购合同号失败');
        console.error("加载产品列表失败:", error);
        proxy.$modal.msgError("加载产品列表失败");
        // å±•开失败时,移除展开状态
        const index = expandedRows.findIndex(item => item.id === row.id);
        if (index > -1) {
          expandedRows.splice(index, 1);
        }
      }
    } else if (type === "edit" && row?.id) {
      // ç¼–辑时加载数据
      currentId.value = row.id;
      try {
        const purchaseRes = await getPurchaseById({ id: row.id, type: 2 });
        form.value = { ...purchaseRes };
        productData.value = purchaseRes.productData || [];
        fileList.value = purchaseRes.salesLedgerFiles || [];
      } catch (error) {
        console.error('加载采购台账数据失败:', error);
        proxy.$modal.msgError('加载数据失败');
        return;
      }
    }
    dialogFormVisible.value = true;
  } catch (error) {
    console.error('打开表单失败:', error);
    proxy.$modal.msgError('加载基础数据失败');
  }
};
// ä¸Šä¼ å‰æ ¡æ£€
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);
  }
}
// ç§»é™¤æ–‡ä»¶
async function handleRemove(file) {
  if (!file?.id) {
    return;
  }
  if (file.size > 1024 * 1024 * 10) {
    // ä»…前端清理,不调用删除接口和提示
    return;
  }
  if (operationType.value === "edit" && file.id) {
    try {
      await delLedgerFile([file.id]);
      proxy.$modal.msgSuccess("删除成功");
    } catch (error) {
      console.error('删除文件失败:', error);
      proxy.$modal.msgError("删除文件失败");
    }
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    const valid = await proxy.$refs["formRef"].validate().catch(() => false);
    if (!valid) {
      return;
    }
    if (!productData.value || productData.value.length === 0) {
      proxy.$modal.msgWarning("请添加产品信息");
      return;
    }
    form.value.productData = proxy.HaveJson(productData.value);
    form.value.tempFileIds = fileList.value
      .filter(item => item.tempId)
      .map((item) => item.tempId);
    form.value.type = 2;
    try {
      await addOrEditPurchase(form.value);
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
      getList();
    } catch (error) {
      console.error('提交表单失败:', error);
      proxy.$modal.msgError("提交失败,请稍后重试");
    }
  } catch (error) {
    console.error('表单验证失败:', error);
  }
};
// å…³é—­å¼¹æ¡†
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 = async () => {
  try {
    const res = await productTreeList();
    productOptions.value = convertIdToValue(res);
  } catch (error) {
    console.error('加载产品选项失败:', error);
    proxy.$modal.msgError('加载产品选项失败');
  }
};
const getModels = async (value) => {
  if (value) {
    productForm.value.productCategory = findNodeById(productOptions.value, value) || "";
    try {
      const res = await modelList({ id: value });
      modelOptions.value = res || [];
    } catch (error) {
      console.error('加载规格型号失败:', error);
      proxy.$modal.msgError('加载规格型号失败');
      modelOptions.value = [];
    }
  } 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 = async () => {
  try {
    const valid = await proxy.$refs["productFormRef"].validate().catch(() => false);
    if (!valid) {
      return;
    }
    if (operationType.value === "edit") {
      await submitProductEdit();
    } else {
      if (productOperationType.value === "add") {
        productData.value.push({ ...productForm.value });
      } else {
        productData.value[productOperationIndex.value] = {
          ...productForm.value,
        };
      }
      closeProductDia();
      expandedRowKeys.value = [];
    }
  } catch (error) {
    console.error('提交产品表单失败:', error);
  }
};
const submitProductEdit = async () => {
  try {
    productForm.value.salesLedgerId = currentId.value;
    productForm.value.type = 2;
    await addOrUpdateSalesLedgerProduct(productForm.value);
    proxy.$modal.msgSuccess("提交成功");
    closeProductDia();
    // é‡æ–°åŠ è½½äº§å“æ•°æ®
  };
  // ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["contractAmount"]);
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeProTable = param => {
    return proxy.summarizeTable(param, [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ]);
  };
  // æ‰“开弹框
  const openForm = async (type, row) => {
    await getTemplateList();
    operationType.value = type;
    form.value = {};
    productData.value = [];
    fileList.value = [];
    templateName.value = "";
    filterInputValue.value = "";
    isTemplateNameDuplicate.value = false;
    try {
      const res = await getPurchaseById({ id: currentId.value, type: 2 });
      productData.value = res.productData || [];
    } catch (error) {
      console.error('重新加载产品数据失败:', error);
    }
  } catch (error) {
    console.error('提交产品编辑失败:', error);
    proxy.$modal.msgError("提交失败,请稍后重试");
  }
};
// åˆ é™¤äº§å“
const deleteProduct = async () => {
  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
      // å¹¶è¡ŒåŠ è½½åŸºç¡€æ•°æ®
      const [userRes, salesRes, supplierRes] = await Promise.all([
        userListNoPage(),
        getSalesNo(),
        getOptions(),
      ]);
      userList.value = userRes.data || [];
      salesContractList.value = salesRes || [];
      // ä¾›åº”商过滤出isWhite=0 çš„æ•°æ®
      supplierList.value = (supplierRes.data || []).filter(
        item => item.isWhite === 0
      );
      if (index !== -1) {
        productData.value.splice(index, 1);
      // è®¾ç½®é»˜è®¤å€¼
      form.value.recorderId = userStore.id;
      form.value.entryDate = getCurrentDate();
      if (type === "add") {
        // æ–°å¢žæ—¶ç”Ÿæˆé‡‡è´­åˆåŒå·
        try {
          const purchaseNoRes = await createPurchaseNo();
          if (purchaseNoRes?.data) {
            form.value.purchaseContractNumber = purchaseNoRes.data;
          }
        } catch (error) {
          console.error("生成采购合同号失败:", error);
          proxy.$modal.msgWarning("生成采购合同号失败");
        }
      } else if (type === "edit" && row?.id) {
        // ç¼–辑时加载数据
        currentId.value = row.id;
        try {
          const purchaseRes = await getPurchaseById({ id: row.id, type: 2 });
          form.value = { ...purchaseRes };
          productData.value = purchaseRes.productData || [];
          fileList.value = purchaseRes.salesLedgerFiles || [];
        } catch (error) {
          console.error("加载采购台账数据失败:", error);
          proxy.$modal.msgError("加载数据失败");
          return;
        }
      }
      if (form.value.salesLedgerId == -1) {
        form.value.salesLedgerId = null;
      }
      console.log(form.value, "form.value===========");
      dialogFormVisible.value = true;
    } catch (error) {
      console.error("打开表单失败:", error);
      proxy.$modal.msgError("加载基础数据失败");
    }
  };
  // ä¸Šä¼ å‰æ ¡æ£€
  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);
    }
  }
  // ç§»é™¤æ–‡ä»¶
  async function handleRemove(file) {
    if (!file?.id) {
      return;
    }
    console.log("handleRemove", file.id);
    if (file.size > 1024 * 1024 * 10) {
      // ä»…前端清理,不调用删除接口和提示
      return;
    }
    if (operationType.value === "edit" && file.id) {
      try {
        await delLedgerFile([file.id]);
        proxy.$modal.msgSuccess("删除成功");
      } catch (error) {
        console.error("删除文件失败:", error);
        proxy.$modal.msgError("删除文件失败");
      }
    }
  }
  // æäº¤è¡¨å•
  const submitForm = () => {
    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;
        // å¦‚æžœsalesLedgerId为空,则不传递salesContractNo
        if (!form.value.salesLedgerId) {
          form.value.salesContractNo = "";
        }
        addOrEditPurchase(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
          getList();
        });
      }
    });
    proxy.$modal.msgSuccess("删除成功");
  } else {
    // ç¼–辑模式下,需要调用接口删除
    const ids = productSelectedRows.value
      .filter(item => item.id)
      .map((item) => item.id);
    if (ids.length === 0) {
      proxy.$modal.msgWarning("请选择有效的数据");
  };
  // å…³é—­å¼¹æ¡†
  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;
    }
    try {
      await ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除确认", {
    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 handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/purchase/ledger/export", {}, "采购台账.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
      await delProduct(ids);
      proxy.$modal.msgSuccess("删除成功");
      closeProductDia();
      // é‡æ–°åŠ è½½äº§å“æ•°æ®
      try {
        const res = await getSalesLedgerWithProducts({ id: currentId.value, type: 2 });
        productData.value = res.productData || [];
      } catch (error) {
        console.error('重新加载产品数据失败:', error);
      }
    } catch (error) {
      if (error !== 'cancel') {
        console.error('删除产品失败:', error);
        proxy.$modal.msgError("删除失败,请稍后重试");
      }
    }
  }
};
// å…³é—­äº§å“å¼¹æ¡†
const closeProductDia = () => {
  proxy.resetForm("productFormRef");
  productFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = async () => {
  try {
    await ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出确认", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    });
    proxy.download("/purchase/ledger/export", {}, "采购台账.xlsx");
  } catch (error) {
    if (error !== 'cancel') {
      console.error('导出失败:', error);
      proxy.$modal.msgError("导出失败,请稍后重试");
    }
  }
};
// åˆ é™¤
const handleDelete = async () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
  const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName);
  if (unauthorizedData.length > 0) {
    proxy.$modal.msgWarning("不可删除他人维护的数据");
    return;
  }
  const ids = selectedRows.value
    .filter(item => item.id)
    .map((item) => item.id);
  if (ids.length === 0) {
    proxy.$modal.msgWarning("请选择有效的数据");
    return;
  }
  try {
    await ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除确认", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    });
    await delPurchase(ids);
    proxy.$modal.msgSuccess("删除成功");
    getList();
  } catch (error) {
    if (error !== 'cancel') {
      console.error('删除失败:', error);
      proxy.$modal.msgError("删除失败,请稍后重试");
    }
  }
};
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
  };
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
      const unauthorizedData = selectedRows.value.filter(
        item => item.recorderName !== userStore.nickName
      );
  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);
      if (unauthorizedData.length > 0) {
        proxy.$modal.msgWarning("不可删除他人维护的数据");
        return;
      }
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œå«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
          (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delPurchase(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .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}`;
  }
  const mathNum = () => {
    if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
    }
    // åç®—不含税总价
    productForm.value.taxExclusiveTotalPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2);
  } else if (field === 'taxExclusiveTotalPrice') {
    // åç®—含税总价
    if (!productForm.value.taxInclusiveUnitPrice) {
      return;
    }
    if (!productForm.value.quantity) {
      return;
    }
    // å«ç¨Žæ€»ä»·è®¡ç®—
    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);
      proxy.calculateTaxIncludeTotalPrice(
        productForm.value.taxInclusiveUnitPrice,
        productForm.value.quantity
      );
    if (productForm.value.taxRate) {
      // ä¸å«ç¨Žæ€»ä»·è®¡ç®—
      productForm.value.taxExclusiveTotalPrice =
        proxy.calculateTaxExclusiveTotalPrice(
          productForm.value.taxInclusiveTotalPrice,
          productForm.value.taxRate
        );
    }
    // å·²çŸ¥å«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
          (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
  };
  const reverseMathNum = field => {
    if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
    }
  }
};
// é”€å”®åˆåŒé€‰æ‹©æ”¹å˜æ–¹æ³•
const salesLedgerChange = async (row) => {
  const index = salesContractList.value.findIndex((item) => item.id === row);
  if (index > -1) {
    form.value.projectName = salesContractList.value[index].projectName;
    await querygProductInfoByContractNo();
  }
};
    const taxRate = Number(productForm.value.taxRate);
    if (!taxRate) return;
const querygProductInfoByContractNo = async () => {
  const { code, data } = await getProductInfoByContractNo({
    contractNo: form.value.salesLedgerId,
    // ç¡®ä¿è¾“入值不为负数
    if (
      field === "taxInclusiveTotalPrice" ||
      field === "taxExclusiveTotalPrice"
    ) {
      const value = Number(productForm.value[field]);
      if (value < 0) {
        productForm.value[field] = "0";
        proxy.$modal.msgWarning("值不能小于0");
        return;
      }
    }
    if (field === "taxInclusiveTotalPrice") {
      // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼Œåç®—含税单价
      if (productForm.value.quantity) {
        productForm.value.taxInclusiveUnitPrice = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
        }
      }
      // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œå«ç¨Žå•价,反算数量
      else if (productForm.value.taxInclusiveUnitPrice) {
        productForm.value.quantity = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.taxInclusiveUnitPrice)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.quantity) < 0) {
          productForm.value.quantity = "0";
        }
      }
      // åç®—不含税总价
      productForm.value.taxExclusiveTotalPrice = (
        Number(productForm.value.taxInclusiveTotalPrice) /
        (1 + taxRate / 100)
      ).toFixed(2);
      // ç¡®ä¿ç»“果不为负数
      if (Number(productForm.value.taxExclusiveTotalPrice) < 0) {
        productForm.value.taxExclusiveTotalPrice = "0";
      }
    } else if (field === "taxExclusiveTotalPrice") {
      // åç®—含税总价
      productForm.value.taxInclusiveTotalPrice = (
        Number(productForm.value.taxExclusiveTotalPrice) *
        (1 + taxRate / 100)
      ).toFixed(2);
      // ç¡®ä¿ç»“果不为负数
      if (Number(productForm.value.taxInclusiveTotalPrice) < 0) {
        productForm.value.taxInclusiveTotalPrice = "0";
      }
      // å·²çŸ¥æ•°é‡ï¼Œåç®—含税单价
      if (productForm.value.quantity) {
        productForm.value.taxInclusiveUnitPrice = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
        }
      }
      // å·²çŸ¥å«ç¨Žå•价,反算数量
      else if (productForm.value.taxInclusiveUnitPrice) {
        productForm.value.quantity = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.taxInclusiveUnitPrice)
        ).toFixed(2);
        // ç¡®ä¿ç»“果不为负数
        if (Number(productForm.value.quantity) < 0) {
          productForm.value.quantity = "0";
        }
      }
    }
  };
  // é”€å”®åˆåŒé€‰æ‹©æ”¹å˜æ–¹æ³•
  const salesLedgerChange = async row => {
    console.log("row", row);
    var index = salesContractList.value.findIndex(item => item.id == row);
    console.log("index", index);
    if (index > -1) {
      await querygProductInfoByContractNo();
    }
  };
  const querygProductInfoByContractNo = async () => {
    const { code, data } = await getProductInfoByContractNo({
      contractNo: form.value.salesLedgerId,
    });
    if (code == 200) {
      productData.value = data;
    }
  };
  const fileListRef = ref(null);
  const downLoadFile = row => {
    fileListRef.value.open(row.salesLedgerFiles);
  };
  // æ˜¾ç¤ºäºŒç»´ç 
  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: "",
  });
  if (code == 200) {
    productData.value = data;
  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 >= 2) {
      scanAddForm.purchaseContractNumber = parts[0] || "";
      scanAddForm.supplierName = parts[1] || "";
      scanAddForm.contractAmount = parts[2] || "";
      scanAddForm.paymentMethod = parts[3] || "";
      scanAddForm.projectName = parts[4] || "";
      // 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' : '';
};
  // æ·»åŠ è¡Œç±»åæ–¹æ³•
  const tableRowClassName = ({ row }) => {
    return row.isInvalid ? "invalid-row" : "";
  };
// èŽ·å–æ¨¡æ¿ä¿¡æ¯
const getTemplateList =async ()=>{
  let res = await getPurchaseTemplateList()
  if(res && res.code===200 && Array.isArray(res.data)){
    templateList.value = res.data
  }
}
  // èŽ·å–æ¨¡æ¿ä¿¡æ¯
  const getTemplateList = async () => {
    let res = await getPurchaseTemplateList();
    if (res && res.code === 200 && Array.isArray(res.data)) {
      templateList.value = res.data;
    }
  };
onMounted(() => {
  getList();
  getTemplateList();
});
  onMounted(() => {
    getList();
    getTemplateList();
  });
</script>
<style scoped lang="scss">
.invalid-row {
  opacity: 0.6;
  background-color: #f5f7fa;
}
.el-row{
  justify-content: space-between;
  align-items: center
}
.no-arrow-select {
  --el-select-suffix-icon-color: transparent; /* éšè—é»˜è®¤ä¸‹æ‹‰ç®­å¤´ */
}
.select-button-group {
  display: flex;
  align-items: center;
}
  .invalid-row {
    opacity: 0.6;
    background-color: #f5f7fa;
  }
  .el-row {
    justify-content: space-between;
    align-items: center;
  }
  .no-arrow-select {
    --el-select-suffix-icon-color: transparent; /* éšè—é»˜è®¤ä¸‹æ‹‰ç®­å¤´ */
  }
  .select-button-group {
    display: flex;
    align-items: center;
  }
</style>
src/views/productionManagement/productionCosting/index.vue
@@ -181,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/formDia.vue
@@ -111,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";
src/views/productionManagement/productionOrder/index.vue
@@ -133,7 +133,7 @@
    {
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: '140px',
      width: '200px',
    },
    {
      label: "需求数量",
src/views/productionManagement/productionReporting/components/formDia.vue
@@ -94,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()
src/views/productionManagement/productionReporting/index.vue
@@ -19,21 +19,6 @@
                    style="width: 200px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="工单状态:">
          <el-select v-model="searchForm.workOrderStatus"
                     placeholder="请选择工单状态"
                     style="width: 140px"
                     clearable>
            <el-option label="待确认"
                       :value="1"></el-option>
            <el-option label="待生产"
                       :value="2"></el-option>
            <el-option label="生产中"
                       :value="3"></el-option>
            <el-option label="已生产"
                       :value="4"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
@@ -416,7 +401,7 @@
      type: "warning",
    })
      .then(() => {
        proxy.download("/salesLedger/work/export", {}, "生产报工.xlsx");
        proxy.download("/productionProductMain/export", {}, "生产报工.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
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
@@ -27,6 +27,24 @@
              <el-input v-model="form.model" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select
                v-model="form.testStandardId"
                placeholder="请选择指标"
                clearable
                @change="handleTestStandardChange"
                style="width: 100%"
              >
                <el-option
                  v-for="item in testStandardOptions"
                  :key="item.id"
                  :label="item.standardName || item.standardNo"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
@@ -101,12 +119,12 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {userListNoPage} from "@/api/system/user.js";
import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -121,6 +139,7 @@
    productName: "",
    productId: "",
    model: "",
    testStandardId: "",
    unit: "",
    quantity: "",
    checkCompany: "",
@@ -132,6 +151,7 @@
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: false, message: "请输入", trigger: "blur" }],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
@@ -169,6 +189,7 @@
const tableLoading = ref(false);
const userList = ref([]);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // æŒ‡æ ‡é€‰æ‹©ä¸‹æ‹‰æ¡†æ•°æ®
// æ‰“开弹框
const openDialog = async (type, row) => {
@@ -180,11 +201,54 @@
    let userLists = await userListNoPage();
    userList.value = userLists.data;
    form.value = {}
  testStandardOptions.value = [];
  tableData.value = [];
  getProductOptions();
  if (operationType.value === 'edit') {
    form.value = {...row}
    // å…ˆä¿å­˜ testStandardId,避免被清空
    const savedTestStandardId = row.testStandardId;
    // å…ˆè®¾ç½®è¡¨å•数据,但暂时清空 testStandardId,等选项加载完成后再设置
    form.value = {...row, testStandardId: ''}
        currentProductId.value = row.productId || 0
        getQualityInspectParamList(row.id)
        // ç¼–辑模式下,先加载指标选项,然后加载参数列表
        if (currentProductId.value) {
            // å…ˆåŠ è½½æŒ‡æ ‡é€‰é¡¹
            let params = {
                productId: currentProductId.value,
                inspectType: 2
            }
            qualityInspectDetailByProductId(params).then(res => {
                testStandardOptions.value = res.data || [];
                // ä½¿ç”¨ nextTick å’Œ setTimeout ç¡®ä¿é€‰é¡¹å·²ç»æ¸²æŸ“到 DOM
                nextTick(() => {
                    setTimeout(() => {
                        // å¦‚果编辑数据中有 testStandardId,则设置并加载对应的参数
                        if (savedTestStandardId) {
                            // ç¡®ä¿ç±»åž‹åŒ¹é…ï¼ˆitem.id å¯èƒ½æ˜¯æ•°å­—或字符串)
                            const matchedOption = testStandardOptions.value.find(item =>
                                item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
                            );
                            if (matchedOption) {
                                // ç¡®ä¿ä½¿ç”¨åŒ¹é…é¡¹çš„ id(保持类型一致)
                                form.value.testStandardId = matchedOption.id;
                                // ç¼–辑场景保留已有检验值,直接拉取原参数数据
                                getQualityInspectParamList(row.id);
                            } else {
                                // å¦‚果找不到匹配项,尝试直接使用原值
                                console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                                form.value.testStandardId = savedTestStandardId;
                                getQualityInspectParamList(row.id);
                            }
                        } else {
                            // å¦åˆ™ä½¿ç”¨æ—§çš„逻辑
                            getQualityInspectParamList(row.id);
                        }
                    }, 100);
                });
            });
        } else {
            getQualityInspectParamList(row.id);
        }
  }
}
const getProductOptions = () => {
@@ -195,7 +259,7 @@
const getModels = (value) => {
    currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
    if (currentProductId) {
    if (currentProductId.value) {
        getList();
    }
};
@@ -253,9 +317,40 @@
  })
}
const getList = () => {
    qualityInspectDetailByProductId(currentProductId.value).then(res => {
        tableData.value = res.data;
  if (!currentProductId.value) {
    testStandardOptions.value = [];
    tableData.value = [];
    return;
  }
  let params = {
    productId: currentProductId.value,
    inspectType: 2
  }
    qualityInspectDetailByProductId(params).then(res => {
        // ä¿å­˜ä¸‹æ‹‰æ¡†é€‰é¡¹æ•°æ®
        testStandardOptions.value = res.data || [];
        // æ¸…空表格数据,等待用户选择指标
        tableData.value = [];
        // æ¸…空指标选择
        form.value.testStandardId = '';
    })
}
// æŒ‡æ ‡é€‰æ‹©å˜åŒ–处理
const handleTestStandardChange = (testStandardId) => {
  if (!testStandardId) {
    tableData.value = [];
    return;
  }
  tableLoading.value = true;
  getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
    tableData.value = res.data || [];
  }).catch(error => {
    console.error('获取标准参数失败:', error);
    tableData.value = [];
  }).finally(() => {
    tableLoading.value = false;
  })
}
const getQualityInspectParamList = (id) => {
    qualityInspectParamInfo(id).then(res => {
@@ -265,6 +360,9 @@
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  tableData.value = [];
  testStandardOptions.value = [];
  form.value.testStandardId = '';
  dialogFormVisible.value = false;
  emit('close')
};
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/metricBinding/index.vue
@@ -10,6 +10,7 @@
        :isSelection="false"
        :rowClassName="rowClassNameCenter"
        :tableLoading="tableLoading"
        :rowClick="handleTableRowClick"
        @pagination="handlePagination"
        :total="page.total"
      >
@@ -197,12 +198,13 @@
}
const standardColumns = ref([
  { label: '标准编号', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, headerSlot: 'standardNoHeader' },
  { label: '标准名称', prop: 'standardName', minWidth: 180, headerSlot: 'standardNameHeader' },
  { 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: '出厂检验' }
@@ -212,6 +214,7 @@
  {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
@@ -223,7 +226,8 @@
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160
    minWidth: 160,
    align: 'center'
  }
  // {
  //   label: '状态',
@@ -304,6 +308,13 @@
    })
}
// è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§ç»‘å®šåˆ—è¡¨ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
@@ -471,4 +482,23 @@
: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/index.vue
@@ -19,6 +19,7 @@
        :isSelection="true"
        :tableLoading="tableLoading"
        :rowClassName="rowClassNameCenter"
        :rowClick="handleTableRowClick"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
        :total="page.total"
@@ -212,7 +213,7 @@
    standardNo: [{ required: true, message: '请输入标准编号', trigger: 'blur' }],
    standardName: [{ required: true, message: '请输入标准名称', trigger: 'blur' }],
    inspectType: [{ required: true, message: '请选择检测类型', trigger: 'change' }],
    processId: [{ required: true, message: '请选择工序', trigger: 'change' }]
    processId: [{ required: false, message: '请选择工序', trigger: 'change' }]
  }
})
@@ -277,18 +278,21 @@
    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 = {
@@ -302,6 +306,7 @@
  {
    label: '工序',
    prop: 'processId',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
@@ -314,6 +319,7 @@
    label: '状态',
    prop: 'state',
    headerSlot: 'stateHeader',
    align: 'center',
    dataType: 'tag',
    formatData: (val) => {
      const map = {
@@ -332,7 +338,8 @@
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160
    minWidth: 160,
    align: 'center'
  },
  {
    dataType: 'action',
@@ -447,7 +454,13 @@
  getStandardList()
}
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§å‚æ•°
// è¡¨æ ¼è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§å‚æ•°
const handleTableRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
}
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§å‚æ•°ï¼ˆä¿ç•™ç”¨äºŽæ ‡å‡†ç¼–å·åˆ—çš„ç‚¹å‡»ï¼‰
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
@@ -567,14 +580,23 @@
      processId: ''
    })
  } else if (type === 'edit' && row) {
    Object.assign(standardForm.value, 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'
      state: '0',
      // ç¡®ä¿ inspectType è½¬æ¢ä¸ºå­—符串
      inspectType: rest.inspectType !== null && rest.inspectType !== undefined ? String(rest.inspectType) : ''
    })
  }
  standardDialogVisible.value = true
@@ -673,14 +695,41 @@
.metric-maintenance {
  display: flex;
  gap: 16px;
  min-width: 0; /* å…è®¸ flex å­å…ƒç´ æ”¶ç¼© */
}
.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 {
@@ -688,6 +737,8 @@
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  flex-wrap: wrap;
  gap: 8px;
}
.toolbar-left {
@@ -699,6 +750,9 @@
.toolbar-right {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.search-label {
@@ -758,4 +812,23 @@
: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/processInspection/components/formDia.vue
@@ -10,7 +10,7 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="工序:" prop="process">
              <el-input v-model="form.process" placeholder="请输入" clearable/>
              <el-input v-model="form.process" placeholder="请输入工序" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -32,6 +32,24 @@
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="model">
              <el-input v-model="form.model" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select
                v-model="form.testStandardId"
                placeholder="请选择指标"
                clearable
                @change="handleTestStandardChange"
                style="width: 100%"
              >
                <el-option
                  v-for="item in testStandardOptions"
                  :key="item.id"
                  :label="item.standardName || item.standardNo"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -108,11 +126,11 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {userListNoPage} from "@/api/system/user.js";
import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
const { proxy } = getCurrentInstance()
@@ -128,6 +146,7 @@
    productName: "",
    productId: "",
    model: "",
    testStandardId: "",
    unit: "",
    quantity: "",
    checkCompany: "",
@@ -135,10 +154,11 @@
  },
  rules: {
    checkTime: [{ required: true, message: "请输入", trigger: "blur" },],
    process: [{ required: true, message: "请输入", trigger: "blur" }],
    process: [{ required: true, message: "请输入工序", trigger: "blur" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: false, message: "请输入", trigger: "blur" }],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
@@ -176,6 +196,7 @@
const tableData = ref([]);
const tableLoading = ref(false);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // æŒ‡æ ‡é€‰æ‹©ä¸‹æ‹‰æ¡†æ•°æ®
// æ‰“开弹框
const openDialog = async (type, row) => {
@@ -187,11 +208,55 @@
    let userLists = await userListNoPage();
    userList.value = userLists.data;
    form.value = {}
    testStandardOptions.value = [];
    tableData.value = [];
    getProductOptions();
    if (operationType.value === 'edit') {
        form.value = {...row}
        // å…ˆä¿å­˜ testStandardId,避免被清空
        const savedTestStandardId = row.testStandardId;
        // å…ˆè®¾ç½®è¡¨å•数据,但暂时清空 testStandardId,等选项加载完成后再设置
        form.value = {...row, testStandardId: ''}
        currentProductId.value = row.productId || 0
        getQualityInspectParamList(row.id)
        // ç¼–辑模式下,先加载指标选项,然后加载参数列表
        if (currentProductId.value) {
            // å…ˆåŠ è½½æŒ‡æ ‡é€‰é¡¹
            let params = {
                productId: currentProductId.value,
                inspectType: 1,
                process: form.value.process || ''
            }
            qualityInspectDetailByProductId(params).then(res => {
                testStandardOptions.value = res.data || [];
                // ä½¿ç”¨ nextTick å’Œ setTimeout ç¡®ä¿é€‰é¡¹å·²ç»æ¸²æŸ“到 DOM
                nextTick(() => {
                    setTimeout(() => {
                        // å¦‚果编辑数据中有 testStandardId,则设置并加载对应的参数
                        if (savedTestStandardId) {
                            // ç¡®ä¿ç±»åž‹åŒ¹é…ï¼ˆitem.id å¯èƒ½æ˜¯æ•°å­—或字符串)
                            const matchedOption = testStandardOptions.value.find(item =>
                                item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
                            );
                            if (matchedOption) {
                                // ç¡®ä¿ä½¿ç”¨åŒ¹é…é¡¹çš„ id(保持类型一致)
                                form.value.testStandardId = matchedOption.id;
                                // ç¼–辑保留原检验值,直接拉取原参数数据
                                getQualityInspectParamList(row.id);
                            } else {
                                // å¦‚果找不到匹配项,尝试直接使用原值
                                console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                                form.value.testStandardId = savedTestStandardId;
                                getQualityInspectParamList(row.id);
                            }
                        } else {
                            // å¦åˆ™ä½¿ç”¨æ—§çš„逻辑
                            getQualityInspectParamList(row.id);
                        }
                    }, 100);
                });
            });
        } else {
            getQualityInspectParamList(row.id);
        }
    }
}
const getProductOptions = () => {
@@ -202,7 +267,7 @@
const getModels = (value) => {
    currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
    if (currentProductId) {
    if (currentProductId.value) {
        getList();
    }
};
@@ -234,17 +299,23 @@
    return newItem;
  });
}
// å·¥åºå˜åŒ–处理
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      form.value.inspectType = 1
            const processName = form.value.process || '';
            if (operationType.value === "add") {
                tableData.value.forEach((item) => {
                    delete item.id
                })
            }
            const data = {...form.value, qualityInspectParams: tableData.value}
            const data = {
                ...form.value,
                process: processName, // ä¿ç•™ process å­—段以兼容后端
                qualityInspectParams: tableData.value
            }
      if (operationType.value === "add") {
        qualityInspectAdd(data).then(res => {
          proxy.$modal.msgSuccess("提交成功");
@@ -260,8 +331,41 @@
  })
}
const getList = () => {
    qualityInspectDetailByProductId(currentProductId.value).then(res => {
        tableData.value = res.data;
    if (!currentProductId.value) {
        testStandardOptions.value = [];
        tableData.value = [];
        return;
    }
    const processName = form.value.process || '';
    let params = {
        productId: currentProductId.value,
        inspectType: 1,
        process: processName
    }
    qualityInspectDetailByProductId(params).then(res => {
        // ä¿å­˜ä¸‹æ‹‰æ¡†é€‰é¡¹æ•°æ®
        testStandardOptions.value = res.data || [];
        // æ¸…空表格数据,等待用户选择指标
        tableData.value = [];
        // æ¸…空指标选择
        form.value.testStandardId = '';
    })
}
// æŒ‡æ ‡é€‰æ‹©å˜åŒ–处理
const handleTestStandardChange = (testStandardId) => {
    if (!testStandardId) {
        tableData.value = [];
        return;
    }
    tableLoading.value = true;
    getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
        tableData.value = res.data || [];
    }).catch(error => {
        console.error('获取标准参数失败:', error);
        tableData.value = [];
    }).finally(() => {
        tableLoading.value = false;
    })
}
const getQualityInspectParamList = (id) => {
@@ -272,6 +376,9 @@
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  tableData.value = [];
  testStandardOptions.value = [];
  form.value.testStandardId = '';
  dialogFormVisible.value = false;
  emit('close')
};
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/rawMaterialInspection/components/formDia.vue
@@ -45,6 +45,24 @@
              <el-input v-model="form.model" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select
                v-model="form.testStandardId"
                placeholder="请选择指标"
                clearable
                @change="handleTestStandardChange"
                style="width: 100%"
              >
                <el-option
                  v-for="item in testStandardOptions"
                  :key="item.id"
                  :label="item.standardName || item.standardNo"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
@@ -121,13 +139,13 @@
</template>
<script setup>
import {ref} from "vue";
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {ElMessageBox} from "element-plus";
import {qualityInspectParamDel, qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
const {proxy} = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -142,6 +160,7 @@
    productName: "",
    productId: "",
    model: "",
    testStandardId: "",
    unit: "",
    quantity: "",
    checkCompany: "",
@@ -153,6 +172,7 @@
    checkName: [{required: false, message: "请输入", trigger: "blur"}],
    productId: [{required: true, message: "请输入", trigger: "blur"}],
    model: [{required: false, message: "请输入", trigger: "blur"}],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{required: false, message: "请输入", trigger: "blur"}],
    quantity: [{required: true, message: "请输入", trigger: "blur"}],
    checkCompany: [{required: false, message: "请输入", trigger: "blur"}],
@@ -190,6 +210,7 @@
const supplierList = ref([]);
const productOptions = ref([]);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // æŒ‡æ ‡é€‰æ‹©ä¸‹æ‹‰æ¡†æ•°æ®
// æ‰“开弹框
const openDialog = (type, row) => {
@@ -199,11 +220,53 @@
    supplierList.value = res.data;
  });
    form.value = {}
  testStandardOptions.value = [];
  tableData.value = [];
  getProductOptions();
  if (operationType.value === 'edit') {
    // å…ˆä¿å­˜ testStandardId,避免被清空
    const savedTestStandardId = row.testStandardId;
    form.value = {...row}
    currentProductId.value = row.productId || 0
    getQualityInspectParamList(row.id)
    // ç¼–辑模式下,先加载指标选项,然后加载参数列表
    if (currentProductId.value) {
      // å…ˆåŠ è½½æŒ‡æ ‡é€‰é¡¹
      let params = {
        productId: currentProductId.value,
        inspectType: 0
      }
      qualityInspectDetailByProductId(params).then(res => {
        testStandardOptions.value = res.data || [];
        // ä½¿ç”¨ nextTick å’Œ setTimeout ç¡®ä¿é€‰é¡¹å·²ç»æ¸²æŸ“到 DOM
        nextTick(() => {
          setTimeout(() => {
            // å¦‚果编辑数据中有 testStandardId,则设置并加载对应的参数
            if (savedTestStandardId) {
              // ç¡®ä¿ç±»åž‹åŒ¹é…ï¼ˆitem.id å¯èƒ½æ˜¯æ•°å­—或字符串)
              const matchedOption = testStandardOptions.value.find(item =>
                item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId)
              );
              if (matchedOption) {
                // ç¡®ä¿ä½¿ç”¨åŒ¹é…é¡¹çš„ id(保持类型一致)
                form.value.testStandardId = matchedOption.id;
                // ç¼–辑保留原检验值,直接拉取原参数数据
                getQualityInspectParamList(row.id);
              } else {
                // å¦‚果找不到匹配项,尝试直接使用原值
                console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                form.value.testStandardId = savedTestStandardId;
                getQualityInspectParamList(row.id);
              }
            } else {
              // å¦åˆ™ä½¿ç”¨æ—§çš„逻辑
              getQualityInspectParamList(row.id);
            }
          }, 100);
        });
      });
    } else {
      getQualityInspectParamList(row.id);
    }
  }
}
const getProductOptions = () => {
@@ -214,7 +277,7 @@
const getModels = (value) => {
  currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
  if (currentProductId) {
  if (currentProductId.value) {
    getList();
  }
};
@@ -275,8 +338,39 @@
}
const getList = () => {
  qualityInspectDetailByProductId(currentProductId.value).then(res => {
    tableData.value = res.data;
  if (!currentProductId.value) {
    testStandardOptions.value = [];
    tableData.value = [];
    return;
  }
  let params = {
    productId: currentProductId.value,
    inspectType: 0
  }
  qualityInspectDetailByProductId(params).then(res => {
    // ä¿å­˜ä¸‹æ‹‰æ¡†é€‰é¡¹æ•°æ®
    testStandardOptions.value = res.data || [];
    // æ¸…空表格数据,等待用户选择指标
    tableData.value = [];
    // æ¸…空指标选择
    form.value.testStandardId = '';
  })
}
// æŒ‡æ ‡é€‰æ‹©å˜åŒ–处理
const handleTestStandardChange = (testStandardId) => {
  if (!testStandardId) {
    tableData.value = [];
    return;
  }
  tableLoading.value = true;
  getQualityTestStandardParamByTestStandardId(testStandardId).then(res => {
    tableData.value = res.data || [];
  }).catch(error => {
    console.error('获取标准参数失败:', error);
    tableData.value = [];
  }).finally(() => {
    tableLoading.value = false;
  })
}
@@ -288,7 +382,9 @@
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  tableData.value = []
  tableData.value = [];
  testStandardOptions.value = [];
  form.value.testStandardId = '';
  dialogFormVisible.value = false;
  emit('close')
};
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/reportAnalysis/dataDashboard/index.vue
@@ -22,34 +22,44 @@
      <div class="left-panel">
        <!-- å®¢æˆ·ä¿¡æ¯ç»Ÿè®¡åˆ†æž -->
                <div class="panel-header">
                    <span class="panel-title">客户信息统计分析</span>
                    <span class="panel-title">在制品统计分析</span>
                </div>
        <div class="panel-item-customers">
                    <div class="panel-title-second">
                        <div class="panel-title-icon"></div>
                        <div class="total-customers">
                            <span class="label">总合同金额(元)</span>
                            <span class="value">{{sum}}</span>
                    <div class="quality-cards">
                        <div class="quality-cardSec">
                            <div class="quality-card one"></div>
                            <div class="quality-cardTitle">
                                <div>总在制数量</div>
                                <div>{{workInProcessStatistics.totalQuantity}}ä»¶</div>
                            </div>
                        </div>
<!--                        <div class="jiantou"></div>-->
                        <div class="quality-cardSec">
                            <div class="quality-card two"></div>
                            <div class="quality-cardTitle">
                                <div>平均周转天数</div>
                                <div>{{workInProcessStatistics.avgTurnoverDays}}天</div>
                            </div>
                        </div>
                        <div class="quality-cardSec">
                            <div class="quality-card three"></div>
                            <div class="quality-cardTitle">
                                <div>周转效率</div>
                                <div>{{workInProcessStatistics.turnoverEfficiency}}%</div>
                            </div>
                        </div>
                    </div>
                    <!-- é¥¼å›¾åŒºåŸŸ -->
                    <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 82%;margin-top: 20px">
                        <div style="width: 240px; height: 240px; background-image: url('/src/assets/BI/zonghetongbingtubiankuang@2x.png'); background-size: contain; background-position: center; background-repeat: no-repeat; display: flex; align-items: center; justify-content: center;">
                            <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"
                                             :series="materialPieSeries"
                                             :tooltip="pieTooltip"
                                             :options="{backgroundColor: 'transparent'}"
                                             style="margin-left: 5px;"></Echarts>
                        </div>
                        <ul class="contract-list" style="margin: 0; padding: 0; display: flex; flex-direction: column;justify-content: space-around; height: 100%; overflow-y: auto; scroll-behavior: smooth;" ref="refContractList">
                            <li v-for="item in materialPieSeries[0].data" :key="item.name" style="list-style: none; margin-bottom: 12px;">
                                <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">
                                    <div class="line" :style="{color: item.itemStyle.color}">■ {{item.name}}</div>
                                    <div style="font-weight: 700;font-size: 16px;color: #85B1E4;">ï¿¥{{item.value}}</div>
                                </div>
                            </li>
                        </ul>
                    <!-- å·¥åºåœ¨åˆ¶å“æ•°é‡æŸ±çж图 -->
                    <div style="height: 82%;margin-top: 20px">
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="workInProcessBarLegend"
                                         :series="workInProcessBarSeries"
                                         :tooltip="tooltip"
                                         :xAxis="workInProcessXAxis"
                                         :yAxis="workInProcessYAxis"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 100%"></Echarts>
                    </div>
        </div>
@@ -63,21 +73,21 @@
                            <div class="quality-cardSec">
                                <div class="quality-card one"></div>
                                <div class="quality-cardTitle">
                                    <div>原材料已检测数</div>
                                    <div>原材料检数</div>
                                    <div>{{qualityStatisticsObject.supplierNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card two"></div>
                                <div class="quality-cardTitle">
                                    <div>过程检验数量</div>
                                    <div>过程检数</div>
                                    <div>{{qualityStatisticsObject.processNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card three"></div>
                                <div class="quality-cardTitle">
                                    <div>出厂已检数量</div>
                                    <div>出厂检数</div>
                                    <div>{{qualityStatisticsObject.factoryNum}}ä»¶</div>
                                </div>
                            </div>
@@ -179,20 +189,72 @@
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="event-header">
                            <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" />
                            <span class="event-title">经营分析</span>
                        <div class="order-statistics-cards" style="margin-bottom: 0px;">
                            <div class="quality-cardSec">
                                <div class="quality-card four"></div>
                                <div class="quality-cardTitle">
                                    <div>总订单数</div>
                                    <div>{{orderStatisticsObject.totalOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card five"></div>
                                <div class="quality-cardTitle">
                                    <div>未完成订单数</div>
                                    <div>{{orderStatisticsObject.uncompletedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card six"></div>
                                <div class="quality-cardTitle">
                                    <div>部分完成订单数</div>
                                    <div>{{orderStatisticsObject.partialCompletedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card seven"></div>
                                <div class="quality-cardTitle">
                                    <div>已完成订单数</div>
                                    <div>{{orderStatisticsObject.completedOrderCount}}ä»¶</div>
                                </div>
                            </div>
                        </div>
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="barLegend1"
                                         :series="barSeries11"
                                         :tooltip="tooltip"
                                         :xAxis="xAxis3"
                                         :yAxis="yAxis3"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 170px"></Echarts>
                        <div class="progress-table-container" ref="progressTableRef" style="margin-top: 0px;" @scroll="handleTableScroll">
                            <table class="progress-table">
                                <thead>
                                    <tr>
                                        <th>生产订单号</th>
                                        <th>产品名称</th>
                                        <th>规格</th>
                                        <th>需求数量</th>
                                        <th>完成数量</th>
                                        <th>完成进度</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr
                                        v-for="(item, index) in progressTableData"
                                        :key="index"
                                        :ref="el => setRowRef(el, index)"
                                        :class="{ 'row-under-header': isRowUnderHeader(index) }"
                                    >
                                        <td>{{ item.npsNo || '-' }}</td>
                                        <td>{{ item.productCategory || '-' }}</td>
                                        <td>{{ item.specificationModel || '-' }}</td>
                                        <td>{{ item.quantity || 0 }}</td>
                                        <td>{{ item.completeQuantity || 0 }}</td>
                                        <td>
                                            <el-progress
                                                :percentage="calculateProgress(item)"
                                                :color="progressColor(calculateProgress(item))"
                                                :status="calculateProgress(item) >= 100 ? 'success' : ''"
                                                :stroke-width="8"
                                            />
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
      </div>
@@ -253,7 +315,7 @@
    getProgressStatistics,
      getWorkInProcessTurnover
} from "@/api/viewIndex.js";
import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js";
import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
import {listCustomer} from "@/api/basicData/customerFile.js";
import {listSupplier} from "@/api/basicData/supplierManageFile.js";
import {getLedgerPage} from "@/api/equipmentManagement/ledger.js";
@@ -261,6 +323,7 @@
import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js";
import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js";
import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js";
import {productOrderListPage} from "@/api/productionManagement/productionOrder.js";
// å…¨å±ç›¸å…³çŠ¶æ€
const isFullscreen = ref(false);
@@ -288,11 +351,17 @@
const realtimeLineChartRef = ref(null)
const refContractList = ref(null)
const refTodoList = ref(null)
const progressTableRef = ref(null)
const timerScroll = ref(null)
const progressTableScrollTimer = ref(null)
const isTableScrolling = ref(false)
const tableScrollTimeout = ref(null)
const tableRowRefs = ref([])
const rowsUnderHeader = ref(new Set())
const chartStylePie = {
    width: '140%',
    height: '140%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
    width: '100%',
    height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const materialPieSeries = ref([
    {
@@ -336,6 +405,21 @@
    supplierNum: 0,
    processNum: 0,
    factoryNum: 0,
})
// è®¢å•统计对象
const orderStatisticsObject = ref({
    totalOrderCount: 0,
    uncompletedOrderCount: 0,
    partialCompletedOrderCount: 0,
    completedOrderCount: 0,
})
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡å¯¹è±¡
const workInProcessStatistics = ref({
    totalQuantity: 0,
    avgTurnoverDays: 0,
    turnoverEfficiency: 0,
})
const chartStyle = {
    width: '100%',
@@ -579,8 +663,83 @@
    axisLabel: { color: '#B8C8E0' }
}]
// åœ¨åˆ¶å“å·¥åºæŸ±çŠ¶å›¾é…ç½®
const workInProcessXAxis = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const workInProcessYAxis = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' },
    name: ''
}]
const workInProcessBarLegend = {
    show: false,
    textStyle: { color: '#B8C8E0' },
    data: []
}
const workInProcessBarSeries = ref([
    {
        name: '在制品数量',
        type: 'bar',
        barWidth: 25, // å›ºå®šæŸ±çŠ¶å›¾å®½åº¦ä¸º40px
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 0, color: '#4EE4FF' },
                    { offset: 1, color: '#00A4ED' }
                ]
            }
        },
        label: {
            show: true,
            position: 'top',
            color: '#B8C8E0'
        },
        data: []
    }
])
// å¾…办事项
const todoList = ref([])
// ç”Ÿäº§è®¢å•完成进度表格数据
const progressTableData = ref([])
// è®¡ç®—完成进度百分比
const calculateProgress = (item) => {
    if (!item) return 0
    // ä¼˜å…ˆä½¿ç”¨completionStatus字段
    if (item.completionStatus !== undefined && item.completionStatus !== null) {
        const percentage = Number(item.completionStatus)
        if (isNaN(percentage)) return 0
        return Math.min(Math.max(Math.round(percentage), 0), 100)
    }
    // å¦‚果没有completionStatus,则根据完成数量和需求数量计算
    if (!item.quantity || item.quantity === 0) return 0
    const percentage = (item.completeQuantity || 0) / item.quantity * 100
    return Math.min(Math.max(Math.round(percentage), 0), 100)
}
// æ ¹æ®è¿›åº¦ç™¾åˆ†æ¯”返回颜色
const progressColor = (percentage) => {
    const p = percentage || 0
    if (p < 30) return "#f56c6c"
    if (p < 50) return "#e6a23c"
    if (p < 80) return "#409eff"
    return "#67c23a"
}
// è®¡ç®—缩放比例
const calculateScale = () => {
@@ -635,6 +794,43 @@
        }))
    })
}
// åœ¨åˆ¶å“å‘¨è½¬ç»Ÿè®¡
const workInProcessTurnoverInfo = () => {
    getWorkInProcessTurnover().then((res) => {
        console.log("在制品周转统计数据:", res)
        if (!res || !res.data) {
            console.warn('在制品周转统计数据为空')
            return
        }
        // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
        workInProcessStatistics.value = {
            totalQuantity: res.data.totalOrderCount || 0,
            avgTurnoverDays: res.data.averageTurnoverDays || 0,
            turnoverEfficiency: res.data.turnoverEfficiency || 0,
        }
        // è®¾ç½®å·¥åºæŸ±çŠ¶å›¾æ•°æ®
        // X轴:processDetails (工序详情数组)
        // Y轴:processQuantityDetails (工序数量详情数组)
        if (res.data.processDetails && Array.isArray(res.data.processDetails)) {
            // è®¾ç½®X轴数据(工序名称)
            workInProcessXAxis.value[0].data = res.data.processDetails
        } else {
            workInProcessXAxis.value[0].data = []
        }
        if (res.data.processQuantityDetails && Array.isArray(res.data.processQuantityDetails)) {
            // è®¾ç½®Y轴数据(在制品数量)
            workInProcessBarSeries.value[0].data = res.data.processQuantityDetails
        } else {
            workInProcessBarSeries.value[0].data = []
        }
    }).catch((error) => {
        console.error('获取在制品周转统计失败:', error)
    })
}
// è´¨æ£€ç»Ÿè®¡
const qualityStatisticsInfo = () => {
    qualityStatistics().then((res) => {
@@ -651,6 +847,7 @@
}
// å„生产订单的完成进度统计
const progressStatisticsInfo = () => {
    // ä»Žç»Ÿè®¡æŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
    getProgressStatistics().then((res) => {
        console.log("生产订单完成进度统计数据:", res)
        
@@ -659,24 +856,22 @@
            return
        }
        
        // è®¾ç½®X轴数据 - ä½¿ç”¨åˆ†ç±»åç§°
        xAxis3.value[0].data = ['已完成进度数', '总订单数', '未完成订单数', '已完成订单数']
        // è®¾ç½®å•个系列的数据 - æ¯ä¸ªX轴分类对应一个值
        if (barSeries11.value && barSeries11.value.length > 0) {
            barSeries11.value[0].data = [
                res.data.completedProgressCount || 0,
                res.data.totalOrderCount || 0,
                res.data.uncompletedOrderCount || 0,
                res.data.completedOrderCount || 0
            ]
        // ä»ŽæŽ¥å£èŽ·å–ç»Ÿè®¡æ•°æ®
        orderStatisticsObject.value = {
            totalOrderCount: res.data.totalOrderCount || 0,
            uncompletedOrderCount: res.data.uncompletedOrderCount || 0,
            partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0,
            completedOrderCount: res.data.completedOrderCount || 0
        }
        progressTableData.value = res.data.completedOrderDetails || []
        // é‡ç½®è¡Œå¼•用
        tableRowRefs.value = []
        rowsUnderHeader.value.clear()
        
        console.log('图表数据设置完成:', {
            xAxis: xAxis3.value[0].data,
            series: barSeries11.value[0]?.data
        // åœ¨èŽ·å–åˆ°æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
        nextTick(() => {
            initProgressTableScroll()
        })
    }).catch((error) => {
        console.error('获取生产订单完成进度统计失败:', error)
    })
@@ -822,6 +1017,163 @@
// è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨
const autoSwitchTimer = ref(null)
// è®¾ç½®è¡Œå¼•用
const setRowRef = (el, index) => {
    if (el) {
        tableRowRefs.value[index] = el
    }
}
// åˆ¤æ–­è¡Œæ˜¯å¦åœ¨è¡¨å¤´ä¸‹æ–¹
const isRowUnderHeader = (index) => {
    return rowsUnderHeader.value.has(index)
}
// å¤„理表格滚动事件
const handleTableScroll = () => {
    const tableContainer = progressTableRef.value
    if (!tableContainer) return
    const thead = tableContainer.querySelector('thead')
    if (!thead) return
    const theadHeight = thead.offsetHeight
    const containerRect = tableContainer.getBoundingClientRect()
    const containerTop = containerRect.top
    const theadBottom = containerTop + theadHeight
    // æ¸…空之前的记录
    rowsUnderHeader.value.clear()
    // æ£€æŸ¥æ¯ä¸€è¡Œæ˜¯å¦åœ¨è¡¨å¤´ä¸‹æ–¹ï¼ˆè¢«è¡¨å¤´é®æŒ¡ï¼‰
    tableRowRefs.value.forEach((row, index) => {
        if (row) {
            const rowRect = row.getBoundingClientRect()
            const rowTop = rowRect.top
            const rowBottom = rowRect.bottom
            // å¦‚果行与表头有重叠(行在表头下方被遮挡)
            // è¡Œçš„顶部在表头底部下方,但行的底部在表头底部上方,说明被遮挡
            if (rowTop < theadBottom && rowBottom > containerTop) {
                rowsUnderHeader.value.add(index)
            }
        }
    })
    // æ¸…除之前的定时器
    if (tableScrollTimeout.value) {
        clearTimeout(tableScrollTimeout.value)
    }
    // æ»šåŠ¨åœæ­¢åŽæ¸…ç©ºæ·¡åŒ–æ ‡è®°
    tableScrollTimeout.value = setTimeout(() => {
        rowsUnderHeader.value.clear()
    }, 150)
}
// åˆå§‹åŒ–生产订单进度表格滚动功能
const initProgressTableScroll = () => {
    const tableContainer = progressTableRef.value
    if (!tableContainer) return
    // æ¸…理之前的滚动动画和定时器
    if (progressTableScrollTimer.value) {
        cancelAnimationFrame(progressTableScrollTimer.value)
        progressTableScrollTimer.value = null
    }
    if (tableContainer._pauseTimer) {
        clearInterval(tableContainer._pauseTimer)
        tableContainer._pauseTimer = null
    }
    const tbody = tableContainer.querySelector('tbody')
    if (!tbody) return
    // æ¸…理之前可能存在的克隆行(保留原始数据行)
    // åŽŸå§‹æ•°æ®è¡Œçš„æ•°é‡åº”è¯¥ç­‰äºŽ progressTableData.value.length
    const originalCount = progressTableData.value.length
    const allRows = Array.from(tbody.querySelectorAll('tr'))
    if (allRows.length > originalCount) {
        // ç§»é™¤æ‰€æœ‰è¶…过原始数量的行(这些是克隆的行)
        for (let i = originalCount; i < allRows.length; i++) {
            allRows[i].remove()
        }
    }
    const scrollItems = Array.from(tbody.querySelectorAll('tr'))
    if (scrollItems.length === 0) return
    // èŽ·å–åŽŸå§‹æ•°æ®é¡¹æ•°é‡
    const originalItemCount = scrollItems.length
    // è®¡ç®—容器高度和表头高度
    const thead = tableContainer.querySelector('thead')
    const theadHeight = thead ? thead.offsetHeight : 40
    const containerHeight = tableContainer.clientHeight
    const visibleHeight = containerHeight - theadHeight
    // è®¡ç®—原始数据的总高度
    const itemHeight = scrollItems[0]?.offsetHeight || 40
    const totalContentHeight = itemHeight * originalItemCount
    // å¦‚果数据量不够,容器可以完全显示所有数据,就不需要滚动和克隆
    if (totalContentHeight <= visibleHeight) {
        // æ•°æ®é‡å°‘,不需要滚动,直接返回
        return
    }
    // æ•°æ®é‡è¶³å¤Ÿï¼Œéœ€è¦æ»šåŠ¨ï¼Œè¿›è¡Œå…‹éš†ä»¥å®žçŽ°æ— ç¼æ»šåŠ¨
    const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2
    // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
    for (let i = 0; i < cloneCount; i++) {
        const clone = scrollItems[i % originalItemCount].cloneNode(true)
        tbody.appendChild(clone)
    }
    let scrollPosition = 0
    const scrollSpeed = 1.5
    const pauseTime = 3000
    let isPaused = false
    let lastTimestamp = 0
    // è¿žç»­æ»šåŠ¨åŠ¨ç”»å‡½æ•°
    function scrollAnimation(timestamp) {
        if (!lastTimestamp) lastTimestamp = timestamp
        const deltaTime = timestamp - lastTimestamp
        lastTimestamp = timestamp
        if (!isPaused) {
            scrollPosition += scrollSpeed * (deltaTime / 16)
            // è®¡ç®—最大滚动位置(原始内容的高度)
            const maxScroll = itemHeight * originalItemCount
            // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
            if (scrollPosition >= maxScroll) {
                scrollPosition = 0
                tableContainer.scrollTop = 0
            } else {
                tableContainer.scrollTop = scrollPosition
            }
        }
        progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation)
    }
    // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
    progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation)
    // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
    const pauseTimer = setInterval(() => {
        isPaused = !isPaused
    }, pauseTime)
    // æ¸…理定时器
    tableContainer._pauseTimer = pauseTimer
}
// åˆå§‹åŒ–待办事项列表滚动功能
const initTodoListScroll = () => {
    const todoList = refTodoList.value
@@ -1031,6 +1383,7 @@
  window.addEventListener('webkitfullscreenchange', handleFullscreenChange)
  window.addEventListener('MSFullscreenChange', handleFullscreenChange)
  analysisCustomer()
  workInProcessTurnoverInfo()
  qualityStatisticsInfo()
    // accountStatisticsInfo()
    progressStatisticsInfo()
@@ -1072,6 +1425,25 @@
      clearInterval(todoList._pauseTimer)
      todoList._pauseTimer = null
    }
  }
  // æ¸…理生产订单进度表格的动画和定时器
  const progressTable = progressTableRef.value
  if (progressTable) {
    if (progressTableScrollTimer.value) {
      cancelAnimationFrame(progressTableScrollTimer.value)
      progressTableScrollTimer.value = null
    }
    if (progressTable._pauseTimer) {
      clearInterval(progressTable._pauseTimer)
      progressTable._pauseTimer = null
    }
  }
  // æ¸…理表格滚动定时器
  if (tableScrollTimeout.value) {
    clearTimeout(tableScrollTimeout.value)
    tableScrollTimeout.value = null
  }
  
  // æ¸…理自动轮换周、月、季度的定时器
@@ -1272,7 +1644,33 @@
}
.quality-card.three {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
/* è®¢å•统计卡片样式 */
.order-statistics-cards {
    display: flex;
    gap: 12px;
    width: 100%;
    height: 94px;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}
.quality-card.four {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.quality-card.five {
    background-image: url("@/assets/BI/guochengyijianicon@2x.png");
}
.quality-card.six {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
.quality-card.seven {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.panel-title-icon {
    width: 60px;
@@ -1555,4 +1953,84 @@
  border-color: rgba(255, 255, 255, 0.5);
  box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5);
}
/* ç”Ÿäº§è®¢å•进度表格样式 */
.progress-table-container {
  height: 250px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-top: 10px;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE和Edge */
}
.progress-table-container::-webkit-scrollbar {
  display: none; /* Chrome、Safari和Opera */
}
.progress-table {
  width: 100%;
  border-collapse: collapse;
  color: #B8C8E0;
  font-size: 12px;
  table-layout: fixed;
}
.progress-table thead {
  position: sticky;
  top: 0;
  background-color: rgba(26, 88, 176, 0.9);
  z-index: 10;
}
.progress-table th {
  padding: 8px 6px;
  text-align: left;
  font-weight: 500;
  border-bottom: 1px solid rgba(184, 200, 224, 0.3);
  color: #B8C8E0;
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.progress-table th:nth-child(1) { width: 15%; } /* ç”Ÿäº§è®¢å•号 */
.progress-table th:nth-child(2) { width: 15%; } /* äº§å“åç§° */
.progress-table th:nth-child(3) { width: 15%; } /* è§„æ ¼ */
.progress-table th:nth-child(4) { width: 12%; } /* éœ€æ±‚数量 */
.progress-table th:nth-child(5) { width: 12%; } /* å®Œæˆæ•°é‡ */
.progress-table th:nth-child(6) { width: 31%; } /* å®Œæˆè¿›åº¦ */
.progress-table td {
  padding: 8px 6px;
  border-bottom: 1px solid rgba(184, 200, 224, 0.1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12px;
  transition: opacity 0.3s ease;
}
.progress-table tbody tr:hover {
  background-color: rgba(184, 200, 224, 0.1);
}
.progress-table tbody tr.row-under-header {
  opacity: 0.5;
}
/* el-progress ç»„件样式调整 */
.progress-table :deep(.el-progress) {
  width: 100%;
}
.progress-table :deep(.el-progress-bar__outer) {
  background-color: rgba(184, 200, 224, 0.2);
}
.progress-table :deep(.el-progress__text) {
  color: #B8C8E0;
  font-size: 11px;
}
</style>
src/views/reportAnalysis/projectProfit/index.vue
@@ -1,30 +1,32 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true" label-width="80px">
      <el-form-item label="客户名称">
        <el-input v-model="filters.customerName" placeholder="请输入客户名称" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData"> æœç´¢ </el-button>
        <el-button @click="resetFilters"> é‡ç½® </el-button>
        <el-button @click="handleOut"> å¯¼å‡º </el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableLoading="loading"
        :tableData="dataList"
        :page="{
    <div class="app-container">
        <el-form :model="filters" :inline="true" label-width="80px">
            <el-form-item label="客户名称">
                <el-input v-model="filters.customerName" placeholder="请输入客户名称" clearable style="width: 240px"/>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="getTableData"> æœç´¢ </el-button>
                <el-button @click="resetFilters"> é‡ç½® </el-button>
                <el-button @click="handleOut"> å¯¼å‡º </el-button>
            </el-form-item>
        </el-form>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="columns"
                :tableLoading="loading"
                :tableData="dataList"
                :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
          total: pagination.total
        }"
        @pagination="changePage"
      ></PIMTable>
    </div>
  </div>
                :isShowSummary="true"
                :summaryMethod="summarizeMainTable"
                @pagination="changePage"
            ></PIMTable>
        </div>
    </div>
</template>
<script setup>
@@ -36,93 +38,89 @@
const { proxy } = getCurrentInstance();
defineOptions({
  name: "项目利润",
    name: "项目利润",
});
const {
  loading,
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
    loading,
    filters,
    columns,
    dataList,
    pagination,
    getTableData,
    resetFilters,
    onCurrentChange,
} = usePaginationApi(
  getPurchaseList,
  {
    customerName: undefined,
  },
  [
    {
      label: "销售合同号",
      align: "center",
      prop: "customerContractNo",
    },
    {
      label: "客户名称",
      align: "center",
      prop: "customerName",
    },
    {
      label: "项目名称",
      align: "center",
      prop: "projectName",
    },
    {
      label: "合同金额",
      align: "center",
      prop: "contractAmount",
    },
    {
      label: "采购金额",
      align: "center",
      prop: "purchaseAmount",
    },
    {
      label: "利润",
      align: "center",
      prop: "balance",
    },
    {
      label: "利润率",
      align: "center",
      prop: "balanceRatio",
    },
    {
      label: "增值税",
      align: "center",
      prop: "balanceAmount",
    },
  ]
    getPurchaseList,
    {
        customerName: undefined,
    },
    [
        {
            label: "销售合同号",
            align: "center",
            prop: "customerContractNo",
        },
        {
            label: "客户名称",
            align: "center",
            prop: "customerName",
        },
        {
            label: "合同金额",
            align: "center",
            prop: "contractAmount",
        },
        {
            label: "采购金额",
            align: "center",
            prop: "purchaseAmount",
        },
        {
            label: "利润",
            align: "center",
            prop: "balance",
        },
        {
            label: "利润率",
            align: "center",
            prop: "balanceRatio",
        },
    ]
);
const changePage = ({ page }) => {
  pagination.currentPage = page;
  onCurrentChange(page);
const changePage = ({ page, limit }) => {
    pagination.currentPage = page;
    pagination.pageSize = limit;
    onCurrentChange(page);
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, ["contractAmount", "purchaseAmount", "balance"]);
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/purchase/report/export", {}, "项目利润.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/purchase/report/export", {}, "项目利润.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
  getTableData();
    getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
    margin-top: unset;
}
</style>
src/views/reportAnalysis/reportManagement/index.vue
@@ -1,715 +1,1402 @@
<template>
    <div class="report-management">
        <!-- ç­›é€‰æ¡ä»¶ -->
        <el-card class="filter-card" shadow="never">
            <el-form :model="filterForm" inline>
                <el-form-item label="时间范围">
                    <el-date-picker
                        style="width: 300px"
                        v-model="filterForm.dateRange"
                        type="daterange"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD"
                        @change="handleFilterChange"
                    />
                </el-form-item>
                <el-form-item label="报表类型">
                    <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange" style="width: 300px">
                        <el-option label="样品进度报表" value="sample" />
                        <el-option label="设备使用报表" value="equipment" />
                        <el-option label="检测项目报表" value="inspection" />
                        <el-option label="领用记录报表" value="usage" />
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleFilterChange">查询</el-button>
                    <el-button @click="resetFilter">重置</el-button>
                    <el-button type="success" @click="exportReport">导出报表</el-button>
                </el-form-item>
            </el-form>
        </el-card>
        <!-- ç»Ÿè®¡å¡ç‰‡ -->
        <div class="statistics-cards">
            <el-row :gutter="20">
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Box /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalSamples }}</div>
                                <div class="stat-label">总样品数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Tools /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                                <div class="stat-label">在用设备</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Document /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.completedInspections }}</div>
                                <div class="stat-label">已完成检测</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><ShoppingCart /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalUsage }}</div>
                                <div class="stat-label">总领用次数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- å›¾è¡¨åŒºåŸŸ -->
        <div class="charts-container">
            <el-row :gutter="20">
                <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>样品进度统计</span>
                                <el-button link @click="refreshSampleChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="sampleChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>设备使用率统计</span>
                                <el-button link @click="refreshEquipmentChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="equipmentChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
            <el-row :gutter="20" style="margin-top: 20px;">
                <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>检测项目分布</span>
                                <el-button link @click="refreshInspectionChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="inspectionChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- é¢†ç”¨è®°å½•趋势 -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>领用记录趋势</span>
                                <el-button link @click="refreshUsageChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="usageChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
        <el-card class="table-card" shadow="hover">
            <template #header>
                <div class="card-header">
                    <span>详细数据</span>
                    <div>
                        <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
                        <el-button type="success" size="small" @click="exportTable">导出</el-button>
                    </div>
                </div>
            </template>
            <el-table
                :data="tableData"
                style="width: 100%"
                v-loading="tableLoading"
                stripe
                border
            >
                <el-table-column prop="id" label="编号" width="80" />
                <el-table-column prop="name" label="名称" />
                <el-table-column prop="status" label="状态">
                    <template #default="scope">
                        <el-tag :type="getStatusType(scope.row.status)">
                            {{ scope.row.status }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="progress" label="进度">
                    <template #default="scope">
                        <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
                    </template>
                </el-table-column>
                <el-table-column prop="createTime" label="创建时间" width="180" />
                <el-table-column prop="updateTime" label="更新时间" width="180" />
                <el-table-column label="操作" width="150" fixed="right">
                    <template #default="scope">
                        <el-button link size="small" @click="viewDetail(scope.row)">查看</el-button>
                        <el-button link size="small" @click="editItem(scope.row)">编辑</el-button>
                    </template>
                </el-table-column>
            </el-table>
            <div class="pagination-container">
                <el-pagination
                    v-model:current-page="pagination.currentPage"
                    v-model:page-size="pagination.pageSize"
                    :page-sizes="[10, 20, 50, 100]"
                    :total="pagination.total"
                    layout="total, sizes, prev, pager, next, jumper"
                    @size-change="handleSizeChange"
                    @current-change="handleCurrentChange"
                />
            </div>
        </el-card>
    </div>
  <div class="report-management">
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="charts-container">
      <el-row :gutter="20">
        <!-- å„类型完成数量 -->
        <el-col :span="9">
          <el-card class="chart-card"
                   shadow="hover">
            <template #header>
              <div class="card-header">
                <div class="chart-title-line"></div>
                <span>各类型完成数量</span>
              </div>
            </template>
            <div class="top-container">
              <div class="typeNum">
                <div class="typeNum-left">
                  <img src="~@/assets/images/chartCard.svg"
                       alt="图表"
                       style="width: 40px; height: 40px; object-fit: contain;">
                  <div class="typeNum-left-text">原材料</div>
                </div>
                <div class="typeNum-center">
                  <div class="typeNum-leftLine">-</div>
                  <div class="typeNum-rightLine"></div>
                </div>
                <div class="typeNum-right">
                  <div class="typeNum-right-top">
                    <div class="typeNum-right-top-name">总数量</div>
                    <div class="typeNum-right-top-text">2099 <span class="unit">个</span></div>
                  </div>
                  <div class="typeNum-right-bottom">
                    <div class="typeNum-right-top-name">已完成数</div>
                    <div class="typeNum-right-top-text">2099 <span class="unit">个</span></div>
                  </div>
                </div>
              </div>
              <div class="typeNum">
                <div class="typeNum-left">
                  <img src="~@/assets/images/chartCard2.svg"
                       alt="图表"
                       style="width: 40px; height: 40px; object-fit: contain;">
                  <div class="typeNum-left-text"
                       style="color: #5EB334;">半成品</div>
                </div>
                <div class="typeNum-center">
                  <div class="typeNum-leftLine2">-</div>
                  <div class="typeNum-rightLine2"></div>
                </div>
                <div class="typeNum-right">
                  <div class="typeNum-right-top">
                    <div class="typeNum-right-top-name">总数量</div>
                    <div class="typeNum-right-top-text">2099 <span class="unit">个</span></div>
                  </div>
                  <div class="typeNum-right-bottom">
                    <div class="typeNum-right-top-name">已完成数</div>
                    <div class="typeNum-right-top-text">2099 <span class="unit">个</span></div>
                  </div>
                </div>
              </div>
              <div class="typeNum">
                <div class="typeNum-left">
                  <img src="~@/assets/images/chartCard3.svg"
                       alt="图表"
                       style="width: 40px; height: 40px; object-fit: contain;">
                  <div class="typeNum-left-text"
                       style="color: #8000FF;">成品</div>
                </div>
                <div class="typeNum-center">
                  <div class="typeNum-leftLine3">-</div>
                  <div class="typeNum-rightLine3"></div>
                </div>
                <div class="typeNum-right">
                  <div class="typeNum-right-top">
                    <div class="typeNum-right-top-name">总数量</div>
                    <div class="typeNum-right-top-text">2099 <span class="unit">个</span></div>
                  </div>
                  <div class="typeNum-right-bottom">
                    <div class="typeNum-right-top-name">已完成数</div>
                    <div class="typeNum-right-top-text">2099 <span class="unit">个</span></div>
                  </div>
                </div>
              </div>
            </div>
          </el-card>
        </el-col>
        <!-- è´¨æ£€åˆæ ¼çއ -->
        <el-col :span="15">
          <el-card class="chart-card"
                   shadow="hover">
            <template #header>
              <div class="card-header">
                <div class="chart-title-line"></div>
                <span>质检合格率</span>
              </div>
            </template>
            <div class="top-container flex-center">
              <div class="quality-card blue-card">
                <div class="quality-card-title">
                  <img src="~@/assets/images/chartCard.svg"
                       alt="原材料"
                       style="width: 24px; height: 24px; margin-right: 8px;">
                  åŽŸææ–™åˆæ ¼çŽ‡
                </div>
                <div class="quality-card-content">
                  <div class="quality-item">
                    <div>
                      <div class="quality-item-label blue-label">完成率</div>
                      <div class="quality-item-tip">占比</div>
                      <div class="quality-item-value">80%</div>
                    </div>
                    <div class="quality-item-chart"
                         ref="materialCompletionChart"></div>
                  </div>
                  <div class="quality-item">
                    <div>
                      <div class="quality-item-label green-label">合格率</div>
                      <div class="quality-item-tip">占比</div>
                      <div class="quality-item-value">80%</div>
                    </div>
                    <div class="quality-item-chart"
                         ref="materialQualityChart"></div>
                  </div>
                </div>
              </div>
              <div class="quality-card green-card">
                <div class="quality-card-title">
                  <img src="~@/assets/images/chartCard2.svg"
                       alt="半成品"
                       style="width: 24px; height: 24px; margin-right: 8px;">
                  åŠæˆå“åˆæ ¼çއ
                </div>
                <div class="quality-card-content">
                  <div class="quality-item">
                    <div>
                      <div class="quality-item-label blue-label">完成率</div>
                      <div class="quality-item-tip">占比</div>
                      <div class="quality-item-value">80%</div>
                    </div>
                    <div class="quality-item-chart"
                         ref="semiCompletionChart"></div>
                  </div>
                  <div class="quality-item">
                    <div>
                      <div class="quality-item-label green-label">合格率</div>
                      <div class="quality-item-tip">占比</div>
                      <div class="quality-item-value">80%</div>
                    </div>
                    <div class="quality-item-chart"
                         ref="semiQualityChart"></div>
                  </div>
                </div>
              </div>
              <div class="quality-card purple-card">
                <div class="quality-card-title">
                  <img src="~@/assets/images/chartCard3.svg"
                       alt="成品"
                       style="width: 24px; height: 24px; margin-right: 8px;">
                  æˆå“åˆæ ¼çއ
                </div>
                <div class="quality-card-content">
                  <div class="quality-item">
                    <div>
                      <div class="quality-item-label blue-label">完成率</div>
                      <div class="quality-item-tip">占比</div>
                      <div class="quality-item-value">80%</div>
                    </div>
                    <div class="quality-item-chart"
                         ref="finalCompletionChart"></div>
                  </div>
                  <div class="quality-item">
                    <div>
                      <div class="quality-item-label green-label">合格率</div>
                      <div class="quality-item-tip">占比</div>
                      <div class="quality-item-value">80%</div>
                    </div>
                    <div class="quality-item-chart"
                         ref="finalQualityChart"></div>
                  </div>
                </div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <div class="charts-container">
      <el-row :gutter="20">
        <!-- è´¨æ£€åˆæ ¼çއ -->
        <el-col :span="24">
          <el-card class="chart-card"
                   shadow="hover">
            <template #header>
              <div class="card-header">
                <div class="chart-title-line"></div>
                <span>质检合格率</span>
              </div>
            </template>
            <div class="chart-container-line">
              <div class="container-line-left">
                <div style="height: 100%; width: 100%;"
                     ref="usageChartRef">
                </div>
              </div>
              <div class="container-line-right">
                <div style="height: 80%; width: 100%;"
                     ref="inspectionChartRef">
                </div>
                <div class="container-line-right-bottom">
                  <div class="inspection-chart-box">
                    <div class="chart-box-title">原材料抽检数</div>
                    <div class="chart-box-num">600</div>
                  </div>
                  <div class="inspection-chart-box">
                    <div class="chart-box-title">半成品抽检数</div>
                    <div class="chart-box-num">200</div>
                  </div>
                  <div class="inspection-chart-box">
                    <div class="chart-box-title">成品抽检数</div>
                    <div class="chart-box-num">200</div>
                  </div>
                </div>
              </div>
            </div>
            <!-- </div> -->
            <!-- <div ref="sampleChartRef"
                 class="chart-container"></div> -->
            <div class="yearchange">
              <div style="margin-right: 8px;font-size: 14px;">年份:</div>
              <el-date-picker v-model="value3"
                              size="mini"
                              :clearable="false"
                              style="width: 60px;"
                              type="year"
                              :disabled-date="disabledDate"
                              placeholder="">
              </el-date-picker>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <div class="charts-container">
      <el-row :gutter="20">
        <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card"
                   shadow="hover">
            <template #header>
              <div class="card-header">
                <div class="chart-title-line"></div>
                <span>质量完成明细</span>
              </div>
            </template>
            <div ref="equipmentChartRef"
                 class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card"
                   shadow="hover">
            <template #header>
              <div class="card-header">
                <div class="chart-title-line"></div>
                <span>检测项目分类</span>
              </div>
            </template>
            <div class="chart-container-line">
              <div class="container-line2-left">
                <div class="info-box">
                  <div class="info-box-header">项目分布</div>
                  <div class="info-line">
                    <div class="info-icon"
                         style="background-color: #165DFF"></div>
                    <div class="info-line-title">物理性能</div>
                    <div class="info-line-value1">30%</div>
                    <div class="info-line-value2">300</div>
                  </div>
                  <div class="info-line">
                    <div class="info-icon"
                         style="background-color: #14C9C9;"></div>
                    <div class="info-line-title">物理性能</div>
                    <div class="info-line-value1">30%</div>
                    <div class="info-line-value2">300</div>
                  </div>
                  <div class="info-line">
                    <div class="info-icon"
                         style="background-color: #F7BA1E;"></div>
                    <div class="info-line-title">物理性能</div>
                    <div class="info-line-value1">30%</div>
                    <div class="info-line-value2">300</div>
                  </div>
                  <div class="info-line">
                    <div class="info-icon"
                         style="background-color: #722ED1;"></div>
                    <div class="info-line-title">物理性能</div>
                    <div class="info-line-value1">30%</div>
                    <div class="info-line-value2">300</div>
                  </div>
                  <div class="info-line">
                    <div class="info-icon"
                         style="background-color: #3491FA;"></div>
                    <div class="info-line-title">物理性能</div>
                    <div class="info-line-value1">30%</div>
                    <div class="info-line-value2">300</div>
                  </div>
                </div>
              </div>
              <div ref="sampleChartRef"
                   style="height: 100%; width: 50%;"
                   class="chart-container"></div>
            </div>
            <!-- Tab é€‰æ‹©å™¨ -->
            <div class="tab-selector">
              <div class="tab-item"
                   :class="{ active: activeTab === 'raw' }"
                   @click="activeTab = 'raw'">原材料</div>
              <div class="tab-item"
                   :class="{ active: activeTab === 'semi' }"
                   @click="activeTab = 'semi'">半成品</div>
              <div class="tab-item"
                   :class="{ active: activeTab === 'final' }"
                   @click="activeTab = 'final'">成品</div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
  import { ref, reactive, onMounted, nextTick } from "vue";
  import { ElMessage, ElMessageBox } from "element-plus";
  import * as echarts from "echarts";
  import { Box, Tools, Document, ShoppingCart } from "@element-plus/icons-vue";
// å“åº”式数据
const filterForm = reactive({
    dateRange: [],
    reportType: 'sample'
})
  // å“åº”式数据
  const filterForm = reactive({
    dateRange: [],
    reportType: "sample",
  });
const statistics = reactive({
    totalSamples: 1250,
    activeEquipment: 45,
    completedInspections: 890,
    totalUsage: 2340
})
  const statistics = reactive({
    totalSamples: 1250,
    activeEquipment: 45,
    completedInspections: 890,
    totalUsage: 2340,
  });
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
    currentPage: 1,
    pageSize: 20,
    total: 0
})
  const tableData = ref([]);
  const tableLoading = ref(false);
  const pagination = reactive({
    currentPage: 1,
    pageSize: 20,
    total: 0,
  });
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
  // åˆå§‹åŒ–年份为当前年份(使用Date对象)
  const currentYear = new Date().getFullYear();
  const value3 = ref(new Date(currentYear, 0, 1));
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
  // é™åˆ¶æ—¥æœŸé€‰æ‹©ï¼Œä¸å…è®¸é€‰æ‹©ä»Šå¹´ä¹‹åŽçš„年份
  const disabledDate = time => {
    const currentYear = new Date().getFullYear();
    return time.getFullYear() > currentYear;
  };
// åˆå§‹åŒ–数据
const initData = () => {
    // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
    tableData.value = [
        {
            id: 'SP001',
            name: '样品A-001',
            type: '金属材料',
            status: '检测中',
            progress: 75,
            createTime: '2025-01-15 09:30:00',
            updateTime: '2025-01-20 14:20:00'
        },
        {
            id: 'SP002',
            name: '样品B-002',
            type: '塑料制品',
            status: '已完成',
            progress: 100,
            createTime: '2025-01-10 10:15:00',
            updateTime: '2025-01-18 16:45:00'
        },
        {
            id: 'SP003',
            name: '样品C-003',
            type: '电子元件',
            status: '待检测',
            progress: 0,
            createTime: '2025-01-22 08:45:00',
            updateTime: '2025-01-22 08:45:00'
        },
        {
            id: 'EQ001',
            name: '检测设备A',
            type: '光谱仪',
            status: '使用中',
            progress: 60,
            createTime: '2025-01-05 14:20:00',
            updateTime: '2025-01-20 11:30:00'
        },
        {
            id: 'EQ002',
            name: '检测设备B',
            type: '显微镜',
            status: '空闲',
            progress: 0,
            createTime: '2025-01-08 16:10:00',
            updateTime: '2025-01-19 09:15:00'
        }
    ]
    pagination.total = tableData.value.length
}
  // Tab é€‰æ‹©å™¨å½“前激活项
  const activeTab = ref("raw");
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
    if (sampleChartRef.value) {
        sampleChart = echarts.init(sampleChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item',
                formatter: '{a} <br/>{b}: {c} ({d}%)'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '样品状态',
                    type: 'pie',
                    radius: ['40%', '70%'],
                    avoidLabelOverlap: false,
                    label: {
                        show: false,
                        position: 'center'
                    },
                    emphasis: {
                        label: {
                            show: true,
                            fontSize: '18',
                            fontWeight: 'bold'
                        }
                    },
                    labelLine: {
                        show: false
                    },
                    data: [
                        { value: 450, name: '已完成' },
                        { value: 320, name: '检测中' },
                        { value: 280, name: '待检测' },
                        { value: 200, name: '已暂停' }
                    ]
                }
            ]
        }
        sampleChart.setOption(option)
    }
}
  // å›¾è¡¨å¼•用
  const sampleChartRef = ref(null);
  const equipmentChartRef = ref(null);
  const inspectionChartRef = ref(null);
  const usageChartRef = ref(null);
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
    if (equipmentChartRef.value) {
        equipmentChart = echarts.init(equipmentChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            xAxis: {
                type: 'category',
                data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
            },
            yAxis: {
                type: 'value',
                name: '使用率(%)'
            },
            series: [
                {
                    name: '使用率',
                    type: 'bar',
                    data: [85, 60, 75, 90, 45, 70],
                    itemStyle: {
                        color: function(params) {
                            const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
                            return colors[params.dataIndex]
                        }
                    }
                }
            ]
        }
        equipmentChart.setOption(option)
    }
}
  // è´¨æ£€åˆæ ¼çŽ‡å›¾è¡¨å¼•ç”¨
  const materialCompletionChart = ref(null);
  const materialQualityChart = ref(null);
  const semiCompletionChart = ref(null);
  const semiQualityChart = ref(null);
  const finalCompletionChart = ref(null);
  const finalQualityChart = ref(null);
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
    if (inspectionChartRef.value) {
        inspectionChart = echarts.init(inspectionChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '检测项目',
                    type: 'pie',
                    radius: '50%',
                    data: [
                        { value: 335, name: '物理性能' },
                        { value: 310, name: '化学分析' },
                        { value: 234, name: '尺寸测量' },
                        { value: 135, name: '外观检查' },
                        { value: 148, name: '其他检测' }
                    ],
                    emphasis: {
                        itemStyle: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        }
        inspectionChart.setOption(option)
    }
}
  // å›¾è¡¨å®žä¾‹
  let sampleChart = null;
  let equipmentChart = null;
  let inspectionChart = null;
  let usageChart = null;
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
    if (usageChartRef.value) {
        usageChart = echarts.init(usageChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis'
            },
            legend: {
                data: ['领用次数', '归还次数']
            },
            xAxis: {
                type: 'category',
                boundaryGap: false,
                data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
            },
            yAxis: {
                type: 'value'
            },
            series: [
                {
                    name: '领用次数',
                    type: 'line',
                    stack: 'Total',
                    data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
                },
                {
                    name: '归还次数',
                    type: 'line',
                    stack: 'Total',
                    data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
                }
            ]
        }
        usageChart.setOption(option)
    }
}
  // è´¨æ£€åˆæ ¼çŽ‡å›¾è¡¨å®žä¾‹
  let materialCompletionChartInstance = null;
  let materialQualityChartInstance = null;
  let semiCompletionChartInstance = null;
  let semiQualityChartInstance = null;
  let finalCompletionChartInstance = null;
  let finalQualityChartInstance = null;
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
    ElMessage.success('筛选条件已更新')
    // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
  // åˆå§‹åŒ–数据
  const initData = () => {
    // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
    tableData.value = [
      {
        id: "SP001",
        name: "样品A-001",
        type: "金属材料",
        status: "检测中",
        progress: 75,
        createTime: "2025-01-15 09:30:00",
        updateTime: "2025-01-20 14:20:00",
      },
      {
        id: "SP002",
        name: "样品B-002",
        type: "塑料制品",
        status: "已完成",
        progress: 100,
        createTime: "2025-01-10 10:15:00",
        updateTime: "2025-01-18 16:45:00",
      },
      {
        id: "SP003",
        name: "样品C-003",
        type: "电子元件",
        status: "待检测",
        progress: 0,
        createTime: "2025-01-22 08:45:00",
        updateTime: "2025-01-22 08:45:00",
      },
      {
        id: "EQ001",
        name: "检测设备A",
        type: "原材料",
        status: "使用中",
        progress: 60,
        createTime: "2025-01-05 14:20:00",
        updateTime: "2025-01-20 11:30:00",
      },
      {
        id: "EQ002",
        name: "检测设备B",
        type: "半成品",
        status: "空闲",
        progress: 0,
        createTime: "2025-01-08 16:10:00",
        updateTime: "2025-01-19 09:15:00",
      },
    ];
const resetFilter = () => {
    filterForm.dateRange = []
    filterForm.reportType = 'sample'
    ElMessage.info('筛选条件已重置')
}
    pagination.total = tableData.value.length;
  };
const exportReport = () => {
    ElMessage.success('报表导出功能开发中...')
}
  // åˆå§‹åŒ–样品进度图表
  const initSampleChart = () => {
    if (sampleChartRef.value) {
      sampleChart = echarts.init(sampleChartRef.value);
      const option = {
        title: {
          show: false,
        },
        tooltip: {
          trigger: "item",
          formatter: "{a} <br/>{b}: {c} ({d}%)",
        },
        // legend: {
        //   orient: "vertical",
        //   left: "left",
        // },
        series: [
          {
            name: "样品状态",
            type: "pie",
            radius: ["40%", "80%"],
            avoidLabelOverlap: false,
            label: {
              show: false,
              position: "center",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: "18",
                fontWeight: "bold",
              },
            },
            labelLine: {
              show: false,
            },
            data: [
              { value: 450, name: "已完成" },
              { value: 320, name: "检测中" },
              { value: 280, name: "待检测" },
              { value: 200, name: "已暂停" },
            ],
          },
        ],
      };
      sampleChart.setOption(option);
    }
  };
const refreshSampleChart = () => {
    initSampleChart()
    ElMessage.success('样品进度图表已刷新')
}
  // åˆå§‹åŒ–设备使用图表
  const initEquipmentChart = () => {
    if (equipmentChartRef.value) {
      equipmentChart = echarts.init(equipmentChartRef.value);
      const option = {
        title: {
          show: false,
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "shadow",
          },
        },
        grid: {
          left: "1%",
          right: "1%",
          bottom: "1%",
          containLabel: true,
        },
        legend: {
          data: ["原材料", "半成品", "成品"], // å›¾ä¾‹æ•°æ®
          icon: ["circle", "circle", "circle"],
          itemWidth: 10, // è®¾ç½®å›¾æ ‡å®½åº¦
          itemHeight: 10,
          itemGap: 30,
          right: 10,
        },
        xAxis: {
          type: "category",
          data: [
            value3.value.getFullYear() + "-1",
            value3.value.getFullYear() + "-2",
            value3.value.getFullYear() + "-3",
            value3.value.getFullYear() + "-4",
            value3.value.getFullYear() + "-5",
            value3.value.getFullYear() + "-6",
            value3.value.getFullYear() + "-7",
            value3.value.getFullYear() + "-8",
            value3.value.getFullYear() + "-9",
            value3.value.getFullYear() + "-10",
            value3.value.getFullYear() + "-11",
            value3.value.getFullYear() + "-12",
          ], // æ”¹ä¸ºåäºŒä¸ªæœˆ
        },
        yAxis: {
          type: "value",
          name: "数(个)",
        },
        series: [
          {
            name: "原材料",
            type: "bar",
            barWidth: "15%",
            data: [85, 75, 80, 85, 90, 88, 92, 87, 89, 91, 93, 95],
            itemStyle: {
              color: "#409EFF",
            },
          },
          {
            name: "半成品",
            type: "bar",
            barWidth: "15%",
const refreshEquipmentChart = () => {
    initEquipmentChart()
    ElMessage.success('设备使用图表已刷新')
}
            data: [60, 65, 70, 68, 72, 75, 78, 80, 79, 82, 84, 85],
            itemStyle: {
              color: "#67C23A",
            },
          },
          {
            name: "成品",
            type: "bar",
            barWidth: "15%",
const refreshInspectionChart = () => {
    initInspectionChart()
    ElMessage.success('检测项目图表已刷新')
}
            data: [75, 78, 80, 82, 85, 83, 86, 88, 87, 89, 90, 92],
            itemStyle: {
              color: "#E6A23C",
            },
          },
        ],
      };
      equipmentChart.setOption(option);
    }
  };
const refreshUsageChart = () => {
    initUsageChart()
    ElMessage.success('领用记录图表已刷新')
}
  // åˆå§‹åŒ–检测项目图表
  const initInspectionChart = () => {
    if (inspectionChartRef.value) {
      inspectionChart = echarts.init(inspectionChartRef.value);
      const option = {
        title: {
          show: false,
        },
        tooltip: {
          trigger: "item",
        },
        series: [
          {
            type: "pie",
            radius: "80%",
            data: [
              { value: 335, name: "原材料", itemStyle: { color: "#1890FF" } },
              { value: 310, name: "半成品", itemStyle: { color: "#F7BA1E" } },
              { value: 234, name: "成品", itemStyle: { color: "#14C9C9" } },
            ],
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: "rgba(0, 0, 0, 0.5)",
              },
            },
          },
        ],
      };
      inspectionChart.setOption(option);
    }
  };
const refreshTable = () => {
    tableLoading.value = true
    setTimeout(() => {
        tableLoading.value = false
        ElMessage.success('表格数据已刷新')
    }, 1000)
}
  // åˆå§‹åŒ–领用记录图表
  const initUsageChart = () => {
    // æ£€æŸ¥å›¾è¡¨å®¹å™¨æ˜¯å¦å­˜åœ¨
    if (usageChartRef.value) {
      // åˆå§‹åŒ– ECharts å®žä¾‹
      usageChart = echarts.init(usageChartRef.value);
      // é…ç½®å›¾è¡¨é€‰é¡¹
      const option = {
        // æ ‡é¢˜é…ç½®ï¼ˆéšè—ï¼‰
        title: {
          show: false,
        },
const exportTable = () => {
    ElMessage.success('表格导出功能开发中...')
}
        // ç½‘格配置(调整边距)
        grid: {
          left: "1%",
          right: "4%",
          bottom: "3%",
          top: "14%",
          containLabel: true,
        },
        // æç¤ºæ¡†é…ç½®
        tooltip: {
          trigger: "axis", // è§¦å‘类型为坐标轴触发
        },
        // å›¾ä¾‹é…ç½®
        legend: {
          data: ["原材料", "半成品", "成品"], // å›¾ä¾‹æ•°æ®
          icon: ["circle", "circle", "circle"],
          itemWidth: 10, // è®¾ç½®å›¾æ ‡å®½åº¦
          itemHeight: 10,
          itemGap: 30,
        },
        // X轴配置
        xAxis: {
          type: "category", // ç±»åˆ«è½´
          boundaryGap: false, // åæ ‡è½´ä¸¤è¾¹ç•™ç™½ç­–ç•¥
          data: [
            value3.value.getFullYear() + "-1",
            value3.value.getFullYear() + "-2",
            value3.value.getFullYear() + "-3",
            value3.value.getFullYear() + "-4",
            value3.value.getFullYear() + "-5",
            value3.value.getFullYear() + "-6",
            value3.value.getFullYear() + "-7",
            value3.value.getFullYear() + "-8",
            value3.value.getFullYear() + "-9",
            value3.value.getFullYear() + "-10",
            value3.value.getFullYear() + "-11",
            value3.value.getFullYear() + "-12",
          ], // X轴数据
        },
        // Y轴配置
        yAxis: {
          type: "value", // æ•°å€¼è½´
          name: "单位:%",
        },
        // ç³»åˆ—数据
        series: [
          {
            name: "原材料", // ç³»åˆ—名称
            type: "line", // å›¾è¡¨ç±»åž‹ä¸ºæŠ˜çº¿å›¾
            stack: "Total", // å †å åç§°
            symbol: "none",
            itemStyle: {
              color: "#1890FF", // è®¾ç½®è¿™æ¡çº¿çš„颜色
            },
            data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330], // é¢†ç”¨æ¬¡æ•°æ•°æ®
          },
          {
            name: "半成品", // ç³»åˆ—名称
            type: "line", // å›¾è¡¨ç±»åž‹ä¸ºæŠ˜çº¿å›¾
            stack: "Total", // å †å åç§°
            symbol: "none",
            itemStyle: {
              color: "#F7BA1E", // è®¾ç½®è¿™æ¡çº¿çš„颜色
            },
            data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320], // å½’还次数数据
          },
          {
            name: "成品", // ç³»åˆ—名称
            type: "line", // å›¾è¡¨ç±»åž‹ä¸ºæŠ˜çº¿å›¾
            stack: "Total", // å †å åç§°
            symbol: "none",
            itemStyle: {
              color: "#14C9C9", // è®¾ç½®è¿™æ¡çº¿çš„颜色
            },
            data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320], // å½’还次数数据
          },
        ],
      };
      // å°†é…ç½®åº”用到图表
      usageChart.setOption(option);
    }
  };
const handleSizeChange = (val) => {
    pagination.pageSize = val
    // é‡æ–°åŠ è½½æ•°æ®
}
  // åˆå§‹åŒ–质检合格率图表
  const initQualityChart = (chartRef, color) => {
    if (chartRef.value) {
      const chart = echarts.init(chartRef.value);
      const option = {
        series: [
          {
            type: "pie",
            radius: ["45%", "90%"],
            itemStyle: {
              borderColor: "#f5f5f5",
              // borderWidth: 2,
            },
            labelLine: {
              show: false,
            },
            data: [
              { value: 0.8, itemStyle: { color: color } },
              { value: 0.2, itemStyle: { color: "#f5f5f5" } },
            ],
          },
        ],
      };
      chart.setOption(option);
      return chart;
    }
    return null;
  };
const handleCurrentChange = (val) => {
    pagination.currentPage = val
    // é‡æ–°åŠ è½½æ•°æ®
}
  // åˆå§‹åŒ–所有质检合格率图表
  const initAllQualityCharts = () => {
    materialCompletionChartInstance = initQualityChart(
      materialCompletionChart,
      "#1890ff"
    );
    materialQualityChartInstance = initQualityChart(
      materialQualityChart,
      "#52c41a"
    );
    semiCompletionChartInstance = initQualityChart(
      semiCompletionChart,
      "#1890ff"
    );
    semiQualityChartInstance = initQualityChart(semiQualityChart, "#52c41a");
    finalCompletionChartInstance = initQualityChart(
      finalCompletionChart,
      "#1890ff"
    );
    finalQualityChartInstance = initQualityChart(finalQualityChart, "#722ed1");
  };
const getStatusType = (status) => {
    const statusMap = {
        '已完成': 'success',
        '检测中': 'warning',
        '待检测': 'info',
        '已暂停': 'danger',
        '使用中': 'primary',
        '空闲': 'info'
    }
    return statusMap[status] || 'info'
}
  // äº‹ä»¶å¤„理函数
  const handleFilterChange = () => {
    ElMessage.success("筛选条件已更新");
    // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
  };
const getProgressStatus = (progress) => {
    if (progress === 100) return 'success'
    if (progress >= 80) return 'warning'
    if (progress >= 50) return ''
    return 'exception'
}
  const resetFilter = () => {
    filterForm.dateRange = [];
    filterForm.reportType = "sample";
    ElMessage.info("筛选条件已重置");
  };
const viewDetail = (row) => {
    ElMessage.info(`查看详情: ${row.name}`)
}
  const exportReport = () => {
    ElMessage.success("报表导出功能开发中...");
  };
const editItem = (row) => {
    ElMessage.info(`编辑项目: ${row.name}`)
}
  const refreshSampleChart = () => {
    initSampleChart();
    ElMessage.success("样品进度图表已刷新");
  };
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
    initData()
    nextTick(() => {
        initSampleChart()
        initEquipmentChart()
        initInspectionChart()
        initUsageChart()
    })
    // ç›‘听窗口大小变化,重新调整图表大小
    window.addEventListener('resize', () => {
        sampleChart?.resize()
        equipmentChart?.resize()
        inspectionChart?.resize()
        usageChart?.resize()
    })
})
  const refreshEquipmentChart = () => {
    initEquipmentChart();
    ElMessage.success("设备使用图表已刷新");
  };
  const refreshInspectionChart = () => {
    initInspectionChart();
    ElMessage.success("检测项目图表已刷新");
  };
  const refreshUsageChart = () => {
    initUsageChart();
    ElMessage.success("领用记录图表已刷新");
  };
  const refreshTable = () => {
    tableLoading.value = true;
    setTimeout(() => {
      tableLoading.value = false;
      ElMessage.success("表格数据已刷新");
    }, 1000);
  };
  const exportTable = () => {
    ElMessage.success("表格导出功能开发中...");
  };
  const handleSizeChange = val => {
    pagination.pageSize = val;
    // é‡æ–°åŠ è½½æ•°æ®
  };
  const handleCurrentChange = val => {
    pagination.currentPage = val;
    // é‡æ–°åŠ è½½æ•°æ®
  };
  const getStatusType = status => {
    const statusMap = {
      å·²å®Œæˆ: "success",
      æ£€æµ‹ä¸­: "warning",
      å¾…检测: "info",
      å·²æš‚停: "danger",
      ä½¿ç”¨ä¸­: "primary",
      ç©ºé—²: "info",
    };
    return statusMap[status] || "info";
  };
  const getProgressStatus = progress => {
    if (progress === 100) return "success";
    if (progress >= 80) return "warning";
    if (progress >= 50) return "";
    return "exception";
  };
  const viewDetail = row => {
    ElMessage.info(`查看详情: ${row.name}`);
  };
  const editItem = row => {
    ElMessage.info(`编辑项目: ${row.name}`);
  };
  // ç”Ÿå‘½å‘¨æœŸ
  onMounted(() => {
    initData();
    nextTick(() => {
      initSampleChart();
      initEquipmentChart();
      initInspectionChart();
      initUsageChart();
      initAllQualityCharts();
    });
    // ç›‘听窗口大小变化,重新调整图表大小
    window.addEventListener("resize", () => {
      sampleChart?.resize();
      equipmentChart?.resize();
      inspectionChart?.resize();
      usageChart?.resize();
      // è°ƒæ•´è´¨æ£€åˆæ ¼çŽ‡å›¾è¡¨å¤§å°
      materialCompletionChartInstance?.resize();
      materialQualityChartInstance?.resize();
      semiCompletionChartInstance?.resize();
      semiQualityChartInstance?.resize();
      finalCompletionChartInstance?.resize();
      finalQualityChartInstance?.resize();
    });
  });
</script>
<style scoped>
.report-management {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
}
  .report-management {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
    /* height: 87vh;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              overflow: hidden; */
  }
.page-header {
    margin-bottom: 20px;
    text-align: center;
}
  .page-header {
    margin-bottom: 20px;
    text-align: center;
  }
.page-header h2 {
    color: #303133;
    margin-bottom: 8px;
    font-size: 24px;
    font-weight: 600;
}
  .page-header h2 {
    color: #303133;
    margin-bottom: 8px;
    font-size: 24px;
    font-weight: 600;
  }
.page-header p {
    color: #909399;
    font-size: 14px;
    margin: 0;
}
  .page-header p {
    color: #909399;
    font-size: 14px;
    margin: 0;
  }
.filter-card {
    margin-bottom: 20px;
}
  .filter-card {
    margin-bottom: 20px;
  }
.statistics-cards {
    margin-bottom: 20px;
}
  .statistics-cards {
    margin-bottom: 20px;
  }
.stat-card {
    height: 120px;
}
  .stat-card {
    height: 120px;
  }
.stat-content {
    display: flex;
    align-items: center;
    height: 100%;
}
  .stat-content {
    display: flex;
    align-items: center;
    height: 100%;
  }
.stat-icon {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    font-size: 24px;
    color: white;
}
  .stat-icon {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    font-size: 24px;
    color: white;
  }
.stat-card:nth-child(1) .stat-icon {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
  .stat-card:nth-child(1) .stat-icon {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  }
.stat-card:nth-child(2) .stat-icon {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
  .stat-card:nth-child(2) .stat-icon {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  }
.stat-card:nth-child(3) .stat-icon {
    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
  .stat-card:nth-child(3) .stat-icon {
    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  }
.stat-card:nth-child(4) .stat-icon {
    background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
  .stat-card:nth-child(4) .stat-icon {
    background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  }
.stat-info {
    flex: 1;
}
  .stat-info {
    flex: 1;
  }
.stat-number {
    font-size: 28px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 8px;
}
  .stat-number {
    font-size: 28px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 8px;
  }
.stat-label {
    font-size: 14px;
    color: #909399;
}
  .stat-label {
    font-size: 14px;
    color: #909399;
  }
.charts-container {
    margin-bottom: 20px;
}
  .charts-container {
    /* margin-bottom: 20px; */
    position: relative;
  }
.chart-card {
    margin-bottom: 20px;
}
  .chart-card {
    margin-bottom: 20px;
  }
  .container-line-right-bottom {
    height: 20%;
    width: 100%;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    /* background-color: #5b3f3f; */
  }
  .card-header {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    font-family: Source Han Sans, Source Han Sans;
    font-weight: 700;
    font-size: 18px;
    color: #000000;
    /* line-height: 27px; */
    text-align: left;
    font-style: normal;
    text-transform: none;
  }
  .chart-title-line {
    width: 6px;
    height: 22px;
    background-color: #161a9a;
    margin-right: 16px;
    border-radius: 3px;
  }
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
  .chart-container {
    height: 250px;
    width: 100%;
  }
  .chart-container-line {
    height: 250px;
    width: 100%;
    display: flex;
    position: relative;
  }
.chart-container {
    height: 300px;
    width: 100%;
}
  /* Tab é€‰æ‹©å™¨æ ·å¼ */
  .tab-selector {
    position: absolute;
    top: 20px;
    right: 40px;
    display: flex;
    border: 1px solid #dcdfe6;
    border-radius: 4px;
    overflow: hidden;
  }
.table-card {
    margin-bottom: 20px;
}
  .tab-item {
    padding: 4px 12px;
    cursor: pointer;
    font-size: 14px;
    color: #606266;
    background-color: #fff;
    border-right: 1px solid #dcdfe6;
    transition: all 0.3s;
  }
.pagination-container {
    margin-top: 20px;
    text-align: right;
}
  .tab-item:last-child {
    border-right: none;
  }
:deep(.el-card__header) {
    padding: 15px 20px;
    border-bottom: 1px solid #ebeef5;
    background-color: #fafafa;
}
  .tab-item:hover {
    color: #409eff;
  }
:deep(.el-card__body) {
    padding: 20px;
}
  .tab-item.active {
    color: #fff;
    background-color: #409eff;
  }
  .container-line-left {
    height: 100%;
    width: 66%;
  }
  .container-line-right {
    height: 100%;
    width: 34%;
  }
  .container-line2-left {
    height: 100%;
    width: 50%;
  }
  .info-box {
    width: 92%;
    margin-left: 4%;
    height: 100%;
    background-color: #f7f8fa;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-around;
  }
  .info-box-header {
    width: 100%;
    margin-left: 20px;
    color: #1d2129;
    font-size: 16px;
    font-weight: 500;
  }
:deep(.el-table) {
    margin-bottom: 20px;
}
  .info-line {
    width: 100%;
    display: flex;
    padding-left: 20px;
    align-items: center;
  }
  .info-icon {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-right: 8px;
  }
  .info-line-title {
    font-size: 12px;
    color: #4e5969;
    flex: 1;
  }
  .info-line-value1 {
    font-size: 12px;
    color: #3d3d3d;
    color: #1d2129;
    font-weight: 500;
    margin-right: 15%;
  }
  .info-line-value2 {
    font-size: 12px;
    color: #3d3d3d;
    color: #1d2129;
    font-weight: 500;
    margin-right: 10%;
  }
  .top-container {
    height: 130px;
    width: 100%;
    display: flex;
  }
  .typeNum {
    height: 100%;
    width: 33.33%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .typeNum-left {
    font-size: 12px;
    color: #909399;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
  .typeNum-left-text {
    font-size: 12px;
    color: #3491fa;
    font-weight: 500;
    margin-top: 5px;
  }
  .table-card {
    margin-bottom: 20px;
  }
  .typeNum-center {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-left: 10px;
  }
  .typeNum-leftLine {
    color: #3491fa;
    font-size: 12px;
  }
  .typeNum-rightLine {
    border-top: 1px solid #3491fa;
    border-left: 1px solid #3491fa;
    border-bottom: 1px solid #3491fa;
    height: 80px;
    width: 8px;
  }
  .typeNum-leftLine2 {
    color: #5eb334;
    font-size: 12px;
  }
  .typeNum-rightLine2 {
    border-top: 1px solid #3491fa;
    border-left: 1px solid #5eb334;
    border-bottom: 1px solid #5eb334;
    height: 80px;
    width: 8px;
  }
  .typeNum-leftLine3 {
    color: #8000ff;
    font-size: 12px;
  }
  .typeNum-rightLine3 {
    border-top: 1px solid #8000ff;
    border-left: 1px solid #8000ff;
    border-bottom: 1px solid #8000ff;
    height: 80px;
    width: 8px;
  }
  .typeNum-right {
    margin-left: 10px;
    display: flex;
    flex-direction: column;
    height: 90%;
    justify-content: space-between;
  }
  .typeNum-right-top-name {
    font-weight: 400;
    font-size: 12px;
    color: #3d3d3d;
  }
  .typeNum-right-top-text {
    font-weight: 400;
    font-size: 16px;
    color: rgba(0, 0, 0, 0.85);
    margin-top: 5px;
  }
  .unit {
    font-size: 12px;
    color: #3d3d3d;
  }
  .inspection-chart-box {
    height: 50px;
    width: 30%;
    background-color: #f7f8fa;
    border-radius: 8px;
    padding-left: 15px;
  }
  .chart-box-title {
    font-size: 12px;
    color: #4e5969;
    margin-top: 5px;
  }
  .unit {
    font-size: 12px;
    color: #3d3d3d;
  }
  .chart-box-num {
    font-size: 18px;
    color: #000;
    margin-top: 5px;
    font-weight: 500;
  }
  /* è´¨æ£€åˆæ ¼çŽ‡å¡ç‰‡æ ·å¼ */
  .top-container合格率 {
    height: 130px;
    width: 100%;
    display: flex;
    gap: 15px;
    align-items: center;
    justify-content: space-between;
  }
  .flex-center {
    justify-content: space-evenly;
  }
  .quality-card {
    /* flex: 1; */
    width: 32%;
    /* height: 100px; */
    border-radius: 8px;
    padding: 12px;
    display: flex;
    flex-direction: column;
  }
:deep(.el-progress) {
    margin: 0;
}
  .blue-card {
    background-color: #e6f7ff;
  }
:deep(.el-tag) {
    margin: 0;
}
  .green-card {
    background-color: #f6ffed;
    color: #000000;
  }
  .purple-card {
    background-color: #f9f0ff;
  }
  .quality-card-title {
    font-size: 14px;
    font-weight: 500;
    margin-bottom: 10px;
    display: flex;
    align-items: center;
  }
  .quality-item-tip {
    font-size: 12px;
    color: #666666;
    margin-bottom: 3px;
  }
  .blue-label {
    color: #1890ff;
  }
  .green-label {
    color: #52c41a;
  }
  .quality-card-title {
    color: #000;
    font-weight: bold;
  }
  .quality-card-content {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex: 1;
  }
  .quality-item {
    display: flex;
    /* flex-direction: column; */
    align-items: center;
    justify-content: center;
    margin-top: 5px;
    flex: 1;
  }
  .quality-item-label {
    font-size: 12px;
    /* color: #666; */
    margin-bottom: 4px;
  }
  .quality-item-value {
    font-size: 20px;
    font-weight: 500;
    margin-bottom: 4px;
  }
  .quality-item-chart {
    width: 60px;
    height: 60px;
    margin-left: 10px;
  }
  /* .flex-center {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            justify-content: space-evenly;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          } */
  .blue-chart {
    /* background-color: rgba(24, 144, 255, 0.1); */
  }
  .green-chart {
    /* background-color: rgba(82, 196, 26, 0.1); */
  }
  .purple-chart {
    /* background-color: rgba(114, 46, 209, 0.1); */
  }
  .chart-ring {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    border: 15px solid transparent;
    position: relative;
  }
  .blue-chart .chart-ring {
    border-top-color: #1890ff;
    border-right-color: #1890ff;
    border-bottom-color: #1890ff;
    transform: rotate(45deg);
  }
  .green-chart .chart-ring {
    border-top-color: #52c41a;
    border-right-color: #52c41a;
    border-bottom-color: #52c41a;
    transform: rotate(45deg);
  }
  .purple-chart .chart-ring {
    border-top-color: #722ed1;
    border-right-color: #722ed1;
    border-bottom-color: #722ed1;
    transform: rotate(45deg);
  }
  .pagination-container {
    margin-top: 20px;
    text-align: right;
  }
  .yearchange {
    position: absolute;
    right: 40px;
    top: 20px;
    display: flex;
    align-items: center;
    /* width: 60px; */
  }
  :deep(.el-card__header) {
    padding: 15px 20px;
    border-bottom: 1px solid #ffffff;
    background-color: #ffffff;
  }
  :deep(.el-card__body) {
    padding: 20px;
  }
  :deep(.el-table) {
    margin-bottom: 20px;
  }
  :deep(.el-progress) {
    margin: 0;
  }
  :deep(.el-tag) {
    margin: 0;
  }
  :deep(.el-input__prefix) {
    display: none !important;
  }
</style>
src/views/salesManagement/salesLedger/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <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">
        <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>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  open
})
</script>
<style></style>
src/views/salesManagement/salesLedger/index.vue
@@ -6,8 +6,16 @@
          <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search"
            @change="handleQuery" />
        </el-form-item>
        <el-form-item label="客户合同号:">
          <el-input v-model="searchForm.customerContractNo" placeholder="请输入" clearable prefix-icon="Search"
            @change="handleQuery" />
        </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 label="录入日期:">
@@ -33,7 +41,7 @@
      </div>
      <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" @expand-change="expandChange" height="calc(100vh - 21em)">
        :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand">
          <template #default="props">
@@ -42,67 +50,72 @@
              <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>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="销售合同号" prop="salesContractNo" width="180" show-overflow-tooltip />
        <el-table-column label="客户合同号" prop="customerContractNo" width="180" show-overflow-tooltip />
        <el-table-column label="客户名称" prop="customerName" width="300" show-overflow-tooltip />
        <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip />
        <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip />
        <el-table-column label="付款方式" prop="paymentMethod" show-overflow-tooltip />
        <el-table-column label="合同金额(元)" prop="contractAmount" width="220" show-overflow-tooltip
          :formatter="formattedNumber" />
        <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip />
                <el-table-column label="生产状态" prop="productionStatus" width="100" show-overflow-tooltip >
                    <template #default="scope">
                        <div>
                            <el-tag v-if="scope.row.productionStatus === '已完成'" type="success">已完成</el-tag>
                            <el-tag v-if="scope.row.productionStatus === '生产中'" type="warning">生产中</el-tag>
                            <el-tag v-if="scope.row.productionStatus === '未开始'" type="danger">未开始</el-tag>
                        </div>
                    </template>
                </el-table-column>
        <el-table-column label="发货车牌" prop="shippingCarNumber" width="120" show-overflow-tooltip>
          <template #default="scope">
            <div>
              <div v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</div>
              <el-tag v-else type="warning">未发货</el-tag>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="发货日期" prop="shippingDate" width="120" show-overflow-tooltip />
        <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip />
        <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="200" align="center">
        <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="100" align="center">
          <template #default="scope">
            <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 v-if="!scope.row.shippingCarNumber" 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>
    <FormDialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'"
      :width="'70%'"
      :operation-type="operationType"
      @close="closeDia"
      @confirm="submitForm"
      @cancel="closeDia">
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'" width="70%"
      @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row v-if="operationType !== 'view'">
          <el-col :span="24" style="display:flex; justify-content:flex-end; gap:10px; margin-bottom: 6px;">
            <el-button type="primary" plain @click="openQuotationDialog">从审批通过的报价单导入</el-button>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="销售合同号:" prop="salesContractNo">
@@ -111,9 +124,7 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="业务员:" prop="salesman">
              <el-select v-model="form.salesman"
                                                 filterable
                         :reserve-keyword="false" placeholder="请选择" clearable :disabled="operationType === 'view'">
              <el-select v-model="form.salesman" placeholder="请选择" clearable :disabled="operationType === 'view'">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                  :value="item.nickName" />
              </el-select>
@@ -122,14 +133,26 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="客户合同号:" prop="customerContractNo">
              <el-input v-model="form.customerContractNo" placeholder="请输入" clearable :disabled="operationType === 'view'"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户名称:" prop="customerId">
              <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'" filterable>
              <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'">
                <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
                  {{
                    item.customerName + "——" + item.taxpayerIdentificationNumber
                  }}
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
@@ -142,10 +165,7 @@
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="录入人:" prop="entryPerson">
                            <el-select v-model="form.entryPerson"
                                                 filterable
                         default-first-option
                         :reserve-keyword="false" placeholder="请选择" clearable @change="changs">
                            <el-select v-model="form.entryPerson" placeholder="请选择" clearable @change="changs" disabled>
                                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
                            </el-select>
                        </el-form-item>
@@ -157,7 +177,13 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <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-row>
        <el-row>
          <el-form-item label="产品信息:" prop="entryDate">
            <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button>
@@ -207,71 +233,14 @@
          </el-col>
        </el-row>
      </el-form>
    </FormDialog>
    <!-- ä»ŽæŠ¥ä»·å•导入(仅审批通过) -->
    <el-dialog
      v-model="quotationDialogVisible"
      title="选择审批通过的销售报价单"
      width="80%"
      :close-on-click-modal="false"
    >
      <div style="margin-bottom: 12px; display:flex; gap: 12px; align-items:center;">
        <el-input
          v-model="quotationSearchForm.quotationNo"
          placeholder="请输入报价单号"
          clearable
          style="max-width: 260px;"
          @change="fetchQuotationList"
        />
        <el-input
          v-model="quotationSearchForm.customer"
          placeholder="请输入客户名称"
          clearable
          style="max-width: 260px;"
          @change="fetchQuotationList"
        />
        <el-button type="primary" @click="fetchQuotationList">搜索</el-button>
        <el-button @click="resetQuotationSearch">重置</el-button>
      </div>
      <el-table
        :data="quotationList"
        border
        stripe
        v-loading="quotationLoading"
        height="420px"
      >
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column prop="quotationNo" label="报价单号" width="180" show-overflow-tooltip />
        <el-table-column prop="customer" label="客户名称" min-width="220" show-overflow-tooltip />
        <el-table-column prop="salesperson" label="业务员" width="120" show-overflow-tooltip />
        <el-table-column prop="quotationDate" label="报价日期" width="140" />
        <el-table-column prop="status" label="审批状态" width="120" align="center" />
        <el-table-column prop="totalAmount" label="报价金额(元)" width="160" align="right">
          <template #default="scope">
            {{ Number(scope.row.totalAmount ?? 0).toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" width="120" align="center">
          <template #default="scope">
            <el-button type="primary" link @click="applyQuotation(scope.row)">选择</el-button>
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <el-button @click="quotationDialogVisible = false">关闭</el-button>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <FormDialog
      v-model="productFormVisible"
      :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
      :width="'40%'"
      :operation-type="productOperationType"
      @close="closeProductDia"
      @confirm="submitProduct"
      @cancel="closeProductDia">
    <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">
        <el-row :gutter="30">
          <el-col :span="24">
@@ -287,7 +256,7 @@
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel" filterable>
              <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel">
                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
              </el-select>
            </el-form-item>
@@ -348,7 +317,13 @@
          </el-col>
        </el-row>
      </el-form>
    </FormDialog>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitProduct">确认</el-button>
          <el-button @click="closeProductDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
        <!-- æ‰“印预览弹窗 -->
        <el-dialog
            v-model="printPreviewVisible"
@@ -383,15 +358,12 @@
                                        <span class="value">{{ formatDate(item.createTime) }}</span>
                                    </div>
                                    <div>
                                        <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>
                                </div>
                                <div class="info-row">
                                    <span class="label">单号:</span>
                                    <span class="value">{{ item.salesContractNo }}</span>
                                </div>
@@ -500,6 +472,15 @@
                        </el-form-item>
                    </el-col>
                </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="审批人:" prop="approverId">
              <el-select v-model="deliveryForm.approverId" placeholder="请选择审批人" clearable :disabled="operationType === 'view'">
                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
@@ -509,35 +490,70 @@
            </template>
        </el-dialog>
    <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" />
    <!-- å¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog
      :title="importUpload.title"
      v-model="importUpload.open"
      width="400px"
      append-to-body
    >
      <el-upload
        ref="importUploadRef"
        :limit="1"
        accept=".xlsx, .xls"
        :headers="importUpload.headers"
        :action="importUpload.url"
        :disabled="importUpload.isUploading"
        :before-upload="importUpload.beforeUpload"
        :on-progress="importUpload.onProgress"
        :on-success="importUpload.onSuccess"
        :on-error="importUpload.onError"
        :on-change="importUpload.onChange"
        :auto-upload="false"
        drag
      >
        <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitImportFile" :loading="importUpload.isUploading">ç¡® å®š</el-button>
          <el-button @click="importUpload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<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 FileListDialog from '@/components/Dialog/FileListDialog.vue';
import FormDialog from '@/components/Dialog/FormDialog.vue';
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import FileList from "./fileList.vue";
import {
    ledgerListPage,
    productList,
    customerList,
    addOrUpdateSalesLedger,
    getSalesLedgerWithProducts,
    delLedger,
    addOrUpdateSalesLedgerProduct,
    delProduct,
    delLedgerFile, getProductInventory,
  ledgerListPage,
  productList,
  customerList,
  addOrUpdateSalesLedger,
  getSalesLedgerWithProducts,
  delLedger,
  addOrUpdateSalesLedgerProduct,
  delProduct,
  delLedgerFile, getProductInventory,
} from "@/api/salesManagement/salesLedger.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();
@@ -563,7 +579,9 @@
const data = reactive({
  searchForm: {
    customerName: "", // å®¢æˆ·åç§°
    customerContractNo: "", // å®¢æˆ·åˆåŒç¼–号
    salesContractNo: "", // é”€å”®åˆåŒç¼–号
    projectName: "", // é¡¹ç›®åç§°
    entryDate: null, // å½•入日期
    entryDateStart: undefined,
    entryDateEnd: undefined,
@@ -571,16 +589,23 @@
  form: {
    salesContractNo: "",
    salesman: "",
    customerContractNo: "",
    customerId: "",
    projectName: "",
    entryPerson: "",
    entryDate: "",
    maintenanceTime: "",
    productData: [],
    executionDate: "",
    paymentMethod: "",
  },
  rules: {
    salesman: [{ required: true, message: "请选择", trigger: "change" }],
    customerContractNo: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    customerId: [{ required: true, message: "请选择", trigger: "change" }],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    entryPerson: [{ required: true, message: "请选择", trigger: "change" }],
    entryDate: [{ required: true, message: "请选择", trigger: "change" }],
    executionDate: [{ required: true, message: "请选择", trigger: "change" }],
@@ -638,16 +663,6 @@
const printPreviewVisible = ref(false);
const printData = ref([]);
// æŠ¥ä»·å•导入相关
const quotationDialogVisible = ref(false);
const quotationLoading = ref(false);
const quotationList = ref([]);
const quotationSearchForm = reactive({
  quotationNo: "",
  customer: "",
});
const selectedQuotation = ref(null);
// å‘货相关
const deliveryFormVisible = ref(false);
const currentDeliveryRow = ref(null);
@@ -663,9 +678,62 @@
    shippingCarNumber: [
      { required: true, message: "请输入发货车牌号", trigger: "blur" }
    ],
    approverId:[
      {
        required: true,message: "",
      }
    ]
  },
});
const { deliveryForm, deliveryRules } = toRefs(deliveryFormData);
// å¯¼å…¥ç›¸å…³
const importUploadRef = ref(null);
const importUpload = reactive({
  title: "导入销售台账",
  open: false,
  url: import.meta.env.VITE_APP_BASE_API + "/sales/ledger/import",
  headers: { Authorization: "Bearer " + getToken() },
  isUploading: false,
  beforeUpload: (file) => {
    const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
    const isLt10M = file.size / 1024 / 1024 < 10;
    if (!isExcel) {
      proxy.$modal.msgError("上传文件只能是 xlsx/xls æ ¼å¼!");
      return false;
    }
    if (!isLt10M) {
      proxy.$modal.msgError("上传文件大小不能超过 10MB!");
      return false;
    }
    return true;
  },
  onChange: (file, fileList) => {
    console.log('文件状态改变', file, fileList);
  },
  onProgress: (event, file, fileList) => {
    console.log('上传中...', event.percent);
  },
  onSuccess: (response, file, fileList) => {
    console.log('上传成功', response, file, fileList);
    importUpload.isUploading = false;
    if (response.code === 200) {
      proxy.$modal.msgSuccess("导入成功");
      importUpload.open = false;
      if (importUploadRef.value) {
        importUploadRef.value.clearFiles();
      }
      getList();
    } else {
      proxy.$modal.msgError(response.msg || "导入失败");
    }
  },
  onError: (error, file, fileList) => {
    console.error('上传失败', error, file, fileList);
    importUpload.isUploading = false;
    proxy.$modal.msgError("导入失败,请重试");
  },
});
const changeDaterange = (value) => {
  if (value) {
@@ -681,11 +749,7 @@
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  // åªæœ‰åœ¨ç‚¹å‡»æœç´¢æŒ‰é’®æ—¶æ‰é‡ç½®é¡µç åˆ°ç¬¬ä¸€é¡µ
  // é¿å…è¡¨å•字段change事件干扰分页
  if (arguments.length === 0) {
    page.current = 1;
  }
  page.current = 1;
    expandedRowKeys.value = [];
  getList();
};
@@ -694,14 +758,12 @@
  page.size = obj.limit;
  getList();
};
const getList = () => {
const getList =async () => {
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  tableLoading.value = true;
  const { entryDate, ...rest } = searchForm;
  // å°†èŒƒå›´æ—¥æœŸå­—段传递给后端
  const params = { ...rest, ...page };
  // ç§»é™¤å½•入日期的默认值设置,只保留范围日期字段
  delete params.entryDate;
  ledgerListPage(params)
  ledgerListPage({ ...rest, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.records;
@@ -716,10 +778,8 @@
};
// èŽ·å–äº§å“å¤§ç±»tree数据
const getProductOptions = () => {
  // è¿”回 Promise,便于在编辑产品时等待加载完成
  return productTreeList().then((res) => {
  productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
    return productOptions.value;
  });
};
const formattedNumber = (row, column, cellValue) => {
@@ -757,6 +817,7 @@
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  if (!data || !Array.isArray(data)) return [];
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
@@ -770,19 +831,6 @@
    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) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
@@ -793,23 +841,31 @@
  productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = (row, expandedRows) => {
  if (expandedRows.length > 0) {
// å±•开行(始终只展开一行)
const expandChange = (row) => {
  const rowKey = row.id;
  const isExpanded = expandedRowKeys.value.includes(rowKey);
  if (isExpanded) {
    // å½“前行已展开 -> æ”¶èµ·
    expandedRowKeys.value = [];
    try {
      productList({ salesLedgerId: row.id, type: 1 }).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 = [];
    return;
  }
  // å±•开当前行前,先收起其它行
  expandedRowKeys.value = [];
  try {
    productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
      const index = tableData.value.findIndex((item) => item.id === row.id);
      if (index > -1) {
        tableData.value[index].children = res.data;
      }
      // åªä¿ç•™å½“前这一行处于展开状态
      expandedRowKeys.value = [rowKey];
    });
  } catch (error) {
    console.log(error);
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
@@ -833,19 +889,11 @@
  operationType.value = type;
  form.value = {};
  productData.value = [];
  selectedQuotation.value = null;
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  customerList().then((res) => {
    customerOption.value = res;
  });
  form.value.entryPerson = userStore.id;
  if (type === "add") {
    // æ–°å¢žæ—¶è®¾ç½®å½•入日期为当天
    form.value.entryDate = getCurrentDate();
    // ç­¾è®¢æ—¥æœŸé»˜è®¤ä¸ºå½“天
    form.value.executionDate = getCurrentDate();
  } else {
  if (type !== "add") {
    currentId.value = row.id;
    getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
      form.value = { ...res };
@@ -862,87 +910,6 @@
  // });
  form.value.entryDate = getCurrentDate(); // è®¾ç½®é»˜è®¤å½•入日期为当前日期
  dialogFormVisible.value = true;
};
// æ‰“开报价单选择弹窗(仅审批通过)
const openQuotationDialog = async () => {
  if (operationType.value === "view") return;
  quotationDialogVisible.value = true;
  // å…ˆç¡®ä¿å®¢æˆ·åˆ—表已加载,便于后续回填 customerId
  if (!customerOption.value || customerOption.value.length === 0) {
    try {
      const res = await customerList();
      customerOption.value = res;
    } catch (e) {
      // ignore,允许用户后续手动选择客户
    }
  }
  await fetchQuotationList();
};
const fetchQuotationList = async () => {
  quotationLoading.value = true;
  try {
    const params = {
      // å…¼å®¹åŽç«¯åˆ†é¡µå­—段:这里沿用报价页面已有可用的字段命名
      currentPage: 1,
      pageSize: 100,
      ...quotationSearchForm,
      status: "通过",
    };
    const res = await getQuotationList(params);
    quotationList.value = res?.data?.records || [];
  } finally {
    quotationLoading.value = false;
  }
};
const resetQuotationSearch = async () => {
  quotationSearchForm.quotationNo = "";
  quotationSearchForm.customer = "";
  await fetchQuotationList();
};
// é€‰ä¸­æŠ¥ä»·å•后回填到台账表单
const applyQuotation = (row) => {
  if (!row) return;
  selectedQuotation.value = row;
  // ä¸šåŠ¡å‘˜
  form.value.salesman = row.salesperson || "";
  // å®¢æˆ·åç§° -> customerId
  const customer = (customerOption.value || []).find((c) => c.customerName === row.customer);
  if (customer?.id) {
    form.value.customerId = customer.id;
  } else {
    // å¦‚果找不到,保留原值(允许用户手动选择/不打断已有输入)
    form.value.customerId = form.value.customerId || "";
  }
  // äº§å“ä¿¡æ¯æ˜ å°„:报价 products -> å°è´¦ productData
  const products = Array.isArray(row.products) ? row.products : [];
  productData.value = products.map((p) => {
    const quantity = Number(p.quantity ?? 0) || 0;
    const unitPrice = Number(p.unitPrice ?? 0) || 0;
    const taxRate = "13"; // é»˜è®¤ 13%,便于直接提交(如需可在产品中自行修改)
    const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(taxInclusiveTotalPrice, taxRate);
    return {
      // å°è´¦å­—段
      productCategory: p.product || p.productName || "",
      specificationModel: p.specification || "",
      unit: p.unit || "",
      quantity: quantity,
      taxRate: taxRate,
      taxInclusiveUnitPrice: unitPrice.toFixed(2),
      taxInclusiveTotalPrice: taxInclusiveTotalPrice,
      taxExclusiveTotalPrice: taxExclusiveTotalPrice,
      invoiceType: "增普票",
    };
  });
  quotationDialogVisible.value = false;
};
function changs(val) {
  console.log(val);
@@ -1016,36 +983,22 @@
const productIndex = ref(0);
// æ‰“开产品弹框
const openProductForm = async (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();
};
// æäº¤äº§å“è¡¨å•
const submitProduct = () => {
@@ -1121,6 +1074,21 @@
  proxy.resetForm("productFormRef");
  productFormVisible.value = false;
};
// å¯¼å…¥
const handleImport = () => {
  importUpload.title = "导入销售台账";
  importUpload.open = true;
  if (importUploadRef.value) {
    importUploadRef.value.clearFiles();
  }
};
// æäº¤å¯¼å…¥æ–‡ä»¶
const submitImportFile = () => {
  importUpload.isUploading = true;
  proxy.$refs["importUploadRef"].submit();
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
@@ -1486,6 +1454,15 @@
    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';
@@ -1698,28 +1675,20 @@
 * @param row ä¸‹è½½æ–‡ä»¶çš„相关信息对象
 */
const fileListRef = ref(null)
const fileListDialogVisible = ref(false)
const downLoadFile = (row) => {
  getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
    if (fileListRef.value) {
      fileListRef.value.open(res.salesLedgerFiles)
      fileListDialogVisible.value = true
    }
    fileListRef.value.open(res.salesLedgerFiles)
  });
}
// æ‰“开发货弹框
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);
  });
  currentDeliveryRow.value = row;
  deliveryForm.value = {
    shippingDate: "", // ç§»é™¤é»˜è®¤å€¼è®¾ç½®
    shippingCarNumber: "",
  };
  deliveryFormVisible.value = true;
};
// æäº¤å‘货表单
@@ -1727,7 +1696,9 @@
  proxy.$refs["deliveryFormRef"].validate((valid) => {
    if (valid) {
      addShippingInfo({
        salesLedgerId: currentDeliveryRow.value.id,
        approverId:deliveryForm.value.approverId,
        salesLedgerId: currentDeliveryRow.value.salesLedgerId,
        salesLedgerProductId: currentDeliveryRow.value.id,
        shippingDate: deliveryForm.value.shippingDate,
        shippingCarNumber: deliveryForm.value.shippingCarNumber,
      })
@@ -1735,6 +1706,7 @@
          proxy.$modal.msgSuccess("发货成功");
          closeDeliveryDia();
          getList();
          expandedRowKeys.value = [];
        })
        .catch(() => {
          proxy.$modal.msgError("发货失败,请重试");