张诺
5 小时以前 2b8c32366a2916dfbeac269eea94b2e6ef65f556
Merge remote-tracking branch 'origin/dev_New' into dev_New

# Conflicts:
# src/views/salesManagement/salesLedger/index.vue
已添加10个文件
已修改48个文件
已删除11个文件
10241 ■■■■■ 文件已修改
src/api/financialManagement/accounting.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/employeeRecord.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/onboarding.js 49 ●●●●● 补丁 | 查看 | 原始文档 | 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/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/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/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 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | 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/salesManagement/salesLedger/index.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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/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/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/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/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: true, 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;
                console.log(22222,form.value.testStandardId);
                                handleTestStandardChange(matchedOption.id);
                            } else {
                                // å¦‚果找不到匹配项,尝试直接使用原值
                                console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                                form.value.testStandardId = savedTestStandardId;
                                handleTestStandardChange(savedTestStandardId);
                            }
                        } 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
@@ -9,8 +9,21 @@
      <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="process">
              <el-input v-model="form.process" placeholder="请输入" clearable/>
            <el-form-item label="工序:" prop="processId">
              <el-select
                v-model="form.processId"
                placeholder="请选择工序"
                clearable
                @change="handleProcessChange"
                style="width: 100%"
              >
                <el-option
                  v-for="item in processOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -32,6 +45,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 +139,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 {productProcessListPage} from "@/api/basicData/productProcess.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()
