Prechádzať zdrojové kódy

Merge remote-tracking branch 'origin/master'

lipenghui 1 mesiac pred
rodič
commit
8e0969cec7

+ 19 - 4
src/api/pms/iotmaintenancebom/index.ts

@@ -12,15 +12,16 @@ export interface IotMaintenanceBomVO {
   naturalDateRule: number   // 保养规则-自然日期(0启用 1停用)
   runningTimeRule: number   // 保养规则-运行时间(0启用 1停用)
   lastRunningTime: number // 上次保养运行时长(小时)
-  nextRunningTime: number // 下次保养运行时长(小时)
-  timePeriod: number // 时间周期(小时)
-  kilometerCycle: number // 公里数周期(千米)
-  naturalDatePeriod: number // 自然日周期(天)
+  nextRunningTime: number // 下次保养运行时长(小时) 运行时长周期
+  timePeriod: number // 时间周期(小时) 距离下次保养运行时长
+  kilometerCycle: number // 公里数周期(千米) 距离下次保养公里数
+  naturalDatePeriod: number // 自然日周期(天) 下次保养自然日期
   timePeriodLead: number  // 运行时长周期提前量 H
   lastRunningKilometers: number // 上次保养运行公里数(千米)
   nextRunningKilometers: number // 下次保养运行公里数(千米)
   kiloCycleLead: number // 公里数周期-提前量 km
   lastNaturalDate: Date // 上次保养自然日期(天)
+  tempLastNaturalDate: Date // 上次保养自然日期(天) 临时变量
   nextNaturalDate: number // 下次保养自然日期(天)
   naturalDatePeriodLead: number // 自然日周期-提前量(天)
   bomNodeId: number // bom节点id
@@ -58,6 +59,20 @@ export interface IotMaintenanceBomVO {
     totalRunTime: number;
     totalMileage: number;
   }>;
+  // 上次保养时间 不同于自然日保养规则下的 上次保养自然日期
+  lastMaintenanceDate: Date
+  // 下次保养公里数
+  nextMaintenanceKm: number
+  // 剩余保养公里数
+  remainKm: number
+  // 下次保养运行时长
+  nextMaintenanceH: number
+  // 剩余保养运行时长
+  remainH: number
+  // 下次保养日期
+  nextMaintenanceDate: Date
+  // 自然日期保养 剩余天数
+  remainDay: number
 }
 
 // PMS 保养计划明细BOM API

+ 17 - 0
src/api/pms/iotmainworkorderbom/index.ts

@@ -51,6 +51,23 @@ export interface IotMainWorkOrderBomVO {
   totalRunTime: number  // 累计运行时间
   tempTotalMileage: number  // 临时 累计运行公里数
   tempTotalRunTime: number  // 临时 累计运行时间
+
+  // 上次保养时间 不同于自然日保养规则下的 上次保养自然日期
+  lastMaintenanceDate: Date
+  // 下次保养公里数
+  nextMaintenanceKm: number
+  // 剩余保养公里数
+  remainKm: number
+  // 下次保养运行时长
+  nextMaintenanceH: number
+  // 剩余保养运行时长
+  remainH: number
+  // 下次保养日期
+  nextMaintenanceDate: Date
+  // 自然日期保养 剩余天数
+  remainDay: number
+  // 设备BOM节点已经绑定的物料
+  deviceBomMaterials: []
 }
 
 // PMS 保养计划明细BOM API

+ 7 - 0
src/locales/en.ts

