Forráskód Böngészése

Merge remote-tracking branch 'origin/master'

lipenghui 1 hónapja
szülő
commit
7ed0d705cd

+ 4 - 1
src/locales/en.ts

@@ -988,7 +988,8 @@ export default {
     accumulatedParams: 'COP',
     accumulatedRunTime: 'COT',
     accumulatedMileage: 'COK',
-    runningTimeCycleError: 'MCT must be greater than 0'
+    runningTimeCycleError: 'MCT must be greater than 0',
+    maintenanceQuery: 'Maintenance Query'
   },
   inspect:{
     InspectionItems:'InspectionItems',
@@ -1046,6 +1047,7 @@ export default {
     SwitchMapType:'SwitchMapType'
   },
   rem:{
+    AttrTemplates:'Attr Templates',
     DeviceAttrTemplateModel:'DeviceAttrTemplateModel',
     AttributeInformationDetails:'AttributeInformationDetails',
     AddProjectInformation:'AddProjectInformation',
@@ -1089,6 +1091,7 @@ export default {
     InspectPlanEdit:'InspectPlanEdit',
     InspectOrderDetail:'InspectOrderDetail',
     FailureDetail:'FailureDetail',
+    MaterialManagement: 'MaterialManagement'
   },
 }
 

+ 5 - 1
src/locales/ru.ts

@@ -77,6 +77,9 @@ export default {
     hasPermission: `请设置操作权限标签值`,
     hasRole: `请设置角色权限标签值`
   },
+  rem: {
+    MaterialManagement: '物料管理'
+  },
   setting: {
     projectSetting: '项目配置',
     theme: '主题',
@@ -969,7 +972,8 @@ export default {
     accumulatedParams: '累计运行参数',
     accumulatedRunTime: '累计运行时长',
     accumulatedMileage: '累计运行公里数',
-    runningTimeCycleError: '运行时间周期必须大于0'
+    runningTimeCycleError: '运行时间周期必须大于0',
+    maintenanceQuery: '保养查询'
   },
   inspect:{
     InspectionItems:'巡检项',

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

@@ -983,7 +983,8 @@ export default {
     accumulatedParams: '累计运行参数',
     accumulatedRunTime: '累计运行时长',
     accumulatedMileage: '累计运行公里数',
-    runningTimeCycleError: '运行时间周期必须大于0'
+    runningTimeCycleError: '运行时间周期必须大于0',
+    maintenanceQuery: '保养查询'
   },
   inspect:{
     InspectionItems:'巡检项',
@@ -1041,6 +1042,7 @@ export default {
     SwitchMapType:'切换地图类型'
   },
   rem:{
+    AttrTemplates:'属性模板列表',
     DeviceAttrTemplateModel:'设备属性详情',
     AttributeInformationDetails:'属性信息详情',
     AddProjectInformation:'添加项目信息',
@@ -1084,7 +1086,7 @@ export default {
     InspectOrderDetail:'巡检工单详情',
     InspectOrder:'巡检工单',
     FailureDetail:'查看故障详情',
-
+    MaterialManagement: '物料管理'
 
 
   },

+ 146 - 5
src/router/modules/remaining.ts

@@ -82,7 +82,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
     component: Layout,
     name: 'DeviceAttrsCenter',
     meta: {
-      hidden: true
+      hidden: true,
+      keepAlive: true
     },
     children: [
       {
@@ -90,15 +91,131 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/devicetemplate/detail/attrsModel/index.vue'),
         name: 'DeviceAttrTemplateModel',
         meta: {
+          keepAlive: true,
           title: t('rem.DeviceAttrTemplateModel'),
           noCache: false,
           hidden: true,
           canTo: true,
           activeMenu: '/template/info'
         }
+      },
+      {
+        path: 'templates',
+        component: () => import('@/views/pms/devicetemplate/index.vue'),
+        name: 'DeviceAttrsTemplate',
+        meta: {
+          keepAlive: true,
+          title: t('rem.AttrTemplates'),
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          activeMenu: '/templates'
+        }
+      }
+    ]
+  },
+
+  {
+    path: '/devicecategoryboms',
+    component: Layout,
+    name: 'DeviceCategoryBomCenter',
+    meta: {
+      hidden: true,
+      keepAlive: true
+    },
+    children: [
+      {
+        path: 'boms',
+        component: () => import('@/views/pms/bom/index.vue'),
+        name: 'Bom',
+        meta: {
+          keepAlive: true,
+          title: t('rem.AttrTemplates'),
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          activeMenu: '/boms'
+        }
+      }
+    ]
+  },
+
+  {
+    path: '/devicetrends',
+    component: Layout,
+    name: 'DeviceTrendsCenter',
+    meta: {
+      hidden: true,
+      keepAlive: true
+    },
+    children: [
+      {
+        path: 'devicepersons',
+        component: () => import('@/views/pms/device/personlog/DevicePerson.vue'),
+        name: 'IotDevicePerson',
+        meta: {
+          keepAlive: true,
+          title: t('rem.EquipmentResponsiblePerson'),
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          activeMenu: '/devicepersons'
+        }
+      },
+      {
+        path: 'allotlogs',
+        component: () => import('@/views/pms/device/allotlog/DeviceAllot.vue'),
+        name: 'IotDeviceAllot',
+        meta: {
+          keepAlive: true,
+          title: t('rem.EquipmentAllocation'),
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          activeMenu: '/allotlogs'
+        }
+      },
+      {
+        path: 'statuslogs',
+        component: () => import('@/views/pms/device/statuslog/DeviceStatus.vue'),
+        name: 'IotDeviceStatus',
+        meta: {
+          keepAlive: true,
+          title: t('devicePerson.status'),
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          activeMenu: '/statuslogs'
+        }
       }
     ]
   },
+
+  {
+    path: '/materials',
+    component: Layout,
+    name: 'MaterialsCenter',
+    meta: {
+      hidden: true,
+      keepAlive: true
+    },
+    children: [
+      {
+        path: 'materials',
+        component: () => import('@/views/pms/material/index.vue'),
+        name: 'Material',
+        meta: {
+          keepAlive: true,
+          title: t('rem.MaterialManagement'),
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          activeMenu: '/materials'
+        }
+      }
+    ]
+  },
+
   {
     path: '/modelattrstemplate',
     component: Layout,
@@ -376,7 +493,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
     component: Layout,
     name: 'PmsLockStockCenter',
     meta: {
-      hidden: true
+      hidden: true,
+      keepAlive: true
     },
     children: [
       {
@@ -384,6 +502,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/iotlockstock/index.vue'),
         name: 'IotLockStock',
         meta: {
+          keepAlive: true,
           noCache: false,
           hidden: true,
           canTo: true,
@@ -426,7 +545,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
     component: Layout,
     name: 'PmsSapStockCenter',
     meta: {
-      hidden: true
+      hidden: true,
+      keepAlive: true
     },
     children: [
       {
@@ -434,6 +554,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/iotsapstock/index.vue'),
         name: 'IotSapStock',
         meta: {
+          keepAlive: true,
           noCache: false,
           hidden: true,
           canTo: true,
@@ -476,7 +597,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
     component: Layout,
     name: 'PmsMaintenanceCenter',
     meta: {
-      hidden: true
+      hidden: true,
+      keepAlive: true
     },
     children: [
       {
@@ -484,6 +606,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/maintenance/index.vue'),
         name: 'IotMaintenancePlan',
         meta: {
+          keepAlive: true,
           noCache: false,
           hidden: true,
           canTo: true,
@@ -497,6 +620,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/maintenance/IotMaintenancePlan.vue'),
         name: 'IotAddMainPlan',
         meta: {
+          keepAlive: true,
           noCache: false,
           hidden: true,
           canTo: true,
@@ -510,6 +634,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/maintenance/IotMaintenancePlanEdit.vue'),
         name: 'IotMainPlanEdit',
         meta: {
+          keepAlive: true,
           noCache: true,
           hidden: true,
           canTo: true,
@@ -539,7 +664,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
     component: Layout,
     name: 'PmsMainWorkOrderCenter',
     meta: {
-      hidden: true
+      hidden: true,
+      keepAlive: true,
     },
     children: [
       {
@@ -547,6 +673,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/iotmainworkorder/index.vue'),
         name: 'IotMainWorkOrder',
         meta: {
+          keepAlive: true,
           noCache: false,
           hidden: true,
           canTo: true,
@@ -555,6 +682,20 @@ const remainingRouter: AppRouteRecordRaw[] = [
           activeMenu: '/mainworkorder/index'
         }
       },
+      {
+        path: 'workorderalarm',
+        component: () => import('@/views/pms/iotmainworkorder/IotDeviceMainAlarm.vue'),
+        name: 'IotDeviceMainAlarm',
+        meta: {
+          keepAlive: true,
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:menu',
+          title: t('mainPlan.maintenanceQuery'),
+          activeMenu: '/mainworkorder/alarm'
+        }
+      },
       {
         path: 'mainworkorder/bom/:id(\\d+)',
         component: () => import('@/views/pms/iotmainworkorder/IotMainWorkOrder.vue'),

+ 157 - 42
src/views/pms/bom/MaterialList.vue

@@ -72,46 +72,53 @@
       </div>
     </ContentWrap>
 
-    <ContentWrap>
-      <el-table
-        ref="tableRef"
-        v-loading="loading"
-                :data="list"
-                :stripe="true"
-                :show-overflow-tooltip="true"
-                @row-click="handleRowClick">
-        <el-table-column width="80" :label="t('workOrderMaterial.select')">
-          <template #default="{ row }">
-            <el-checkbox
-              :model-value="selectedRows.some(item => item.id === row.id)"
-              @click.stop="selectRow(row)"
-              class="no-label-radio"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('workOrderMaterial.materialName')" align="center" prop="name" />
-        <el-table-column :label="t('workOrderMaterial.materialCode')" align="center" prop="code" />
-        <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" />
-        <el-table-column :label="t('route.quantity')" align="center" prop="quantity">
-          <template #default="scope">
-            <el-input
-              type="number"
-              :controls="false"
-              v-model="scope.row.quantity"
-              @click.stop=""
-              @focus="handleInputFocus(scope.row)"
-              @blur="(event) => handleQuantityBlur(event, scope.row)"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column
-          :label="t('deviceList.createTime')"
-          align="center"
-          prop="createTime"
-          width="180"
-          :formatter="dateFormatter"
-        />
-      </el-table>
+    <ContentWrap class="table-content-wrap">
+      <div class="flex-table-container">
+        <el-table
+          ref="tableRef"
+          v-loading="loading"
+                  :data="list"
+                  :stripe="true"
+                  :row-key="rowKey"
+                  :show-overflow-tooltip="true"
+                  table-layout="auto"
+                  @row-click="handleRowClick"
+                  class="full-width-table" >
+          <el-table-column :label="t('workOrderMaterial.select')" class-name="select-column" min-width="30">
+            <template #default="{ row }">
+              <el-checkbox
+                :model-value="selectedRows.some(item => item.id === row.id)"
+                @click.stop="selectRow(row)"
+                class="no-label-radio"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column :label="t('workOrderMaterial.materialName')" align="center" prop="name"
+                           class-name="name-column" min-width="180"/>
+          <el-table-column :label="t('workOrderMaterial.materialCode')" align="center" prop="code" min-width="130"/>
+          <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" min-width="50"/>
+          <el-table-column :label="t('route.quantity')" align="center" prop="quantity" min-width="80" class-name="quantity-column">
+            <template #default="scope">
+              <el-input
+                type="number"
+                :controls="false"
+                v-model="scope.row.quantity"
+                @click.stop=""
+                @focus="handleInputFocus(scope.row)"
+                @blur="(event) => handleQuantityBlur(event, scope.row)"
+                class="quantity-input"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('deviceList.createTime')"
+            align="center"
+            prop="createTime"
+            min-width="100"
+            :formatter="dateFormatter"
+          />
+        </el-table>
+      </div>
       <!-- 分页 -->
       <Pagination
         :total="total"
@@ -120,6 +127,7 @@
         @pagination="getList"
       />
     </ContentWrap>
+
   </Dialog>
 </template>
 
@@ -147,7 +155,7 @@ const queryFormRef = ref() // 搜索的表单
 const list = ref<MaterialApi.MaterialVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const selectedRows = ref<MaterialApi.MaterialVO[]>([]); // 多选数据(存储所有选中行的数组)
-const tableRef = ref();
+const tableRef = ref<InstanceType<typeof ElTable>>();
 
 const deviceCategoryName = ref('') // 存储设备分类名称
 const bomNodeName = ref('') // 存储BOM节点名称
@@ -172,6 +180,13 @@ const updateSelectedRowQuantity = (row: MaterialApi.MaterialVO) => {
   }
 };
 
+const adjustColumnWidths = () => {
+  nextTick(() => {
+    if (tableRef.value) {
+      tableRef.value.doLayout();
+    }
+  });
+};
 
 // 处理单选逻辑
 const selectRow = (row) => {
@@ -214,6 +229,9 @@ const getList = async () => {
         row.quantity = selectedItem.quantity;
       }
     });
+
+    // 数据加载后自动调整列宽
+    adjustColumnWidths();
   } finally {
     loading.value = false
   }
@@ -276,6 +294,44 @@ const handleInputFocus = (row: MaterialApi.MaterialVO) => {
   }
 }
 
+// 计算列宽
+const flexColumnWidth = (prop: string, label: string, data: any[]) => {
+  if (!data || !data.length) return 'auto';
+
+  const contentWidths = data.map(item => {
+    const value = prop && item[prop] ? String(item[prop]) : '';
+    return getTextWidth(value);
+  });
+
+  const labelWidth = getTextWidth(label);
+  const maxContentWidth = Math.max(...contentWidths);
+  const maxWidth = Math.max(labelWidth, maxContentWidth);
+
+  return Math.min(Math.max(maxWidth + 10, 120), 600) + 'px'; // 10px内边距,120px最小宽度
+};
+
+// 获取文本宽度
+const getTextWidth = (text: string) => {
+  if (!text) return 0;
+
+  const span = document.createElement('span');
+  span.style.visibility = 'hidden';
+  span.style.position = 'absolute';
+  span.style.whiteSpace = 'nowrap';
+  span.style.font = '14px Microsoft YaHei'; // 匹配表格字体
+  span.textContent = text;
+  document.body.appendChild(span);
+
+  const width = span.offsetWidth;
+  document.body.removeChild(span);
+  return width;
+};
+
+// 生成行唯一标识
+const rowKey = (row: any) => {
+  return `${row.id}-${Date.now()}`; // 确保行更新时重新渲染
+};
+
 // 确认选择
 const handleConfirm = () => {
   // 同步当前页所有选中行的最新数量
@@ -320,6 +376,12 @@ onMounted(async () => {
   materialGroupList.value = handleTree(await MaterialGroupApi.getSimpleMaterialGroupList())
   await getList()
 })
+
+// 清除事件监听
+onUnmounted(() => {
+  window.removeEventListener('resize', adjustColumnWidths);
+});
+
 </script>
 <style lang="scss" scoped>
 .no-label-radio .el-radio__label {
@@ -329,6 +391,54 @@ onMounted(async () => {
   margin-right: 0;
 }
 
+.table-content-wrap {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 300px; // 确保有最小高度
+}
+
+.flex-table-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+
+  // 确保表格填满容器
+  .full-width-table {
+    width: 100%;
+    flex: 1;
+
+    // 让表格头与内容同步宽度
+    :deep(.el-table__header) {
+      width: 100% !important;
+    }
+
+    :deep(.el-table__body) {
+      width: 100% !important;
+    }
+  }
+}
+
+// 名称列:优先分配剩余空间
+:deep(.name-column) {
+  flex-grow: 1 !important;
+  min-width: 180px;
+
+  .cell {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 100%;
+  }
+}
+
+/* 新增表格容器样式 */
+.table-container {
+  width: 100%;
+  overflow-x: auto;
+}
+
 /* 自定义淡绿色按钮 */
 :deep(.custom-green-button) {
   background-color: #e1f3d8;
@@ -349,7 +459,7 @@ onMounted(async () => {
   border-color: #c2dca8;
 }
 
-// 新增标签样式
+// 标签样式
 .close-icon {
   cursor: pointer;
   color: #999;
@@ -370,4 +480,9 @@ onMounted(async () => {
   }
 }
 
+/* 调整输入框宽度 */
+:deep(.el-input) {
+  width: 95% !important; /* 保留5%空间避免溢出 */
+}
+
 </style>

+ 137 - 4
src/views/pms/bom/MaterialListDrawer.vue

@@ -27,8 +27,29 @@
         <el-table :data="materials" style="width: 100%" fit :row-style="{ height: '48px' }">
           <el-table-column prop="name" label="物料名称" min-width="150" show-overflow-tooltip/>
           <el-table-column prop="code" label="物料编码" min-width="80" />
-          <el-table-column prop="quantity" label="数量" min-width="60" align="center"/>
-          <el-table-column prop="unit" label="单位" min-width="60" align="center"/>
+          <el-table-column prop="quantity" label="数量" min-width="70" align="center">
+            <template #default="scope">
+              <el-form
+                :model="scope.row"
+                :rules="quantityRules"
+                ref="quantityFormRef"
+                @submit.prevent
+              >
+                <el-form-item prop="quantity" :error="scope.row.quantityError">
+                  <el-input
+                    v-model.number="scope.row.quantity"
+                    type="number"
+                    size="small"
+                    placeholder="请输入数量"
+                    @blur="clearQuantityError(scope.row)"
+                    @keyup.enter="handleUpdate(scope.row)"
+                    :class="{ 'error-input': scope.row.quantityError }"
+                  />
+                </el-form-item>
+              </el-form>
+            </template>
+          </el-table-column>
+          <el-table-column prop="unit" label="单位" min-width="50" align="center"/>
           <el-table-column
             label="创建时间"
             align="center"
@@ -36,8 +57,15 @@
             min-width="110"
             :formatter="dateFormatter"
           />
-          <el-table-column label="操作" align="right" width="100" fixed="right">
+          <el-table-column label="操作" align="right" width="180" fixed="right">
             <template #default="scope">
+              <el-button
+                size="small"
+                type="success"
+                :loading="scope.row.updating"
+                @click="handleUpdate(scope.row)"
+              >更新</el-button>
+
               <el-button
                 size="small"
                 type="danger"
@@ -83,6 +111,24 @@ const queryParams = reactive({
   code: ''
 })
 
+const quantityRules: FormRules = {
+  quantity: [
+    { required: true, message: '数量不能为空', trigger: 'blur' },
+    {
+      validator: (rule, value, callback) => {
+        if (isNaN(value)) {
+          callback(new Error('请输入有效数字'));
+        } else if (value <= 0) {
+          callback(new Error('数量必须大于0'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'blur'
+    }
+  ]
+};
+
 const windowWidth = ref(window.innerWidth)
 // 动态计算百分比
 const computedSize = computed(() => {
@@ -121,7 +167,11 @@ const loadMaterials = async (nodeId) => {
     // API调用
     // const data = await PmsMaterialApi.listByBomId(queryParams)
     const data = await CommonBomMaterialApi.getCommonBomMaterialPage(queryParams)
-    materials.value = data.list
+    materials.value = data.list.map(item => ({
+      ...item,
+      updating: false, // 更新状态标记
+      quantityError: '' // 错误状态标记
+    }))
     total.value = data.total
   } catch (error) {
     ElMessage.error('数据加载失败')
@@ -130,6 +180,52 @@ const loadMaterials = async (nodeId) => {
   }
 }
 
+/** 清除数量错误状态 */
+const clearQuantityError = (row: CommonBomMaterialVO) => {
+  // 延迟清除错误状态,避免在验证过程中清除
+  setTimeout(() => {
+    row.quantityError = '';
+  }, 100);
+}
+
+/** 更新物料数据 */
+const handleUpdate = async (row: CommonBomMaterialVO) => {
+  try {
+    if (isNaN(row.quantity)) {
+      row.quantityError = '请输入有效数字';
+      ElMessage.warning('请输入有效的数字');
+      return;
+    }
+
+    if (row.quantity <= 0) {
+      row.quantityError = '数量必须大于0';
+      ElMessage.warning('数量必须大于0');
+      return;
+    }
+
+    // 清除错误状态
+    row.quantityError = '';
+
+    // 设置更新状态
+    row.updating = true
+    // 构造更新参数
+    const updateParams = {
+      ...row,
+      bomNodeId: props.nodeId
+    }
+    // 调用更新接口
+    await CommonBomMaterialApi.updateCommonBomMaterial(updateParams)
+    // 更新成功反馈
+    message.success('数量更新成功')
+    // 可选:刷新父组件数据
+    // emit('refresh')
+  } catch (error) {
+    ElMessage.error('更新失败')
+  } finally {
+    row.updating = false
+  }
+}
+
 /** 删除BOM节点已经挂载的物料 */
 const handleDelete = async (row) => {
   try {
@@ -180,6 +276,43 @@ defineExpose({ openDrawer, closeDrawer, loadMaterials }) // 暴露方法给父
   }
 }
 
+/* 优化输入框样式 */
+.el-input {
+  width: 80px;
+
+  :deep(.el-input__inner) {
+    text-align: center;
+    padding: 0 5px;
+  }
+  &.error-input :deep(.el-input__inner) {
+    border-color: #f56c6c;
+    background-color: #fff6f7;
+  }
+}
+
+/* 错误提示样式 */
+:deep(.el-form-item__error) {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  padding-top: 2px;
+  font-size: 12px;
+  line-height: 1;
+  color: #f56c6c;
+  white-space: nowrap;
+}
+
+/* 更新按钮自定义样式 */
+.el-button--success {
+  background-color: #e8f5e9;
+  border-color: #c8e6c9;
+  color: #2e7d32;
+
+  &:hover {
+    background-color: #c8e6c9;
+  }
+}
+
 .info-header {
   display: flex;
   align-items: center;

+ 8 - 9
src/views/pms/iotmainworkorder/IotMainWorkOrder.vue

@@ -181,7 +181,7 @@
           <el-table-column :label="t('mainPlan.remainKm')"
                            align="center" prop="remainKm" :width="columnWidths.remainKm">
             <template #default="{ row }">
-              {{ row.remainKm ?? '-' }}
+              {{ row.remainKm !== null ? row.remainKm.toFixed(2) : '-' }}
             </template>
           </el-table-column>
         </el-table-column>
@@ -203,7 +203,7 @@
           <el-table-column :label="t('mainPlan.remainH')"
                            align="center" prop="remainH" :width="columnWidths.remainH">
             <template #default="{ row }">
-              {{ row.remainH ?? '-' }}
+              {{ row.remainH !== null ? row.remainH.toFixed(2) : '-' }}
             </template>
           </el-table-column>
           <el-table-column
@@ -1281,9 +1281,9 @@ const calculateRemainKm = (row: IotMaintenanceBomVO) => {
     mileageValue > 0 &&
     row.nextRunningKilometers > 0;
 
-  return isValid
-    ? (row.nextRunningKilometers - (mileageValue - row.lastRunningKilometers))
-    : null; // 不满足条件返回null
+  if (!isValid) return null;
+  const result = row.nextRunningKilometers - (mileageValue - row.lastRunningKilometers);
+  return parseFloat(result.toFixed(2));
 };
 
 // 计算下次保养运行时长(通用函数)
@@ -1307,10 +1307,9 @@ const calculateRemainH = (row: IotMaintenanceBomVO) => {
     row.lastRunningTime > 0 &&
     runTimeValue > 0 &&
     row.nextRunningTime > 0;
-
-  return isValid
-    ? (row.nextRunningTime - (runTimeValue - row.lastRunningTime))
-    : null; // 不满足条件返回null
+  if (!isValid) return null;
+  const result = row.nextRunningTime - (runTimeValue - row.lastRunningTime);
+  return parseFloat(result.toFixed(2));
 };
 
 // 计算下次保养日期(通用函数)