@@ -123,11 +155,12 @@
const data = reactive({
  form: {
    checkTime: "",
    process: "",
    processId: "",
    checkName: "",
    productName: "",
    productId: "",
    model: "",
    testStandardId: "",
    unit: "",
    quantity: "",
    checkCompany: "",
@@ -135,10 +168,11 @@
  },
  rules: {
    checkTime: [{ required: true, message: "请输入", trigger: "blur" },],
    process: [{ required: true, message: "请输入", trigger: "blur" }],
    processId: [{ required: true, message: "请选择工序", trigger: "change" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: false, message: "请输入", trigger: "blur" }],
    testStandardId: [{required: true, message: "请选择指标", trigger: "change"}],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
@@ -176,6 +210,24 @@
const tableData = ref([]);
const tableLoading = ref(false);
const currentProductId = ref(0);
const processOptions = ref([]); // å·¥åºä¸‹æ‹‰æ¡†æ•°æ®
const testStandardOptions = ref([]); // æŒ‡æ ‡é€‰æ‹©ä¸‹æ‹‰æ¡†æ•°æ®
// èŽ·å–å·¥åºåˆ—è¡¨
const getProcessList = async () => {
    try {
        const res = await productProcessListPage({ current: 1, size: 1000 })
        if (res?.code === 200) {
            const records = res?.data?.records || []
            processOptions.value = records.map(item => ({
                label: item.processName || item.name || item.label,
                value: item.id || item.processId || item.value
            }))
        }
    } catch (error) {
        console.error('获取工序列表失败:', error)
    }
}
// æ‰“开弹框
const openDialog = async (type, row) => {
@@ -187,11 +239,64 @@
    let userLists = await userListNoPage();
    userList.value = userLists.data;
    form.value = {}
    testStandardOptions.value = [];
    tableData.value = [];
    getProductOptions();
    // å…ˆåŠ è½½å·¥åºåˆ—è¡¨
    await getProcessList();
    if (operationType.value === 'edit') {
        form.value = {...row}
        // å…ˆä¿å­˜ testStandardId,避免被清空
        const savedTestStandardId = row.testStandardId;
        // å…ˆè®¾ç½®è¡¨å•数据,但暂时清空 testStandardId,等选项加载完成后再设置
        form.value = {...row, testStandardId: ''}
        // å…¼å®¹æ—§æ•°æ®ï¼šå¦‚æžœ row ä¸­æœ‰ process å­—段,转换为 processId
        if (row.process && !row.processId) {
            // å°è¯•从 processOptions ä¸­æŸ¥æ‰¾åŒ¹é…çš„工序
            const processOption = processOptions.value.find(p => p.label === row.process);
            if (processOption) {
                form.value.processId = processOption.value;
            }
        }
        currentProductId.value = row.productId || 0
        getQualityInspectParamList(row.id)
        // ç¼–辑模式下,先加载指标选项,然后加载参数列表
        if (currentProductId.value) {
            // å…ˆåŠ è½½æŒ‡æ ‡é€‰é¡¹
            let params = {
                productId: currentProductId.value,
                inspectType: 1,
                process: form.value.processId ? processOptions.value.find(p => p.value === form.value.processId)?.label : ''
            }
            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;
                                handleTestStandardChange(matchedOption.id);
                            } else {
                                // å¦‚果找不到匹配项,尝试直接使用原值
                                console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                                form.value.testStandardId = savedTestStandardId;
                                handleTestStandardChange(savedTestStandardId);
                            }
                        } else {
                            // å¦åˆ™ä½¿ç”¨æ—§çš„逻辑
                            getQualityInspectParamList(row.id);
                        }
                    }, 100);
                });
            });
        } else {
            getQualityInspectParamList(row.id);
        }
    }
}
const getProductOptions = () => {
@@ -202,7 +307,7 @@
const getModels = (value) => {
    currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
    if (currentProductId) {
    if (currentProductId.value) {
        getList();
    }
};
@@ -234,17 +339,31 @@
    return newItem;
  });
}
// å·¥åºå˜åŒ–处理
const handleProcessChange = () => {
    // å·¥åºå˜åŒ–时,如果已选择产品,重新加载指标列表
    if (currentProductId.value) {
        getList();
    }
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      form.value.inspectType = 1
            // å°† processId è½¬æ¢ä¸º process åç§°ï¼ˆå¦‚果后端需要 process å­—段)
            const processName = form.value.processId ? processOptions.value.find(p => p.value === form.value.processId)?.label : '';
            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 +379,42 @@
  })
}
const getList = () => {
    qualityInspectDetailByProductId(currentProductId.value).then(res => {
        tableData.value = res.data;
    if (!currentProductId.value) {
        testStandardOptions.value = [];
        tableData.value = [];
        return;
    }
    // èŽ·å–å·¥åºåç§°
    const processName = form.value.processId ? processOptions.value.find(p => p.value === form.value.processId)?.label : '';
    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 +425,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: true, 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,52 @@
    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;
                handleTestStandardChange(matchedOption.id);
              } else {
                // å¦‚果找不到匹配项,尝试直接使用原值
                console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value);
                form.value.testStandardId = savedTestStandardId;
                handleTestStandardChange(savedTestStandardId);
              }
            } else {
              // å¦åˆ™ä½¿ç”¨æ—§çš„逻辑
              getQualityInspectParamList(row.id);
            }
          }, 100);
        });
      });
    } else {
      getQualityInspectParamList(row.id);
    }
  }
}
const getProductOptions = () => {
@@ -214,7 +276,7 @@
const getModels = (value) => {
  currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
  if (currentProductId) {
  if (currentProductId.value) {
    getList();
  }
};
@@ -275,8 +337,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 +381,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/salesManagement/salesLedger/index.vue
@@ -359,7 +359,7 @@
                                        <span class="value">{{ formatDate(item.createTime) }}</span>
                                    </div>
                                    <div>
                                        <span class="label">客户名称:</span>
                                        <span class="value">{{ item.customerName || '张爱有' }}</span>
                                    </div>
@@ -490,16 +490,55 @@
                </div>
            </template>
        </el-dialog>
    <FileList ref="fileListRef" />
    <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 {ElMessage, ElMessageBox} from "element-plus";
import { ElMessageBox, ElMessage } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
import FileList from "./fileList.vue";
@@ -650,6 +689,54 @@
});
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) {
    searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
@@ -708,7 +795,6 @@
  });
};
const getProductModel = (value) => {
  console.log("value", value);
  const index = modelOptions.value.findIndex((item) => item.id === value);
  if (index !== -1) {
    productForm.value.specificationModel = modelOptions.value[index].model;
@@ -733,6 +819,7 @@
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  if (!data || !Array.isArray(data)) return [];
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
@@ -902,6 +989,12 @@
  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;
@@ -983,6 +1076,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("选中的内容将被导出,是否确认导出?", "导出", {