@@ -953,6 +953,7 @@ export default {
     lastMaintenanceMileage:'LastMaintenanceMileage(KM)',
     lastMaintenanceOperationTime:'LastMOTime(H)',
     lastMaintenanceNaturalDate:'LastMaintenanceNaturalDate',
+    lastMaintenanceDate: 'LastMaintenanceTime',
     operatingMileageRuleConfiguration:'OperatingMileageRuleConfig',
     operatingMileageCycle:'OperatingMileageCycle(KM)',
     OperatingMileageCycle_lead:'OMC_Lead(KM)',
@@ -972,9 +973,15 @@ export default {
     DelayDuration:'DelayDuration',
     DelayDate:'DelayDate(D)',
     MaintItems:'MaintItems',
+    deviceBomMaterials:'DeviceBomMaterials',
     nextMaintTime:'NextMaintTime(H)',
     nextMaintKil:'NextMaintKil(KM)',
     nextMaintDate:'NextMaintDate',
+    nextMaintenanceKm:'NextMaintKil(KM)',
+    nextMaintenanceH:'NextMaintTime(H)',
+    remainKm: 'Remaining Mileage(KM)',
+    remainH: 'Remaining Duration(H)',
+    remainDay: 'Remaining Day',
     completed: 'Completed',
     delayed: 'Delayed',
     maintaining: 'Maintaining',

+ 8 - 1
src/locales/ru.ts

@@ -932,8 +932,9 @@ export default {
   mainPlan:{
     basicMaintenanceRecords:'基础保养记录',
     lastMaintenanceMileage:'上次保养里程数(KM)',
-    lastMaintenanceOperationTime:'上次保养运行时间(H)',
+    lastMaintenanceOperationTime:'上次保养时长(H)',
     lastMaintenanceNaturalDate:'上次保养自然日期',
+    lastMaintenanceDate: '上次保养时间',
     operatingMileageRuleConfiguration:'运行里程规则配置',
     operatingMileageCycle:'运行里程周期(KM)',
     OperatingMileageCycle_lead:'运行里程周期-提前量(KM)',
@@ -953,9 +954,15 @@ export default {
     DelayDuration:'推迟时长',
     DelayDate:'推迟自然日期(D)',
     MaintItems:'保养项',
+    deviceBomMaterials:'保养项物料',
     nextMaintTime:'距离下次保养运行时长(H)',
     nextMaintKil:'距离下次保养公里数(KM)',
     nextMaintDate:'下次保养自然日期',
+    nextMaintenanceKm:'下次保养公里数(KM)',
+    nextMaintenanceH:'下次保养时长(H)',
+    remainKm: '剩余公里数(KM)',
+    remainH: '剩余时长(H)',
+    remainDay: '剩余天数',
     completed: '完成',
     delayed: '延时',
     maintaining: '保养中',

+ 11 - 4
src/locales/zh-CN.ts

@@ -502,7 +502,7 @@ export default {
   operationFillForm:{
     team:'所属队伍',
     sumTime:'累计运行时间(H)',
-    sumKil:'累计运行公里数(Km)',
+    sumKil:'累计运行公里数(KM)',
     confirm:'确定',
     cancel:'取消',
     alert:'以下数值取自PLC,如有不符请修改',
@@ -946,8 +946,9 @@ export default {
   mainPlan:{
     basicMaintenanceRecords:'基础保养记录',
     lastMaintenanceMileage:'上次保养里程数(KM)',
-    lastMaintenanceOperationTime:'上次保养运行时间(H)',
-    lastMaintenanceNaturalDate:'上次保养自然日期',
+    lastMaintenanceOperationTime:'上次保养时长(H)',
+    lastMaintenanceNaturalDate:'上次保养日期',
+    lastMaintenanceDate: '上次保养时间',
     operatingMileageRuleConfiguration:'运行里程规则配置',
     operatingMileageCycle:'运行里程周期(KM)',
     OperatingMileageCycle_lead:'运行里程周期-提前量(KM)',
@@ -967,9 +968,15 @@ export default {
     DelayDuration:'推迟时长',
     DelayDate:'推迟自然日期(D)',
     MaintItems:'保养项',
+    deviceBomMaterials:'保养项物料',
     nextMaintTime:'距离下次保养运行时长(H)',
     nextMaintKil:'距离下次保养公里数(KM)',
-    nextMaintDate:'下次保养自然日期',
+    nextMaintDate:'下次保养日期',
+    nextMaintenanceKm:'下次保养公里数(KM)',
+    nextMaintenanceH:'下次保养时长(H)',
+    remainKm: '剩余公里数(KM)',
+    remainH: '剩余时长(H)',
+    remainDay: '剩余天数(D)',
     completed: '完成',
     delayed: '延时',
     maintaining: '保养中',

+ 282 - 0
src/views/pms/iotmainworkorder/DeviceBomMaterials.vue

@@ -0,0 +1,282 @@
+<template>
+  <Dialog v-model="dialogVisible" title="保养项物料" style="width: 1200px; min-height: 400px">
+
+    <!-- 新增设备分类和BOM节点信息显示 -->
+    <div style="margin: 0 0px 15px; background: #f5f7fa; padding: 12px 20px; border-radius: 4px;">
+      <el-row>
+        <el-col :span="8">
+          <span style="font-weight: bold; margin-right: 10px;">设备编码:</span>
+          <span>{{ deviceCode }}</span>
+        </el-col>
+        <el-col :span="8">
+          <span style="font-weight: bold; margin-right: 10px;">设备名称:</span>
+          <span>{{ deviceName }}</span>
+        </el-col>
+        <el-col :span="8">
+          <span style="font-weight: bold; margin-right: 10px;">{{bomNodeLabel}}:</span>
+          <span>{{ bomNodeName }}</span>
+        </el-col>
+      </el-row>
+    </div>
+
+    <ContentWrap>
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item :label="t('workOrderMaterial.materialCode')" prop="code">
+          <el-input
+            v-model="queryParams.code"
+            :placeholder="t('workOrderMaterial.codeHolder')"
+            clearable
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item :label="t('workOrderMaterial.materialName')" prop="name">
+          <el-input
+            v-model="queryParams.name"
+            :placeholder="t('workOrderMaterial.nameHolder')"
+            clearable
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />
+            {{ t('workOrderMaterial.search') }}</el-button>
+          <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />
+            {{ t('workOrderMaterial.reset') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+    <ContentWrap>
+      <el-table
+        v-loading="loading"
+        :data="paginatedList"
+        :stripe="true"
+        ref="tableRef"
+        :show-overflow-tooltip="true"
+        @row-click="handleRowClick"
+        :row-class-name="rowClassName"
+      >
+        <el-table-column
+          :label="t('workOrderMaterial.materialCode')"
+          align="center"
+          prop="code"
+          :show-overflow-tooltip="true"
+          class="!w-100px"
+        />
+        <el-table-column
+          :label="t('workOrderMaterial.materialName')"
+          align="center"
+          prop="name"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" />
+        <el-table-column :label="t('route.quantity')" align="center" prop="quantity" />
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="handleLocalPagination"
+      />
+    </ContentWrap>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { IotMaintainMaterialVO } from '@/api/pms/maintain/material'
+import * as WorkOrderBomMaterialApi from '@/api/pms/iotmainworkorderbommaterial'
+import { defineExpose, ref } from 'vue'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+// const emit = defineEmits(['choose']) // 定义 success 事件,用于操作成功后的回调
+// 调整 emit 类型
+const emit = defineEmits<{
+  (e: 'choose', value: WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO[]): void
+  (e: 'close'): void
+}>()
+// 跟踪新添加的行的索引
+const lastAddedIndex = ref<number | null>(null)
+const dialogVisible = ref(false) // 弹窗的是否展示
+const loading = ref(true) // 列表的加载中
+const queryFormRef = ref() // 搜索的表单
+const list = ref<WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO[]>([]) // 列表的数据
+
+const originalList = ref<WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO[]>([]) // 原始数据
+const filteredList = ref<WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO[]>([]) // 过滤后数据
+
+const tableRef = ref()
+const selectedRows = ref<WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO[]>([]) // 多选数据(存储所有选中行的数组)
+
+const deviceName = ref('') // 设备名称
+const deviceCode = ref('')    // 设备编码
+const bomNodeName = ref('')   // 保养项 or 维修项 名称
+const bomNodeLabel = ref('')   // 保养项 or 维修项 名称
+
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: 0,
+  bomNodeId: 0,
+  deviceId: 0,
+  name: '',
+  code: ''
+})
+
+// 保存初始查询参数用于重置
+const defaultQueryParams = {
+  name: '',
+  code: '',
+  pageNo: 1,
+  pageSize: 10,
+}
+
+const drawerVisible = ref<boolean>(false)
+const showDrawer = ref()
+const handleView = () => {
+  drawerVisible.value = true
+  showDrawer.value.openDrawer()
+}
+const addMaterial = ref(null)
+
+// 计算当前页的数据切片
+const paginatedList = computed(() => {
+  const start = (queryParams.pageNo - 1) * queryParams.pageSize;
+  return filteredList.value.slice(start, start + queryParams.pageSize)
+});
+
+// 计算总条数(直接使用 list.length)
+const total = computed(() => filteredList.value.length);
+
+// 本地分页切换逻辑
+const handleLocalPagination = (e: { page: number; limit: number }) => {
+  queryParams.pageNo = e.page;
+  queryParams.pageSize = e.limit;
+  // 无需调用 getList,paginatedList 会自动更新
+};
+
+// 点击整行选中
+const handleRowClick = (row) => {
+  toggleRow(row)
+}
+const open = async (deptId: number, bomNodeId: number, row: any, type: string) => {
+  console.log('传递过来的数据:', row.deviceId)
+  Object.assign(queryParams, defaultQueryParams)
+
+  selectedRows.value = []
+  dialogVisible.value = true
+  queryParams.deptId = deptId
+  queryParams.bomNodeId = bomNodeId
+  queryParams.deviceId = row.deviceId
+  if(type === 'repair'){
+    queryParams.deviceId = row.deviceId
+  }
+  deviceName.value = row.deviceName
+  deviceCode.value = row.deviceCode
+  bomNodeName.value = row.name
+  if (type === 'maintenance') {
+    bomNodeLabel.value = '保养项'
+  } else if (type === 'repair') {
+    bomNodeLabel.value = '维修项'
+  }
+
+  // 修改后的设备BOM物料处理逻辑
+  let sourceData = []
+  if (row.deviceBomMaterials) {
+    sourceData = typeof row.deviceBomMaterials === 'string'
+      ? JSON.parse(row.deviceBomMaterials)
+      : row.deviceBomMaterials
+  }
+  // 统一字段并保存
+  originalList.value = sourceData.map(normalizeItem)
+  // 初始化过滤列表
+  resetQuery()
+  // 重置分页参数
+  queryParams.pageNo = 1;
+  queryParams.pageSize = 10;
+  loading.value = false
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+// 关闭时清空选择
+const handleClose = () => {
+  tableRef.value?.clearSelection()
+  selectedRows.value = []
+  emit('close')
+}
+
+// 字段统一处理
+const normalizeItem = (item: any): WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO => ({
+  ...item,
+  code: item.code,
+  name: item.name
+})
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  const { code, name } = queryParams
+
+  // 本地过滤逻辑
+  filteredList.value = originalList.value.filter(item => {
+    const codeMatch = (item.code || '').toLowerCase().includes(code.toLowerCase())
+    const nameMatch = (item.name || '').toLowerCase().includes(name.toLowerCase())
+    return codeMatch && nameMatch
+  })
+}
+const choose = (row: IotMaintainMaterialVO) => {
+  emit('choose', row)
+  dialogVisible.value = false
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  if (queryFormRef.value) {
+    queryFormRef.value.resetFields()
+  }
+  Object.assign(queryParams, defaultQueryParams)
+  filteredList.value = [...originalList.value] // 恢复完整数据集
+}
+</script>
+<style lang="scss" scoped>
+.no-label-radio .el-radio__label {
+  display: none;
+}
+.no-label-radio .el-radio__inner {
+  margin-right: 0;
+}
+
+/* 自定义淡绿色按钮 */
+:deep(.custom-green-button) {
+  background-color: #e1f3d8;
+  border-color: #e1f3d8;
+  color: #67c23a;
+}
+
+/* 悬停效果 */
+:deep(.custom-green-button:hover) {
+  background-color: #d1e8c0;
+  border-color: #d1e8c0;
+  color: #5daf34;
+}
+
+/* 点击效果 */
+:deep(.custom-green-button:active) {
+  background-color: #c2dca8;
+  border-color: #c2dca8;
+}
+
+:deep(.el-table .row-green-bg) {
+  background-color: #f0f9eb !important; /* Element Plus 绿色背景 */
+}
+
+:deep(.el-table .bg-blue-5) {
+  background-color: #3fa5de !important; /* Element Plus 绿色背景 */
+}
+</style>

+ 554 - 81
src/views/pms/iotmainworkorder/IotMainWorkOrder.vue

@@ -79,35 +79,41 @@
   <ContentWrap>
     <!-- 列表 -->
     <ContentWrap>
-      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table v-loading="loading" :data="paginatedList" :stripe="true" :show-overflow-tooltip="true" :header-cell-style="tableHeaderStyle">
         <!-- 添加序号列 -->
         <el-table-column
           type="index"
           :label="t('maintain.serial')"
-          width="70"
           align="center"
+          prop="serial"
+          :width="columnWidths.serial"
+          fixed="left"
         />
         <el-table-column :label="t('bomList.bomNode')" align="center" prop="bomNodeId" v-if="false"/>
-        <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" />
-        <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName" />
-        <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime" :formatter="erpPriceTableColumnFormatter">
+        <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" :width="columnWidths.deviceCode" fixed="left">
           <template #default="{ row }">
-            {{ row.totalRunTime ?? row.tempTotalRunTime }}
+            <div class="full-content-cell"> <!-- 自定义样式 -->
+              {{ row.deviceCode }}
+            </div>
           </template>
         </el-table-column>
-        <el-table-column :label="t('operationFillForm.sumKil')" align="center" prop="totalMileage" :formatter="erpPriceTableColumnFormatter">
+        <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName" :width="columnWidths.deviceName" fixed="left">
           <template #default="{ row }">
-            {{ row.totalMileage ?? row.tempTotalMileage }}
+            <div class="full-content-cell"> <!-- 自定义样式 -->
+              {{ row.deviceName }}
+            </div>
           </template>
         </el-table-column>
-        <el-table-column :label="t('mainPlan.MaintItems')" align="center" prop="name" :show-overflow-tooltip="false" :width="maintItemsWidth" >
+        <el-table-column :label="t('mainPlan.MaintItems')" align="center" prop="name"
+                         :show-overflow-tooltip="false" :width="columnWidths.name" fixed="left">
           <template #default="{ row }">
             <div class="full-content-cell"> <!-- 自定义样式 -->
               {{ row.name }}
             </div>
           </template>
         </el-table-column>
-        <el-table-column :label="t('main.mileage')" key="mileageRule" width="80">
+        <el-table-column :label="t('main.mileage')" key="mileageRule" align="center"
+                         :width="columnWidths.mileageRule" v-if="hasMileageRuleInCurrentPage">
           <template #default="scope">
             <el-switch
               v-model="scope.row.mileageRule"
@@ -117,7 +123,8 @@
             />
           </template>
         </el-table-column>
-        <el-table-column :label="t('main.runTime')" key="runningTimeRule" width="90">
+        <el-table-column :label="t('main.runTime')" key="runningTimeRule" align="center"
+                         :width="columnWidths.runningTimeRule" v-if="hasTimeRuleInCurrentPage">
           <template #default="scope">
             <el-switch
               v-model="scope.row.runningTimeRule"
@@ -127,7 +134,8 @@
             />
           </template>
         </el-table-column>
-        <el-table-column :label="t('main.date')" key="naturalDateRule" width="80">
+        <el-table-column :label="t('main.date')" key="naturalDateRule" align="center"
+                         :width="columnWidths.naturalDateRule" v-if="hasDateRuleInCurrentPage">
           <template #default="scope">
             <el-switch
               v-model="scope.row.naturalDateRule"
@@ -137,78 +145,213 @@
             />
           </template>
         </el-table-column>
-        <el-table-column
-          v-if="shouldShowRunningTimeColumn"
-          :label="t('mainPlan.RunTimeCycle')"
-          align="center"
-          width="120"
-        >
-          <template #default="scope">
-            <el-input-number
-              v-if="scope.row.runningTimeRule === 0"
-              v-model="scope.row.nextRunningTime"
-              :precision="1"
-              :min="1"
-              :controls="false"
-              style="width: 100%"
-              @change="validateRunningTime(scope.row)"
-            />
-            <span v-else>-</span>
-            <!-- 错误提示 -->
-            <div v-if="scope.row.timeError" class="error-text">
-              {{ scope.row.timeError }}
+        <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime" v-if="hasTimeRuleInCurrentPage"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.totalRunTime">
+          <template #default="{ row }">
+            {{ row.totalRunTime ?? row.tempTotalRunTime }}
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('operationFillForm.sumKil')" align="center" prop="totalMileage" v-if="hasMileageRuleInCurrentPage"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.totalMileage">
+          <template #default="{ row }">
+            {{ row.totalMileage ?? row.tempTotalMileage }}
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('mainPlan.lastMaintenanceDate')" prop="lastMaintenanceDate" :width="columnWidths.lastMaintenanceDate">
+          <template #default="{ row }">
+            <div class="full-content-cell">
+              {{ row.lastMaintenanceDate }}
             </div>
           </template>
         </el-table-column>
+        <!-- 保养里程 分组 -->
+        <el-table-column v-if="hasMileageRuleInCurrentPage" label="保养里程" align="center">
+          <el-table-column :label="t('mainPlan.lastMaintenanceMileage')" align="center" prop="lastRunningKilometers"
+                           :formatter="erpPriceTableColumnFormatter" :width="columnWidths.lastRunningKilometers">
+            <template #default="{ row }">
+              {{ row.lastRunningKilometers }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.nextMaintenanceKm')"
+                           align="center" prop="nextMaintenanceKm" :width="columnWidths.nextMaintenanceKm">
+            <template #default="{ row }">
+              {{ row.nextMaintenanceKm ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.remainKm')"
+                           align="center" prop="remainKm" :width="columnWidths.remainKm">
+            <template #default="{ row }">
+              {{ row.remainKm ?? '-' }}
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <!-- 保养时长 分组 -->
+        <el-table-column v-if="hasTimeRuleInCurrentPage" label="保养时长" align="center">
+          <el-table-column :label="t('mainPlan.lastMaintenanceOperationTime')" align="center" prop="lastRunningTime"
+                           :formatter="erpPriceTableColumnFormatter" :width="columnWidths.lastRunningTime">
+            <template #default="{ row }">
+              {{ row.lastRunningTime }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.nextMaintenanceH')"
+                           align="center" prop="nextMaintenanceH" :width="columnWidths.nextMaintenanceH">
+            <template #default="{ row }">
+              {{ row.nextMaintenanceH ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.remainH')"
+                           align="center" prop="remainH" :width="columnWidths.remainH">
+            <template #default="{ row }">
+              {{ row.remainH ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            v-if="shouldShowRunningTimeColumn"
+            :label="t('mainPlan.RunTimeCycle')"
+            align="center"
+            prop="nextRunningTime"
+            :width="columnWidths.nextRunningTime"
+          >
+            <template #default="scope">
+              <el-input-number
+                v-if="scope.row.runningTimeRule === 0"
+                v-model="scope.row.nextRunningTime"
+                :precision="1"
+                :min="1"
+                :controls="false"
+                style="width: 100%"
+                @change="validateRunningTime(scope.row)"
+              />
+              <span v-else>-</span>
+              <!-- 错误提示 -->
+              <div v-if="scope.row.timeError" class="error-text">
+                {{ scope.row.timeError }}
+              </div>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <!-- 保养日期 分组 -->
+        <el-table-column v-if="hasDateRuleInCurrentPage" label="保养日期" align="center">
+          <el-table-column :label="t('mainPlan.lastMaintenanceNaturalDate')" align="center" prop="tempLastNaturalDate"
+                           :formatter="erpPriceTableColumnFormatter" :width="columnWidths.tempLastNaturalDate">
+            <template #default="{ row }">
+              {{ row.tempLastNaturalDate }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.nextMaintDate')"
+                           align="center" prop="nextMaintenanceDate" :width="columnWidths.nextMaintenanceDate">
+            <template #default="{ row }">
+              {{ row.nextMaintenanceDate ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.remainDay')"
+                           align="center" prop="remainDay" :width="columnWidths.remainDay">
+            <template #default="{ row }">
+              {{ row.remainDay ?? '-' }}
+            </template>
+          </el-table-column>
+        </el-table-column>
+
         <el-table-column :label="t('common.status')" align="center" width="100">
           <template #default="scope">
             {{ getStatusText(scope.row) }}
           </template>
         </el-table-column>
+
         <el-table-column :label="t('iotMaintain.numberOfMaterials')" align="center" width="90">
           <template #default="scope">
             {{ getMaterialCount(scope.row.bomNodeId) }}
           </template>
         </el-table-column>
-        <el-table-column :label="t('iotMaintain.operation')" align="center" min-width="120px">
+
+        <el-table-column :label="t('iotMaintain.operation')" align="center" prop="operation" :width="columnWidths.operation" fixed="right">
           <template #default="scope">
-            <el-dropdown trigger="hover" @command="handleDropdownCommand">
-              <el-button type="primary" link>
-                {{ t('action.more') }}<el-icon><arrow-down /></el-icon>
-              </el-button>
-              <template #dropdown>
-                <el-dropdown-menu>
-                  <!-- 延迟保养按钮 -->
-                  <el-dropdown-item
-                    v-if="scope.row.status === 0"
-                    :command="{ action: 'delay', row: scope.row }"
-                    :icon="Clock"
-                  >
-                    {{ t('stock.DelayMaintenance') }}
-                  </el-dropdown-item>
-
-                  <!-- 选择物料按钮 -->
-                  <el-dropdown-item
-                    v-if="scope.row.status === 0"
-                    :command="{ action: 'material', row: scope.row }"
-                    :icon="Box"
-                  >
-                    {{ t('stock.selectMaterial') }}
-                  </el-dropdown-item>
-
-                  <!-- 物料详情按钮 -->
-                  <el-dropdown-item
-                    :command="{ action: 'detail', row: scope.row }"
-                    :icon="Document"
-                  >
-                    {{ t('bomList.materialDetail') }}
-                  </el-dropdown-item>
-                </el-dropdown-menu>
-              </template>
-            </el-dropdown>
+            <div class="horizontal-actions">
+              <!-- 查看当前保养项已经绑定的物料列表 -->
+              <el-tooltip
+                v-if="scope.row.deviceBomMaterials && scope.row.deviceBomMaterials.length > 0"
+                effect="dark"
+                :content="t('mainPlan.deviceBomMaterials')"
+                placement="top"
+                :show-after="300"
+              >
+                <el-button
+                  type="primary"
+                  link
+                  :icon="View"
+                  @click="handleDropdownCommand({ action: 'deviceBomMaterials', row: scope.row })"
+                  class="action-button"
+                />
+              </el-tooltip>
+
+              <!-- 延迟保养按钮 -->
+              <el-tooltip
+                v-if="scope.row.status === 0"
+                effect="dark"
+                :content="t('stock.DelayMaintenance')"
+                placement="top"
+                :show-after="300"
+              >
+                <el-button
+                  type="primary"
+                  link
+                  :icon="Clock"
+                  @click="handleDropdownCommand({ action: 'delay', row: scope.row })"
+                  class="action-button"
+                />
+              </el-tooltip>
+
+              <!-- 选择物料按钮 -->
+              <el-tooltip
+                v-if="scope.row.status === 0"
+                effect="dark"
+                :content="t('stock.selectMaterial')"
+                placement="top"
+                :show-after="300"
+              >
+                <el-button
+                  type="primary"
+                  link
+                  :icon="Box"
+                  @click="handleDropdownCommand({ action: 'material', row: scope.row })"
+                  class="action-button"
+                />
+              </el-tooltip>
+
+              <!-- 物料详情按钮 -->
+              <el-tooltip
+                effect="dark"
+                :content="t('bomList.materialDetail')"
+                placement="top"
+                :show-after="300"
+              >
+                <el-button
+                  type="primary"
+                  link
+                  :icon="Document"
+                  @click="handleDropdownCommand({ action: 'detail', row: scope.row })"
+                  class="action-button"
+                />
+              </el-tooltip>
+            </div>
           </template>
         </el-table-column>
       </el-table>
+
+      <div style="margin-top: 20px; display: flex; justify-content: flex-end;">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10]"
+          :background="true"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="list.length"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        />
+      </div>
     </ContentWrap>
 
     <!-- 选择的物料列表 -->
@@ -470,6 +613,8 @@
   </el-dialog>
   <!-- 表单弹窗:添加/修改 -->
   <WorkOrderMaterial ref="materialFormRef" @choose="selectChoose" />
+  <!-- 设备BOM节点绑定的物料列表 -->
+  <DeviceBomMaterials ref="deviceBomMaterialsRef" />
   <!-- 抽屉组件 展示已经选择的物料 并编辑物料消耗 -->
   <MaterialListDrawer
     :model-value="drawerVisible"
@@ -484,6 +629,7 @@
 import * as UserApi from '@/api/system/user'
 import { useUserStore } from '@/store/modules/user'
 import { ref } from 'vue'
+import { useRouter } from 'vue-router'
 import { IotMaintenanceBomApi, IotMaintenanceBomVO } from '@/api/pms/iotmaintenancebom'
 import { IotMainWorkOrderBomApi, IotMainWorkOrderBomVO } from '@/api/pms/iotmainworkorderbom'
 import { IotMainWorkOrderBomMaterialApi, IotMainWorkOrderBomMaterialVO } from '@/api/pms/iotmainworkorderbommaterial'
@@ -497,7 +643,7 @@ import WorkOrderMaterial from "@/views/pms/iotmainworkorder/WorkOrderMaterial.vu
 import { IotDevicePersonApi, IotDevicePersonVO } from '@/api/pms/iotdeviceperson'
 import {DICT_TYPE, getIntDictOptions} from "@/utils/dict";
 // 引入图标
-import { ArrowDown, Clock, Box, Document } from '@element-plus/icons-vue';
+import { View, Clock, Box, Document } from '@element-plus/icons-vue';
 
 /** 保养计划 表单 */
 defineOptions({ name: 'IotMainWorkOrderBom' })
@@ -526,9 +672,19 @@ const devicePersonsMap = ref<Map<number, Set<string>>>(new Map()) // 存储设
 // 控制抽屉额外列的显示
 const hideExtraColumnsInDrawer = ref(false)
 
+// 分页相关变量
+const currentPage = ref(1)
+const pageSize = ref(10)
+
+const tableRef = ref();
+
 // 新增响应式变量
 const maintItemsWidth = ref('auto')
 
+const lastNaturalDateWatchers = ref(new Map())
+
+const columnWidths = ref<Record<string, string>>({});
+
 const formData = ref({
   id: undefined,
   deptId: undefined,
@@ -622,6 +778,8 @@ interface MaterialFormExpose {
 
 const materialFormRef = ref<MaterialFormExpose>();
 
+const deviceBomMaterialsRef = ref<MaterialFormExpose>();
+
 // 新增配置相关状态
 const configDialog = reactive({
   visible: false,
@@ -691,6 +849,31 @@ const validateRunningTime = (row: IotMainWorkOrderBomVO) => {
   return true;
 };
 
+// 计算属性:获取当前页的数据
+const paginatedList = computed(() => {
+  const start = (currentPage.value - 1) * pageSize.value
+  const end = start + pageSize.value
+  return list.value.slice(start, end)
+})
+
+// 页码改变处理
+const handleCurrentChange = (newPage: number) => {
+  currentPage.value = newPage
+}
+
+// 每页条数改变处理(保持10条)
+const handleSizeChange = (newSize: number) => {
+  pageSize.value = newSize
+  currentPage.value = 1 // 重置到第一页
+}
+
+// 列宽调整后的处理
+const handleHeaderDragEnd = () => {
+  nextTick(() => {
+    tableRef.value?.doLayout();
+  });
+};
+
 // 运行时间周期 全局校验方法(在submitForm中调用)
 const validateAllRunningTimes = (): boolean => {
   let isValid = true;
@@ -705,7 +888,6 @@ const validateAllRunningTimes = (): boolean => {
   return isValid;
 };
 
-// const materialFormRef = ref()
 const openMaterialForm = (row: any) => {
   bomNodeId.value = row.bomNodeId;
   console.log('这是一个对象:', row.bomNodeId)
@@ -713,6 +895,13 @@ const openMaterialForm = (row: any) => {
   materialFormRef.value.open(formData.value.deptId, bomNodeId.value, row, type)
 }
 
+// 查看当前保养项已经绑定的物料列表
+const openDeviceBomMaterials = (row: any) => {
+  bomNodeId.value = row.bomNodeId;
+  const type = 'maintenance'
+  deviceBomMaterialsRef.value.open(formData.value.deptId, bomNodeId.value, row, type)
+}
+
 const selectChoose = (selectedMaterial) => {
   selectedMaterial.bomNodeId = bomNodeId.value
   // 关联 bomNodeId
@@ -1028,7 +1217,7 @@ const configFormRules = reactive({
 })
 
 // 计算文本宽度的辅助函数
-const getTextWidth = (text: string): number => {
+const getTextWidth = (text: string, fontSize = 14): number => {
   const span = document.createElement('span')
   span.style.visibility = 'hidden'
   span.style.position = 'absolute'
@@ -1069,6 +1258,100 @@ const calculateMaintItemsWidth = () => {
   maintItemsWidth.value = `${maxWidth}px`
 }
 
+// 计算下次保养公里数(通用函数)
+const calculateNextMaintenanceKm = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且 > 0
+  const isValid = row.mileageRule === 0 &&
+    row.lastRunningKilometers > 0 &&
+    row.nextRunningKilometers > 0;
+
+  return isValid
+    ? (row.lastRunningKilometers + row.nextRunningKilometers)
+    : null; // 不满足条件返回null
+};
+
+// 计算剩余保养公里数(通用函数)
+const calculateRemainKm = (row: IotMaintenanceBomVO) => {
+  // 确定使用的里程值(优先totalMileage)
+  const mileageValue = row.totalMileage ?? row.tempTotalMileage;
+  // 验证条件:规则开启 + 3个值都存在且 > 0
+  const isValid = row.mileageRule === 0 &&
+    row.lastRunningKilometers > 0 &&
+    mileageValue > 0 &&
+    row.nextRunningKilometers > 0;
+
+  return isValid
+    ? (row.nextRunningKilometers - (mileageValue - row.lastRunningKilometers))
+    : null; // 不满足条件返回null
+};
+
+// 计算下次保养运行时长(通用函数)
+const calculateNextMaintenanceH = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且 > 0
+  const isValid = row.runningTimeRule === 0 &&
+    row.lastRunningTime > 0 &&
+    row.nextRunningTime > 0;
+
+  return isValid
+    ? (row.lastRunningTime + row.nextRunningTime)
+    : null; // 不满足条件返回null
+};
+
+// 计算剩余运行时间(通用函数)
+const calculateRemainH = (row: IotMaintenanceBomVO) => {
+  // 确定使用的 运行时长 值(优先 totalRunTime)
+  const runTimeValue = row.totalRunTime ?? row.tempTotalRunTime;
+  // 验证条件:规则开启 + 3个值都存在且 > 0
+  const isValid = row.runningTimeRule === 0 &&
+    row.lastRunningTime > 0 &&
+    runTimeValue > 0 &&
+    row.nextRunningTime > 0;
+
+  return isValid
+    ? (row.nextRunningTime - (runTimeValue - row.lastRunningTime))
+    : null; // 不满足条件返回null
+};
+
+// 计算下次保养日期(通用函数)
+const calculateNextMaintenanceDate = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且 > 0
+  const isValid = row.naturalDateRule === 0 &&
+    row.lastNaturalDate &&
+    row.nextNaturalDate;
+
+  return isValid
+    ? dayjs(row.lastNaturalDate).add(row.nextNaturalDate, 'day').format('YYYY-MM-DD')
+    : null; // 不满足条件返回null
+};
+
+// 计算 自然日期保养 剩余天数(通用函数)
+const calculateRemainDay = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且有效
+  const isValid = row.naturalDateRule === 0 &&
+    row.lastNaturalDate !== null &&
+    row.nextNaturalDate !== null &&
+    row.nextNaturalDate > 0;
+
+  if (!isValid) {
+    return null;
+  }
+
+  try {
+    // 上次保养日期:将时间戳转换为 Day.js 对象
+    const lastNaturalDate = dayjs(row.lastNaturalDate);
+
+    // 计算下次保养日期
+    const nextMaintenanceDate = lastNaturalDate.add(row.nextNaturalDate, 'day');
+
+    // 计算剩余天数(当前日期到下次保养日期的天数差)
+    return nextMaintenanceDate.diff(dayjs(), 'day');
+  } catch (error) {
+    console.error('计算保养剩余天数错误:', error);
+    return null;
+  }
+
+};
+
 const getStatusText = (row: any) => {
   // 状态为1直接返回"完成"
   if (row.status === 1) return t('mainPlan.completed');
@@ -1088,9 +1371,155 @@ const getStatusText = (row: any) => {
 };
 
 // 监听数据变化重新计算
-watch(() => list.value, () => {
+/* watch(() => list.value, () => {
   calculateMaintItemsWidth()
-}, { deep: true })
+}, { deep: true }) */
+
+// 计算属性 - 检查当前页是否有开启的里程规则
+const hasMileageRuleInCurrentPage = computed(() => {
+  return paginatedList.value.some(row => row.mileageRule === 0);
+});
+
+// 计算属性 - 检查当前页是否有开启的 运行时间 规则
+const hasTimeRuleInCurrentPage = computed(() => {
+  return paginatedList.value.some(row => row.runningTimeRule === 0);
+});
+
+// 计算属性 - 检查当前页是否有开启的 自然日期 规则
+const hasDateRuleInCurrentPage = computed(() => {
+  return paginatedList.value.some(row => row.naturalDateRule === 0);
+});
+
+// 统一计算所有列宽
+const calculateAllColumnsWidth = () => {
+  const MIN_WIDTH = 70; // 最小列宽
+  const PADDING = 10; // 列内边距
+  const FIXED_COLUMN_PADDING = 10;  // 固定列额外内边距
+  const GROUP_COLUMN_EXTRA = 20; // 分组列额外宽度
+
+  // 需要自适应的列配置
+  const autoColumns = [
+    { prop: 'serial', label: t('iotDevice.serial') },
+    { prop: 'deviceCode', label: t('iotMaintain.deviceCode') },
+    { prop: 'deviceName', label: t('iotMaintain.deviceName') },
+    {
+      prop: 'totalRunTime',
+      label: t('operationFillForm.sumTime'),
+      getValue: (row) => row.totalRunTime ?? row.tempTotalRunTime
+    },
+    {
+      prop: 'totalMileage',
+      label: t('operationFillForm.sumKil'),
+      getValue: (row) => row.totalMileage ?? row.tempTotalMileage
+    },
+    { prop: 'name', label: t('bomList.bomNode') },
+    { prop: 'lastMaintenanceDate', label: t('mainPlan.lastMaintenanceDate') },
+    { prop: 'mileageRule', label: t('main.mileage') },
+    { prop: 'runningTimeRule', label: t('main.runTime') },
+    { prop: 'naturalDateRule', label: t('main.date') },
+    { prop: 'lastRunningKilometers', label: t('mainPlan.lastMaintenanceMileage') },
+    { prop: 'nextMaintenanceKm', label: t('mainPlan.nextMaintenanceKm') },
+    { prop: 'remainKm', label: t('mainPlan.remainKm') },
+    { prop: 'lastRunningTime', label: t('mainPlan.lastMaintenanceOperationTime') },
+    { prop: 'nextMaintenanceH', label: t('mainPlan.nextMaintenanceH') },
+    { prop: 'remainH', label: t('mainPlan.remainH') },
+    { prop: 'nextRunningTime', label: t('mainPlan.RunTimeCycle') },
+    { prop: 'tempLastNaturalDate', label: t('mainPlan.lastMaintenanceNaturalDate') },
+    { prop: 'nextMaintenanceDate', label: t('mainPlan.nextMaintDate') },
+    { prop: 'remainDay', label: t('mainPlan.remainDay') },
+    { prop: 'operation', label: t('operationFill.operation') }
+  ];
+
+  const newWidths: Record<string, number> = {};
+
+  autoColumns.forEach(col => {
+    const headerText = col.label;
+    // 计算表头宽度
+    const headerWidth = getTextWidth(headerText) * 1.2;
+
+    // 计算内容最大宽度
+    let contentMaxWidth = 0;
+    if (col.prop === 'operation') {
+      // 操作列固定宽度(根据按钮数量)
+      contentMaxWidth = 120;
+    } else if (['mileageRule', 'runningTimeRule', 'naturalDateRule'].includes(col.prop)) {
+      // 开关列固定宽度
+      contentMaxWidth = 80;
+    } else {
+      list.value.forEach(row => {
+        const text = col.getValue ? String(col.getValue(row)) : String(row[col.prop] || '');
+        const textWidth = getTextWidth(text);
+        if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+      });
+    }
+    // 取最大值并添加内边距
+    let finalWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
+    // 为分组列增加额外宽度(重点修改)
+    if ([
+      'lastRunningKilometers',
+      'nextMaintenanceKm',
+      'remainKm',
+      'lastRunningTime',
+      'nextMaintenanceH',
+      'remainH',
+      'tempLastNaturalDate',
+      'nextMaintenanceDate',
+      'remainDay'
+    ].includes(col.prop)) {
+      finalWidth += GROUP_COLUMN_EXTRA;
+    }
+
+    newWidths[col.prop] = finalWidth;
+  });
+
+  // 固定列特殊处理 - 增加额外空间
+  ['serial', 'deviceCode', 'deviceName', 'name'].forEach(prop => {
+    if (newWidths[prop]) {
+      newWidths[prop] += FIXED_COLUMN_PADDING;
+    }
+  });
+
+  // 转换为CSS宽度值
+  Object.keys(newWidths).forEach(prop => {
+    columnWidths.value[prop] = `${newWidths[prop]}px`;
+  });
+};
+
+// 为每一行建立lastNaturalDate到tempLastNaturalDate的同步
+const setupNaturalDateSync = (row: IotMaintenanceBomVO) => {
+  // 如果该行已有watcher则跳过
+  if (lastNaturalDateWatchers.value.has(row.id)) return
+
+  // 为该行创建单独的watcher
+  const unwatch = watch(
+    () => row.lastNaturalDate,
+    (newVal) => {
+      // 转换日期格式 (时间戳 -> YYYY-MM-DD)
+      row.tempLastNaturalDate = newVal
+        ? dayjs(newVal).format('YYYY-MM-DD')
+        : null;
+    },
+    { immediate: true, deep: true }
+  )
+
+  // 保存watcher用于后续清理
+  lastNaturalDateWatchers.value.set(row.id, unwatch)
+}
+
+const tableHeaderStyle = ({ row, rowIndex }) => {
+  return {
+    border: '1px solid #333',
+    backgroundColor: rowIndex === 0 ? '#f0f9eb' : '#f5f7fa' // 分组行特殊背景
+  }
+}
+
+// 监听分页数据和规则变化 - 重新布局表格
+watch([paginatedList, hasMileageRuleInCurrentPage, hasTimeRuleInCurrentPage, hasDateRuleInCurrentPage], () => {
+  nextTick(() => {
+    tableRef.value?.doLayout();
+    calculateAllColumnsWidth(); // 重新计算列宽
+  });
+});
 
 // 下拉菜单命令处理
 const handleDropdownCommand = (command: { action: string; row: IotMainWorkOrderBomVO }) => {
@@ -1101,6 +1530,9 @@ const handleDropdownCommand = (command: { action: string; row: IotMainWorkOrderB
     case 'material':
       openMaterialForm(command.row);
       break;
+    case 'deviceBomMaterials':
+      openDeviceBomMaterials(command.row);
+      break;
     case 'detail':
       handleView(command.row);
       break;
@@ -1234,13 +1666,25 @@ onMounted(async () => {
     const data = await IotMainWorkOrderBomApi.getWorkOrderBOMs(queryParams);
     list.value = []
     if (Array.isArray(data)) {
-      // 添加过滤逻辑:只保留 status === 0 的记录
-      list.value = data
-        // .filter(item => item.status === 0) // 关键过滤代码
-        .map(item => ({
-          ...item,
-          lastNaturalDate: item.lastNaturalDate
-        }))
+      list.value = data.map(item => {
+        if (item.mileageRule === 0) {
+          item.nextMaintenanceKm = calculateNextMaintenanceKm(item);
+          item.remainKm = calculateRemainKm(item);
+        }
+        if (item.runningTimeRule === 0) {
+          item.nextMaintenanceH = calculateNextMaintenanceH(item);
+          item.remainH = calculateRemainH(item);
+        }
+        if (item.naturalDateRule === 0) {
+          item.nextMaintenanceDate = calculateNextMaintenanceDate(item);
+          item.remainDay = calculateRemainDay(item);
+        }
+        setupNaturalDateSync(item);
+          return {
+            ...item,
+            lastNaturalDate: item.lastNaturalDate
+          }
+        })
     }
     // 查询当前保养工单已经关联的所有物料
     const materials = await IotMainWorkOrderBomMaterialApi.getWorkOrderBomMaterials(queryParams);
@@ -1255,8 +1699,14 @@ onMounted(async () => {
     console.error('数据加载失败:', error)
     message.error('数据加载失败,请重试')
   }
-  window.addEventListener('resize', handleResize)
-  nextTick(calculateMaintItemsWidth)
+  nextTick(() => {
+    calculateAllColumnsWidth()
+    window.addEventListener('resize', calculateAllColumnsWidth);
+  })
+})
+
+onUnmounted(async () => {
+  window.removeEventListener('resize', calculateAllColumnsWidth);
 })
 
 </script>
@@ -1312,4 +1762,27 @@ onMounted(async () => {
   white-space: nowrap; /* 禁止换行 */
   overflow: visible;   /* 允许内容溢出单元格 */
 }
+
+/* 新增分组表头样式 */
+:deep(.el-table__header) {
+  border: 1px solid #dcdfe6 !important;
+}
+:deep(.el-table__header th) {
+  border-right: 1px solid #dcdfe6 !important;
+  border-bottom: 1px solid #dcdfe6 !important;
+}
+
+:deep(.el-table__header .is-group th) {
+  background-color: #f5f7fa !important;
+  border-bottom: 1px solid #dcdfe6 !important;
+  font-weight: 600;
+  position: relative;
+}
+:deep(.el-table__header .is-group th::after) {
+  display: none !important;
+}
+/* 分组标题下的子表头单元格 */
+:deep(.el-table__header .el-table__cell:not(.is-group)) {
+  border-top: 1px solid #dcdfe6 !important; /* 添加顶部边框连接分组标题 */
+}
 </style>

+ 65 - 1
src/views/pms/iotmainworkorder/WorkOrderMaterial.vue

@@ -1,7 +1,7 @@
 <template>
   <Dialog v-model="dialogVisible" title="选择物料" style="width: 1200px; min-height: 400px">
 
-    <!-- 新增设备分类和BOM节点信息显示 -->
+    <!-- 设备分类和BOM节点信息显示 -->
     <div style="margin: 0 0px 15px; background: #f5f7fa; padding: 12px 20px; border-radius: 4px;">
       <el-row>
         <el-col :span="8">
@@ -19,6 +19,63 @@
       </el-row>
     </div>
 
+    <!-- 可折叠的区域 展示当前保养项 已经绑定的物料-->
+    <div style="margin-bottom: 15px;">
+      <el-button
+        type="text"
+        @click="showMaterialArea = !showMaterialArea"
+        style="padding-left: 20px; margin-bottom: 10px;"
+      >
+        {{ showMaterialArea ? '隐藏保养项物料' : '显示保养项物料' }}
+      </el-button>
+
+      <div
+        v-show="showMaterialArea"
+        style="background: #f5f7fa; padding: 12px 20px; border-radius: 4px;"
+      >
+        <template v-if="deviceBomMaterials.length === 0">
+          <div style="text-align: center; padding: 10px; color: #999;">
+            无绑定物料
+          </div>
+        </template>
+        <template v-else>
+          <!-- 表头 -->
+          <div
+            style="
+            display: grid;
+            grid-template-columns: 1fr 1fr 1fr;
+            gap: 15px;
+            margin-bottom: 12px;
+            font-weight: bold;
+            border-bottom: 1px solid #e4e7ed;
+            padding-bottom: 8px;
+          "
+          >
+            <div>物料编码</div>
+            <div>物料名称</div>
+            <div>数量</div>
+          </div>
+
+          <!-- 内容区 -->
+          <div
+            v-for="(item, index) in deviceBomMaterials"
+            :key="index"
+            style="
+            display: grid;
+            grid-template-columns: 1fr 1fr 1fr;
+            gap: 15px;
+            padding: 8px 0;
+            border-bottom: 1px dashed #ebeef5;
+          "
+          >
+            <div style="word-break: break-all;">{{ item.code || '-' }}</div>
+            <div style="word-break: break-all;">{{ item.name || '-' }}</div>
+            <div>{{ item.quantity !== undefined ? item.quantity : '-' }}</div>
+          </div>
+        </template>
+      </div>
+    </div>
+
     <ContentWrap>
       <el-form
         class="-mb-15px"
@@ -196,6 +253,9 @@ const total = ref(0) // 列表的总页数
 const tableRef = ref()
 const selectedRows = ref<WorkOrderBomMaterialApi.IotMainWorkOrderBomMaterialVO[]>([]) // 多选数据(存储所有选中行的数组)
 
+const showMaterialArea = ref(false) // 控制A区域显示状态
+const deviceBomMaterials = ref<any[]>([]) // 存储绑定物料数据
+
 const deviceName = ref('') // 设备名称
 const deviceCode = ref('')    // 设备编码
 const bomNodeName = ref('')   // 保养项 or 维修项 名称
@@ -280,6 +340,10 @@ const open = async (deptId: number, bomNodeId: number, row: any, type: string) =
   } else if (type === 'repair') {
     bomNodeLabel.value = '维修项'
   }
+
+  // 初始化BOM节点绑定的物料区域
+  deviceBomMaterials.value = row.deviceBomMaterials || []
+  showMaterialArea.value = false // 初始状态隐藏A区域
   await getList()
 }
 

+ 561 - 71
src/views/pms/maintenance/IotMaintenancePlanEdit.vue

@@ -48,37 +48,29 @@
 
     <!-- 列表 -->
     <ContentWrap>
-      <el-table v-loading="loading" :data="pagedList" :stripe="true" :show-overflow-tooltip="true">
+      <el-table ref="tableRef" @header-dragend="handleHeaderDragEnd" v-loading="loading"
+                :data="pagedList" :stripe="true" :show-overflow-tooltip="true"
+                style="table-layout: fixed" border :header-cell-style="tableHeaderStyle">
         <!-- 添加序号列 -->
         <el-table-column
           type="index"
           :label="t('iotDevice.serial')"
-          width="70"
+          :width="columnWidths.serial"
           align="center"
+          prop="serial"
+          fixed="left"
         />
         <el-table-column label="设备id" align="center" prop="deviceId" v-if="false"/>
-        <el-table-column :label="t('iotMaintain.deviceCode')" align="center" prop="deviceCode" />
-        <el-table-column :label="t('iotMaintain.deviceName')" align="center" prop="deviceName" />
-        <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime" :formatter="erpPriceTableColumnFormatter">
+        <el-table-column :label="t('iotMaintain.deviceCode')" align="center" prop="deviceCode" :width="columnWidths.deviceCode" fixed="left"/>
+        <el-table-column :label="t('iotMaintain.deviceName')" align="center" prop="deviceName" :width="columnWidths.deviceName" fixed="left"/>
+        <el-table-column :label="t('bomList.bomNode')" align="center" prop="name" :show-overflow-tooltip="false" :width="columnWidths.name" fixed="left">
           <template #default="{ row }">
-            {{ row.totalRunTime ?? row.tempTotalRunTime }}
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('operationFillForm.sumKil')" align="center" prop="totalMileage" :formatter="erpPriceTableColumnFormatter">
-          <template #default="{ row }">
-            {{ row.totalMileage ?? row.tempTotalMileage }}
-          </template>
-        </el-table-column>
-        <el-table-column label="tempTotalRunTime" align="center" prop="tempTotalRunTime" :formatter="erpPriceTableColumnFormatter" v-if="false"/>
-        <el-table-column label="tempTotalMileage" align="center" prop="tempTotalMileage" :formatter="erpPriceTableColumnFormatter" v-if="false"/>
-        <el-table-column :label="t('bomList.bomNode')" align="center" prop="name" :show-overflow-tooltip="false" :width="bomNodeColumnWidth">
-          <template #default="{ row }">
-            <div class="full-content-cell"> <!-- 自定义样式 -->
+            <div class="full-content-cell">
               {{ row.name }}
             </div>
           </template>
         </el-table-column>
-        <el-table-column :label="t('main.mileage')" key="mileageRule" width="80">
+        <el-table-column :label="t('main.mileage')" key="mileageRule" prop="mileageRule" :width="columnWidths.mileageRule">
           <template #default="scope">
             <el-switch
               v-model="scope.row.mileageRule"
@@ -88,7 +80,7 @@
             />
           </template>
         </el-table-column>
-        <el-table-column :label="t('main.runTime')" key="runningTimeRule" width="90">
+        <el-table-column :label="t('main.runTime')" key="runningTimeRule" prop="runningTimeRule" :width="columnWidths.runningTimeRule">
           <template #default="scope">
             <el-switch
               v-model="scope.row.runningTimeRule"
@@ -98,16 +90,105 @@
             />
           </template>
         </el-table-column>
-        <el-table-column :label="t('main.date')" key="naturalDateRule" width="80">
+        <el-table-column :label="t('main.date')" key="naturalDateRule" prop="naturalDateRule" :width="columnWidths.naturalDateRule">
           <template #default="scope">
             <el-switch
               v-model="scope.row.naturalDateRule"
               :active-value="0"
               :inactive-value="1"
+              @change="handleRuleChange(scope.row, 'date')"
             />
           </template>
         </el-table-column>
-        <el-table-column :label="t('operationFill.operation')" align="center" min-width="120px">
+        <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.totalRunTime">
+          <template #default="{ row }">
+            {{ row.totalRunTime ?? row.tempTotalRunTime }}
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('operationFillForm.sumKil')" align="center" prop="totalMileage"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.totalMileage">
+          <template #default="{ row }">
+            {{ row.totalMileage ?? row.tempTotalMileage }}
+          </template>
+        </el-table-column>
+        <el-table-column label="tempTotalRunTime" align="center" prop="tempTotalRunTime" :formatter="erpPriceTableColumnFormatter" v-if="false"/>
+        <el-table-column label="tempTotalMileage" align="center" prop="tempTotalMileage" :formatter="erpPriceTableColumnFormatter" v-if="false"/>
+        <el-table-column :label="t('mainPlan.lastMaintenanceDate')" prop="lastMaintenanceDate" :width="columnWidths.lastMaintenanceDate">
+          <template #default="{ row }">
+            <div class="full-content-cell">
+              {{ row.lastMaintenanceDate }}
+            </div>
+          </template>
+        </el-table-column>
+        <!-- 保养里程 分组 -->
+        <el-table-column v-if="hasMileageRuleInCurrentPage" label="保养里程" align="center">
+          <el-table-column :label="t('mainPlan.lastMaintenanceMileage')" align="center" prop="lastRunningKilometers"
+                           :formatter="erpPriceTableColumnFormatter" :width="columnWidths.lastRunningKilometers">
+            <template #default="{ row }">
+              {{ row.lastRunningKilometers }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.nextMaintenanceKm')"
+                           align="center" prop="nextMaintenanceKm" :width="columnWidths.nextMaintenanceKm">
+            <template #default="{ row }">
+              {{ row.nextMaintenanceKm ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.remainKm')"
+                           align="center" prop="remainKm" :width="columnWidths.remainKm">
+            <template #default="{ row }">
+              {{ row.remainKm ?? '-' }}
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <!-- 保养时长 分组 -->
+        <el-table-column v-if="hasTimeRuleInCurrentPage" label="保养时长" align="center">
+          <el-table-column :label="t('mainPlan.lastMaintenanceOperationTime')" align="center" prop="lastRunningTime"
+                           :formatter="erpPriceTableColumnFormatter" :width="columnWidths.lastRunningTime">
+            <template #default="{ row }">
+              {{ row.lastRunningTime }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.nextMaintenanceH')"
+                           align="center" prop="nextMaintenanceH" :width="columnWidths.nextMaintenanceH">
+            <template #default="{ row }">
+              {{ row.nextMaintenanceH ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.remainH')"
+                           align="center" prop="remainH" :width="columnWidths.remainH">
+            <template #default="{ row }">
+              {{ row.remainH ?? '-' }}
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <!-- 保养时长 分组 -->
+        <el-table-column v-if="hasDateRuleInCurrentPage" label="保养日期" align="center">
+          <el-table-column :label="t('mainPlan.lastMaintenanceNaturalDate')" align="center" prop="tempLastNaturalDate"
+                           :formatter="erpPriceTableColumnFormatter" :width="columnWidths.tempLastNaturalDate">
+            <template #default="{ row }">
+              {{ row.tempLastNaturalDate }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.nextMaintDate')"
+                           align="center" prop="nextMaintenanceDate" :width="columnWidths.nextMaintenanceDate">
+            <template #default="{ row }">
+              {{ row.nextMaintenanceDate ?? '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('mainPlan.remainDay')"
+                           align="center" prop="remainDay" :width="columnWidths.remainDay">
+            <template #default="{ row }">
+              {{ row.remainDay ?? '-' }}
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column :label="t('operationFill.operation')" align="center"
+                         :width="columnWidths.operation" prop="operation" fixed="right">
           <template #default="scope">
             <div style="display: flex; justify-content: center; align-items: center; width: 100%">
               <div>
@@ -414,10 +495,16 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const deviceLabel = ref('') // 表单的类型:create - 新增;update - 修改
 const list = ref<IotMaintenanceBomVO[]>([]) // 设备bom关联列表的数据
 
+const lastNaturalDateWatchers = ref(new Map())
+
 const deviceIds = ref<number[]>([]) // 已经选择的设备id数组
 
 const bomNodeColumnWidth = ref('auto')
 
+const tableRef = ref();
+
+const columnWidths = ref<Record<string, string>>({});
+
 // 分页相关变量
 const currentPage = ref(1)
 const pageSize = ref(10)
@@ -531,6 +618,13 @@ const openConfigDialog = (row: IotMaintenanceBomVO) => {
   configDialog.visible = true
 }
 
+// 列宽调整后的处理
+const handleHeaderDragEnd = () => {
+  nextTick(() => {
+    tableRef.value?.doLayout();
+  });
+};
+
 // 保存配置
 const saveConfig = () => {
   (configFormRef.value as any).validate((valid: boolean) => {
@@ -639,7 +733,34 @@ const saveConfig = () => {
     }
 
     // 更新当前行的数据
-    Object.assign(configDialog.current, updateData);
+    if (configDialog.current) {
+      Object.assign(configDialog.current, updateData);
+      // 重新计算 下次保养公里数 剩余公里数
+      configDialog.current.nextMaintenanceKm = calculateNextMaintenanceKm(
+        configDialog.current
+      );
+      configDialog.current.remainKm = calculateRemainKm(
+        configDialog.current
+      );
+      // 重新计算 下次保养运行时长 剩余时长
+      configDialog.current.nextMaintenanceH = calculateNextMaintenanceH(
+        configDialog.current
+      );
+      configDialog.current.remainH = calculateRemainH(
+        configDialog.current
+      );
+      // 重新计算 下次保养日期 剩余天数
+      if (configDialog.form.lastNaturalDate) {
+        configDialog.current.tempLastNaturalDate =
+          dayjs(configDialog.form.lastNaturalDate).format('YYYY-MM-DD');
+        configDialog.current.nextMaintenanceDate = calculateNextMaintenanceDate(
+          configDialog.current
+        );
+        configDialog.current.remainDay = calculateRemainDay(
+          configDialog.current
+        );
+      }
+    }
     configDialog.visible = false
   })
 }
@@ -651,7 +772,7 @@ const queryParams = reactive({
 })
 
 // 处理保养规则变化 取消保养规则 时 清空已经设置的相应保养规则数据
-const handleRuleChange = (row: IotMaintenanceBomVO, ruleType: 'mileage' | 'runningTime') => {
+const handleRuleChange = (row: IotMaintenanceBomVO, ruleType: 'mileage' | 'runningTime' | 'date') => {
   // 当规则关闭时(inactive-value=1)
   console.log('执行了保养规则变化事件' + row.totalRunTime + ' - ' + row.totalMileage)
   // 当前保养项行已经返回了 totalRunTime totalMileage 数据 不需要再清空 累计运行时长 累计公里数
@@ -695,6 +816,39 @@ const handleRuleChange = (row: IotMaintenanceBomVO, ruleType: 'mileage' | 'runni
       configDialog.form.accumulatedMileageOption = null;
     }
   }
+
+  // 规则变化后按新条件重新计算 下次保养公里数 剩余公里数
+  if (ruleType === 'mileage') {
+    if(row.mileageRule === 0){
+      row.nextMaintenanceKm = calculateNextMaintenanceKm(row);
+      row.remainKm = calculateRemainKm(row);
+    } else {
+      row.nextMaintenanceKm = null;
+      row.remainKm = null;
+    }
+  }
+
+  // 规则变化后按新条件重新计算 下次保养时长 剩余时长
+  if (ruleType === 'runningTime') {
+    if(row.runningTimeRule === 0){
+      row.nextMaintenanceH = calculateNextMaintenanceH(row);
+      row.remainH = calculateRemainH(row);
+    } else {
+      row.nextMaintenanceH = null;
+      row.remainH = null;
+    }
+  }
+
+  // 规则变化后按新条件重新计算 下次保养日期 剩余天数
+  if (ruleType === 'date') {
+    if(row.naturalDateRule === 0){
+      row.nextMaintenanceDate = calculateNextMaintenanceDate(row);
+      row.remainDay = calculateRemainDay(row);
+    } else {
+      row.nextMaintenanceDate = null;
+      row.remainDay = null;
+    }
+  }
 }
 
 const deviceChoose = async(selectedDevices) => {
@@ -770,10 +924,257 @@ const deviceChoose = async(selectedDevices) => {
   currentPage.value = 1
 }
 
+const getTextWidth = (text: string, fontSize = 14) => {
+  const span = document.createElement('span');
+  span.style.visibility = 'hidden';
+  span.style.position = 'absolute';
+  span.style.whiteSpace = 'nowrap';
+  span.style.fontSize = `${fontSize}px`;
+  span.style.fontFamily = 'inherit';
+  span.innerText = text;
+
+  document.body.appendChild(span);
+  const width = span.offsetWidth;
+  document.body.removeChild(span);
+
+  return width;
+};
+
+// 计算下次保养公里数(通用函数)
+const calculateNextMaintenanceKm = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且 > 0
+  const isValid = row.mileageRule === 0 &&
+    row.lastRunningKilometers > 0 &&
+    row.nextRunningKilometers > 0;
+
+  return isValid
+    ? (row.lastRunningKilometers + row.nextRunningKilometers)
+    : null; // 不满足条件返回null
+};
+
+// 计算剩余保养公里数(通用函数)
+const calculateRemainKm = (row: IotMaintenanceBomVO) => {
+  // 确定使用的里程值(优先totalMileage)
+  const mileageValue = row.totalMileage ?? row.tempTotalMileage;
+  // 验证条件:规则开启 + 3个值都存在且 > 0
+  const isValid = row.mileageRule === 0 &&
+    row.lastRunningKilometers > 0 &&
+    mileageValue > 0 &&
+    row.nextRunningKilometers > 0;
+
+  return isValid
+    ? (row.nextRunningKilometers - (mileageValue - row.lastRunningKilometers))
+    : null; // 不满足条件返回null
+};
+
+// 计算下次保养运行时长(通用函数)
+const calculateNextMaintenanceH = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且 > 0
+  const isValid = row.runningTimeRule === 0 &&
+    row.lastRunningTime > 0 &&
+    row.nextRunningTime > 0;
+
+  return isValid
+    ? (row.lastRunningTime + row.nextRunningTime)
+    : null; // 不满足条件返回null
+};
+
+// 计算剩余运行时间(通用函数)
+const calculateRemainH = (row: IotMaintenanceBomVO) => {
+  // 确定使用的 运行时长 值(优先 totalRunTime)
+  const runTimeValue = row.totalRunTime ?? row.tempTotalRunTime;
+  // 验证条件:规则开启 + 3个值都存在且 > 0
+  const isValid = row.runningTimeRule === 0 &&
+    row.lastRunningTime > 0 &&
+    runTimeValue > 0 &&
+    row.nextRunningTime > 0;
+
+  return isValid
+    ? (row.nextRunningTime - (runTimeValue - row.lastRunningTime))
+    : null; // 不满足条件返回null
+};
+
+// 计算下次保养日期(通用函数)
+const calculateNextMaintenanceDate = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且 > 0
+  const isValid = row.naturalDateRule === 0 &&
+    row.lastNaturalDate &&
+    row.nextNaturalDate;
+
+  return isValid
+    ? dayjs(row.lastNaturalDate).add(row.nextNaturalDate, 'day').format('YYYY-MM-DD')
+    : null; // 不满足条件返回null
+};
+
+// 计算 自然日期保养 剩余天数(通用函数)
+const calculateRemainDay = (row: IotMaintenanceBomVO) => {
+  // 验证条件:规则开启 + 两个值都存在且有效
+  const isValid = row.naturalDateRule === 0 &&
+    row.lastNaturalDate !== null &&
+    row.nextNaturalDate !== null &&
+    row.nextNaturalDate > 0;
+
+  if (!isValid) {
+    return null;
+  }
+
+  try {
+    // 上次保养日期:将时间戳转换为 Day.js 对象
+    const lastNaturalDate = dayjs(row.lastNaturalDate);
+
+    // 计算下次保养日期
+    const nextMaintenanceDate = lastNaturalDate.add(row.nextNaturalDate, 'day');
+
+    // 计算剩余天数(当前日期到下次保养日期的天数差)
+    return nextMaintenanceDate.diff(dayjs(), 'day');
+  } catch (error) {
+    console.error('计算保养剩余天数错误:', error);
+    return null;
+  }
+
+};
+
+// 统一计算所有列宽
+const calculateAllColumnsWidth = () => {
+  const MIN_WIDTH = 80; // 最小列宽
+  const PADDING = 25; // 列内边距
+  const FIXED_COLUMN_PADDING = 15;  // 固定列额外内边距
+
+  // 需要自适应的列配置
+  const autoColumns = [
+    { prop: 'serial', label: t('iotDevice.serial') },
+    { prop: 'deviceCode', label: t('iotMaintain.deviceCode') },
+    { prop: 'deviceName', label: t('iotMaintain.deviceName') },
+    {
+      prop: 'totalRunTime',
+      label: t('operationFillForm.sumTime'),
+      getValue: (row) => row.totalRunTime ?? row.tempTotalRunTime
+    },
+    {
+      prop: 'totalMileage',
+      label: t('operationFillForm.sumKil'),
+      getValue: (row) => row.totalMileage ?? row.tempTotalMileage
+    },
+    { prop: 'name', label: t('bomList.bomNode') },
+    { prop: 'lastMaintenanceDate', label: t('mainPlan.lastMaintenanceDate') },
+    { prop: 'mileageRule', label: t('main.mileage') },
+    { prop: 'runningTimeRule', label: t('main.runTime') },
+    { prop: 'naturalDateRule', label: t('main.date') },
+    { prop: 'lastRunningKilometers', label: t('mainPlan.lastMaintenanceMileage') },
+    { prop: 'nextMaintenanceKm', label: t('mainPlan.nextMaintenanceKm') },
+    { prop: 'remainKm', label: t('mainPlan.remainKm') },
+    { prop: 'lastRunningTime', label: t('mainPlan.lastMaintenanceOperationTime') },
+    { prop: 'nextMaintenanceH', label: t('mainPlan.nextMaintenanceH') },
+    { prop: 'remainH', label: t('mainPlan.remainH') },
+    { prop: 'tempLastNaturalDate', label: t('mainPlan.lastMaintenanceNaturalDate') },
+    { prop: 'nextMaintenanceDate', label: t('mainPlan.nextMaintDate') },
+    { prop: 'remainDay', label: t('mainPlan.remainDay') },
+    { prop: 'operation', label: t('operationFill.operation') }
+  ];
+
+  const newWidths: Record<string, number> = {};
+
+  autoColumns.forEach(col => {
+    const headerText = col.label;
+    // 计算表头宽度
+    const headerWidth = getTextWidth(headerText) * 1.2;
+
+    // 计算内容最大宽度
+    let contentMaxWidth = 0;
+    if (col.prop === 'operation') {
+      // 操作列固定宽度(根据按钮数量)
+      contentMaxWidth = 100;
+    } else if (['mileageRule', 'runningTimeRule', 'naturalDateRule'].includes(col.prop)) {
+      // 开关列固定宽度
+      contentMaxWidth = 80;
+    } else {
+      list.value.forEach(row => {
+        const text = col.getValue ? String(col.getValue(row)) : String(row[col.prop] || '');
+        const textWidth = getTextWidth(text);
+        if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+      });
+    }
+    // 取最大值并添加内边距
+    newWidths[col.prop] = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
+  });
+
+  // 固定列特殊处理 - 增加额外空间
+  ['serial', 'deviceCode', 'deviceName', 'name'].forEach(prop => {
+    if (newWidths[prop]) {
+      newWidths[prop] += FIXED_COLUMN_PADDING;
+    }
+  });
+
+  // 转换为CSS宽度值
+  Object.keys(newWidths).forEach(prop => {
+    columnWidths.value[prop] = `${newWidths[prop]}px`;
+  });
+};
+
+// 计算属性 - 检查当前页是否有开启的里程规则
+const hasMileageRuleInCurrentPage = computed(() => {
+  return pagedList.value.some(row => row.mileageRule === 0);
+});
+
+// 计算属性 - 检查当前页是否有开启的 运行时间 规则
+const hasTimeRuleInCurrentPage = computed(() => {
+  return pagedList.value.some(row => row.runningTimeRule === 0);
+});
+
+// 计算属性 - 检查当前页是否有开启的 自然日期 规则
+const hasDateRuleInCurrentPage = computed(() => {
+  return pagedList.value.some(row => row.naturalDateRule === 0);
+});
+
+// 为每一行建立lastNaturalDate到tempLastNaturalDate的同步
+const setupNaturalDateSync = (row: IotMaintenanceBomVO) => {
+  // 如果该行已有watcher则跳过
+  if (lastNaturalDateWatchers.value.has(row.id)) return
+
+  // 为该行创建单独的watcher
+  const unwatch = watch(
+    () => row.lastNaturalDate,
+    (newVal) => {
+      // 转换日期格式 (时间戳 -> YYYY-MM-DD)
+      row.tempLastNaturalDate = newVal
+        ? dayjs(newVal).format('YYYY-MM-DD')
+        : null;
+    },
+    { immediate: true, deep: true }
+  )
+
+  // 保存watcher用于后续清理
+  lastNaturalDateWatchers.value.set(row.id, unwatch)
+}
+
+const tableHeaderStyle = ({ row, rowIndex }) => {
+  return {
+    border: '1px solid #333',
+    backgroundColor: rowIndex === 0 ? '#f0f9eb' : '#f5f7fa' // 分组行特殊背景
+  }
+}
+
+// 在添加新行时建立同步关系
+watch(list, (newList) => {
+  newList.forEach(row => {
+    if (!lastNaturalDateWatchers.value.has(row.id)) {
+      setupNaturalDateSync(row)
+    }
+  })
+}, { deep: true, immediate: true })
+
 // 监听数据变化重新计算列宽
 watch(() => [...list.value], () => {
-  calculateBomNodeColumnWidth()
-}, { deep: true })
+  calculateAllColumnsWidth()
+}, { deep: true, immediate: true })
+
+// 监听分页数据和规则变化 - 重新布局表格
+watch([pagedList, hasMileageRuleInCurrentPage, hasTimeRuleInCurrentPage, hasDateRuleInCurrentPage], () => {
+  nextTick(() => {
+    tableRef.value?.doLayout();
+    calculateAllColumnsWidth(); // 重新计算列宽
+  });
+});
 
 const deviceFormRef = ref<InstanceType<typeof MainPlanDeviceList>>()
 const openForm = () => {
@@ -977,31 +1378,7 @@ const sortDeviceList = (devices: IotMaintenanceBomVO[]) => {
   })
 };
 
-// 计算BOM节点列宽
-const calculateBomNodeColumnWidth = () => {
-  // 获取表头文本
-  const headerText = t('bomList.bomNode')
 
-  // 计算表头宽度
-  const headerWidth = getTextWidth(headerText)
-
-  // 计算内容最大宽度
-  let contentMaxWidth = 0
-  if (list.value.length > 0) {
-    list.value.forEach(item => {
-      if (item.name) {
-        const width = getTextWidth(item.name)
-        if (width > contentMaxWidth) {
-          contentMaxWidth = width
-        }
-      }
-    })
-  }
-
-  // 取表头和内容最大宽度 + 内边距(25px)
-  const maxWidth = Math.max(headerWidth, contentMaxWidth) + 25
-  bomNodeColumnWidth.value = `${maxWidth}px`
-}
 
 // 累计运行时长变更
 const handleAccumulatedTimeChange = (option) => {
@@ -1056,10 +1433,31 @@ onMounted(async () => {
       const data = await IotMaintenanceBomApi.getMainPlanBOMs(queryParams);
       list.value = []
       if (Array.isArray(data)) {
-        list.value = data.map(item => ({
-          ...item,
-          lastNaturalDate: item.lastNaturalDate
-        }))
+        list.value = data.map(item => {
+          if (item.mileageRule === 0) {
+            item.nextMaintenanceKm = calculateNextMaintenanceKm(item);
+            item.remainKm = calculateRemainKm(item);
+          }
+          if (item.runningTimeRule === 0) {
+            item.nextMaintenanceH = calculateNextMaintenanceH(item);
+            item.remainH = calculateRemainH(item);
+          }
+          if (item.naturalDateRule === 0) {
+            item.nextMaintenanceDate = calculateNextMaintenanceDate(item);
+            item.remainDay = calculateRemainDay(item);
+          }
+          setupNaturalDateSync(item);
+          return {
+            ...item,
+            lastNaturalDate: item.lastNaturalDate,
+            /* tempLastNaturalDate: item.lastNaturalDate
+              ? dayjs(item.lastNaturalDate).format("YYYY-MM-DD")  // 时间戳 → 日期字符串
+              : null, */
+            lastMaintenanceDate: item.lastMaintenanceDate
+              ? dayjs(item.lastMaintenanceDate).format("YYYY-MM-DD")  // 时间戳 → 日期字符串
+              : null // 处理空值
+          }
+        })
         applySorting()
       }
     }
@@ -1070,27 +1468,13 @@ onMounted(async () => {
     formData.value.responsiblePerson = userInfo.user.id;
   }
   nextTick(() => {
-    calculateBomNodeColumnWidth()
+    calculateAllColumnsWidth()
+    window.addEventListener('resize', calculateAllColumnsWidth);
   })
 })
 
-// 辅助函数:计算文本实际宽度 BOM节点名称显示全称
-const getTextWidth = (text: string) => {
-  const span = document.createElement('span')
-  span.style.visibility = 'hidden'
-  span.style.position = 'absolute'
-  span.style.whiteSpace = 'nowrap'
-  span.style.fontSize = '14px' // 与表格实际字体一致
-  span.style.fontFamily = 'inherit' // 继承文档字体
-  span.innerText = text
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-  return width
-}
-
 onUnmounted(async () => {
-  console.log('组件卸载......')
+  window.removeEventListener('resize', calculateAllColumnsWidth);
 })
 
 const handleDelete = async (str: string) => {
@@ -1166,4 +1550,110 @@ const handleDelete = async (str: string) => {
   white-space: nowrap; /* 禁止换行 */
   overflow: visible;   /* 允许内容溢出单元格 */
 }
+
+/* 确保表格容器可滚动 */
+.table-container {
+  overflow-x: auto;
+  width: 100%;
+
+  /* 修复固定列错位 */
+  :deep(.el-table__fixed-body-wrapper) {
+    bottom: 8px !important;
+  }
+}
+
+/* 滚动条调整 */
+:deep(.el-table__body-wrapper) {
+  overflow-x: auto;
+  padding-bottom: 12px;
+}
+
+/* 固定表格布局 */
+el-table {
+  table-layout: fixed;
+  min-width: 100%;
+}
+
+/* 全局禁止换行和省略号 */
+:deep(.el-table th > .cell),
+:deep(.el-table td > .cell) {
+  white-space: nowrap !important;
+  overflow: visible !important;
+  text-overflow: clip !important;
+}
+
+/* 表头特别处理 */
+:deep(.el-table__header) {
+  border: 1px solid #dcdfe6 !important;
+  /* .cell {
+    display: inline-block;
+    white-space: nowrap;
+    width: auto !important;
+  } */
+}
+
+:deep(.el-table__header tr) {
+  border-bottom: 1px solid #e4e7ed !important;
+}
+
+:deep(.el-table__header th) {
+  border-right: 1px solid #dcdfe6 !important;
+  border-bottom: 1px solid #dcdfe6 !important;
+}
+
+:deep(.el-table__header .is-group th) {
+  background-color: #f5f7fa !important;
+  border-bottom: 1px solid #dcdfe6 !important;
+  font-weight: 600;
+  position: relative;
+}
+
+:deep(.el-table__header .is-group th::after) {
+  display: none !important;
+}
+
+/* 分组标题下的子表头单元格 */
+:deep(.el-table__header .el-table__cell:not(.is-group)) {
+  border-top: 1px solid #dcdfe6 !important; /* 添加顶部边框连接分组标题 */
+}
+
+/* 单元格内容强制不换行 */
+:deep(.el-table__body) {
+  .cell {
+    white-space: nowrap !important;
+  }
+}
+
+:deep(.el-table__body-wrapper::-webkit-scrollbar) {
+  height: 12px;
+}
+
+/* 固定列样式修复 */
+:deep(.el-table__fixed) {
+  height: calc(100% - 12px) !important;
+  box-shadow: none !important;
+}
+
+:deep(.el-table__fixed:before) {
+  background-color: transparent !important;
+}
+
+:deep(.el-table__fixed-body-wrapper) {
+  bottom: 12px !important;
+}
+
+/* 固定列内容对齐 */
+:deep(.el-table__fixed .el-table__cell) {
+  background-color: var(--el-table-bg-color);
+}
+
+/* 日期选择器特殊处理 */
+:deep(.el-date-editor) {
+  width: 100% !important;
+  min-width: 220px;
+
+  .el-input__wrapper {
+    padding: 0 30px 0 11px !important; /* 日历图标空间 */
+  }
+}
 </style>