Kaynağa Gözat

feat(pms): 优化设备保养预警列表及详情弹窗

- 修复预警列表查询项标签换行问题
- 优化详情弹窗标题、设备信息及保养项目区域布局
- 将详情表格替换为 ZmTable 统一组件
- 保留保养时长、里程及日期的动态分组表头
- 移除手动列宽计算、宽度缓存及相关监听逻辑
- 移除 Element Plus 表格样式覆盖,使用 ZmTable 默认样式
- 优化弹窗响应式宽度及分页区域右侧间距
Zimo 1 hafta önce
ebeveyn
işleme
09fdeaf885

+ 307 - 426
src/views/pms/iotmainworkorder/DeviceAlarmBomList.vue

@@ -1,137 +1,165 @@
 <template>
-  <Dialog v-model="dialogVisible"
-          :title="t('monitor.details')"
-          :width="dialogWidth"
-          class="fixed-height-dialog"
-          @close="handleClose" >
-    <ContentWrap>
-
-      <!-- 添加设备信息展示区域 -->
-      <div v-if="deviceInfo" class="device-info-card">
-        <div class="info-item">
-          <span class="info-label">{{ t('iotDevice.code') }}:</span>
-          <span class="info-value">{{ deviceInfo.deviceCode }}</span>
-        </div>
-        <div class="info-item">
-          <span class="info-label">{{ t('iotDevice.name') }}:</span>
-          <span class="info-value">{{ deviceInfo.deviceName }}</span>
-        </div>
-        <div class="info-item" v-if="deviceInfo.model">
-          <span class="info-label">{{ t('deviceForm.model') }}:</span>
-          <span class="info-value">{{ deviceInfo.model }}</span>
+  <Dialog
+    v-model="dialogVisible"
+    :title="t('monitor.details')"
+    :width="dialogWidth"
+    class="device-alarm-detail-dialog"
+    @close="handleClose">
+    <template #title>
+      <div class="dialog-title">
+        <div class="dialog-title__icon"><Icon icon="ep:calendar" /></div>
+        <div>
+          <div class="dialog-title__text">{{ t('monitor.details') }}</div>
+          <div class="dialog-title__subtitle">查看设备保养项目及各维度保养里程</div>
         </div>
       </div>
-
-      <div class="table-container">
-        <!-- 添加表格容器并设置滚动 -->
-        <el-table
-          v-loading="loading"
-          :data="paginatedList"
-          :stripe="true"
-          :show-overflow-tooltip="true"
-          style="width: auto"
-          height="100%"
-          class="scrollable-table"
-          ref="tableRef"
-        >
-          <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode"
-                           :width="columnWidths.deviceCode" v-if="false"/>
-          <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName"
-                           :width="columnWidths.deviceName" v-if="false"/>
-          <el-table-column :label="t('mainPlan.MaintItems')" align="center" prop="name"
-                           :width="columnWidths.name"/>
-
-          <el-table-column :label="t('operationFillForm.sumTime')" align="center" prop="totalRunTime"
-                           :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"
-                           :width="columnWidths.totalMileage">
-            <template #default="{ row }">
-              {{ row.totalMileage ?? row.tempTotalMileage }}
-            </template>
-          </el-table-column>
-
-          <!-- 时间分组列 -->
-          <el-table-column v-if="showTimeColumns" label="保养时长"
-                           align="center"
-                           :width="columnWidths.timeGroup">
-            <el-table-column :label="t('mainPlan.lastMaintenanceOperationTime')" align="center" prop="lastRunningTime"
-                             :width="columnWidths.lastRunningTime"/>
-            <el-table-column :label="t('mainPlan.RunTimeCycle')" align="center" prop="nextRunningTime"
-                             :width="columnWidths.nextRunningTime"/>
-            <el-table-column :label="t('mainPlan.nextMaintTime')" align="center" prop="timePeriod"
-                             :width="columnWidths.timePeriod">
+    </template>
+
+    <div class="dialog-content">
+      <section v-if="deviceInfo" class="device-info-section">
+        <div class="section-heading"><span></span>设备信息</div>
+        <div class="device-info-grid">
+          <div class="info-item">
+            <span class="info-label">{{ t('iotDevice.code') }}</span>
+            <span class="info-value">{{ deviceInfo.deviceCode }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">{{ t('iotDevice.name') }}</span>
+            <span class="info-value">{{ deviceInfo.deviceName }}</span>
+          </div>
+          <div class="info-item" v-if="deviceInfo.model">
+            <span class="info-label">{{ t('deviceForm.model') }}</span>
+            <span class="info-value">{{ deviceInfo.model }}</span>
+          </div>
+        </div>
+      </section>
+
+      <section class="maintenance-section">
+        <div class="section-heading"><span></span>保养项目</div>
+        <div class="table-container">
+          <ZmTable
+            :loading="loading"
+            :data="paginatedList"
+            :show-border="true"
+            settings-cache-key="pms-device-maintenance-alarm-detail">
+            <ZmTableColumn
+              :label="t('iotDevice.code')"
+              align="center"
+              prop="deviceCode"
+              v-if="false" />
+            <ZmTableColumn
+              :label="t('iotDevice.name')"
+              align="center"
+              prop="deviceName"
+              v-if="false" />
+            <ZmTableColumn :label="t('mainPlan.MaintItems')" align="center" prop="name" />
+
+            <ZmTableColumn
+              :label="t('operationFillForm.sumTime')"
+              align="center"
+              prop="totalRunTime">
               <template #default="{ row }">
-                <span :class="{ 'negative-value': isNegative(row.timePeriod) }">
-                  {{ row.timePeriod }}
-                </span>
+                {{ row.totalRunTime ?? row.tempTotalRunTime }}
               </template>
-            </el-table-column>
-          </el-table-column>
-
-          <!-- 里程分组列 -->
-          <el-table-column v-if="showMileageColumns" label="保养里程"
-                           align="center"
-                           :width="columnWidths.mileageGroup">
-            <el-table-column :label="t('mainPlan.lastMaintenanceMileage')" align="center" prop="lastRunningKilometers"
-                             :width="columnWidths.lastRunningKilometers"/>
-            <el-table-column :label="t('mainPlan.operatingMileageCycle')" align="center" prop="nextRunningKilometers"
-                             :width="columnWidths.nextRunningKilometers"/>
-            <el-table-column :label="t('mainPlan.nextMaintKil')" align="center" prop="kilometerCycle"
-                             :width="columnWidths.kilometerCycle">
+            </ZmTableColumn>
+            <ZmTableColumn
+              :label="t('operationFillForm.sumKil')"
+              align="center"
+              prop="totalMileage">
               <template #default="{ row }">
-                <span :class="{ 'negative-value': isNegative(row.kilometerCycle) }">
-                  {{ row.kilometerCycle }}
-                </span>
+                {{ row.totalMileage ?? row.tempTotalMileage }}
               </template>
-            </el-table-column>
-          </el-table-column>
-
-          <!-- 日期分组列 -->
-          <el-table-column v-if="showNaturalDateColumns" label="保养日期"
-                           align="center"
-                           :width="columnWidths.dateGroup">
-            <el-table-column :label="t('mainPlan.lastMaintenanceNaturalDate')" align="center" prop="lastNaturalDate"
-                             :width="columnWidths.lastNaturalDate">
-              <template #default="scope">
-                <el-date-picker
-                  v-model="scope.row.lastNaturalDate"
-                  type="date"
-                  placeholder="选择日期"
-                  format="YYYY-MM-DD"
-                  value-format="YYYY-MM-DD"
-                  style="width: 100%"
-                  :disabled="true"
-                />
-              </template>
-            </el-table-column>
-            <el-table-column :label="t('mainPlan.NaturalDailyCycle')" align="center" prop="nextNaturalDate"
-                             :width="columnWidths.nextNaturalDate"/>
-            <el-table-column :label="t('mainPlan.nextMaintDate')" align="center" prop="naturalDatePeriod"
-                             :width="columnWidths.naturalDatePeriod"/>
-          </el-table-column>
-        </el-table>
-      </div>
-      <Pagination
-        :total="total"
-        v-model:page="queryParams.pageNo"
-        v-model:limit="queryParams.pageSize"
-        @pagination="handlePagination"
-      />
-    </ContentWrap>
+            </ZmTableColumn>
+
+            <!-- 时间分组列 -->
+            <ZmTableColumn v-if="showTimeColumns" label="保养时长" align="center">
+              <ZmTableColumn
+                :label="t('mainPlan.lastMaintenanceOperationTime')"
+                align="center"
+                prop="lastRunningTime" />
+              <ZmTableColumn
+                :label="t('mainPlan.RunTimeCycle')"
+                align="center"
+                prop="nextRunningTime" />
+              <ZmTableColumn :label="t('mainPlan.nextMaintTime')" align="center" prop="timePeriod">
+                <template #default="{ row }">
+                  <span :class="{ 'negative-value': isNegative(row.timePeriod) }">
+                    {{ row.timePeriod }}
+                  </span>
+                </template>
+              </ZmTableColumn>
+            </ZmTableColumn>
+
+            <!-- 里程分组列 -->
+            <ZmTableColumn v-if="showMileageColumns" label="保养里程" align="center">
+              <ZmTableColumn
+                :label="t('mainPlan.lastMaintenanceMileage')"
+                align="center"
+                prop="lastRunningKilometers" />
+              <ZmTableColumn
+                :label="t('mainPlan.operatingMileageCycle')"
+                align="center"
+                prop="nextRunningKilometers" />
+              <ZmTableColumn
+                :label="t('mainPlan.nextMaintKil')"
+                align="center"
+                prop="kilometerCycle">
+                <template #default="{ row }">
+                  <span :class="{ 'negative-value': isNegative(row.kilometerCycle) }">
+                    {{ row.kilometerCycle }}
+                  </span>
+                </template>
+              </ZmTableColumn>
+            </ZmTableColumn>
+
+            <!-- 日期分组列 -->
+            <ZmTableColumn v-if="showNaturalDateColumns" label="保养日期" align="center">
+              <ZmTableColumn
+                :label="t('mainPlan.lastMaintenanceNaturalDate')"
+                align="center"
+                prop="lastNaturalDate">
+                <template #default="scope">
+                  <el-date-picker
+                    v-model="scope.row.lastNaturalDate"
+                    type="date"
+                    placeholder="选择日期"
+                    format="YYYY-MM-DD"
+                    value-format="YYYY-MM-DD"
+                    style="width: 100%"
+                    :disabled="true" />
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                :label="t('mainPlan.NaturalDailyCycle')"
+                align="center"
+                prop="nextNaturalDate" />
+              <ZmTableColumn
+                :label="t('mainPlan.nextMaintDate')"
+                align="center"
+                prop="naturalDatePeriod" />
+            </ZmTableColumn>
+          </ZmTable>
+        </div>
+        <Pagination
+          class="mr-4"
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="handlePagination" />
+      </section>
+    </div>
   </Dialog>
 </template>
 
 <script setup lang="ts">
 import { DictDataVO } from '@/api/system/dict/dict.data'
 import dayjs from 'dayjs'
-import { IotMainWorkOrderBomApi, IotMainWorkOrderBomVO } from '@/api/pms/iotmainworkorderbom'
+import { IotMainWorkOrderBomApi } from '@/api/pms/iotmainworkorderbom'
 import { IotMaintenanceBomApi, IotMaintenanceBomVO } from '@/api/pms/iotmaintenancebom'
-import {propTypes} from "@/utils/propTypes";
+import { propTypes } from '@/utils/propTypes'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 const { t } = useI18n() // 国际化
+const { ZmTable, ZmTableColumn } = useTableComponents<IotMaintenanceBomVO>()
 const emit = defineEmits(['close']) // 定义 success 事件,用于操作成功后的回调
 const dialogVisible = ref(false) // 弹窗的是否展示
 const loading = ref(true) // 列表的加载中
@@ -145,10 +173,7 @@ const shouldResetPagination = ref(false)
 // 添加外部传入的设备信息
 const externalDeviceInfo = ref(null)
 
-const dialogWidth = '1500px';
-
-const tableRef = ref(null)    // 表格实例引用
-const columnWidths = ref({}) // 存储列宽的对象
+const dialogWidth = 'min(1200px, 92vw)'
 
 const queryParams = reactive({
   pageNo: 1,
@@ -159,69 +184,11 @@ const queryParams = reactive({
 })
 
 const props = defineProps({
-  flag: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
+  flag: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired
 })
 
 const selectedRow = ref(null)
 
-const getTextWidth = (str: string) => {
-  if (!str) return 0;
-  if (widthCache.has(str)) return widthCache.get(str)!;
-
-  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 = str;
-
-  document.body.appendChild(span);
-  const width = span.offsetWidth;
-  document.body.removeChild(span);
-
-  widthCache.set(str, width);
-  return width;
-};
-
-// 添加列宽计算函数
-const widthCache = new Map<string, number>();
-
-const flexColumnWidth = (label: string, prop: keyof IotMaintenanceBomVO) => {
-  // 确保有数据时才计算
-  if (!paginatedList.value.length) return "auto";
-  // 基础内边距30px
-  const basePadding = 20;
-  const labelWidth = getActualWidth(label) + basePadding; // 文本宽度 + 内边距
-  // 计算内容最大宽度
-  let maxContentWidth = 0;
-  // 获取该列所有内容的宽度
-  for (const row of paginatedList.value) {
-    let value = "";
-
-    // 特殊列处理
-    if (prop === "totalRunTime") {
-      value = (row.totalRunTime ?? row.tempTotalRunTime)?.toString() || "";
-    } else if (prop === "totalMileage") {
-      value = (row.totalMileage ?? row.tempTotalMileage)?.toString() || "";
-    } else {
-      value = row[prop]?.toString() || "";
-    }
-
-    // 数值格式化
-    if (value && !isNaN(Number(value))) {
-      value = Number(value).toLocaleString();
-    }
-
-    const contentWidth = getActualWidth(value) + basePadding;
-    if (contentWidth > maxContentWidth) {
-      maxContentWidth = contentWidth;
-    }
-  }
-
-  // 返回较大值加上安全边距
-  return Math.max(labelWidth, maxContentWidth, 120) + "px";
-};
-
 // 处理单选逻辑
 const selectRow = (row) => {
   selectedRow.value = selectedRow.value?.id === row.id ? null : row
@@ -231,82 +198,21 @@ const selectRow = (row) => {
 
 // 分页事件处理
 const handlePagination = () => {
-  console.log("分页变化,当前页:", queryParams.pageNo);
-};
-
-// 改进的宽度计算函数
-const getActualWidth = (text: string) => {
-  if (!text) return 0;
-
-  // 更精准的字符宽度计算
-  const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
-  const otherChars = text.length - chineseChars;
-
-  // 中文16px,英文9px
-  return chineseChars * 16 + otherChars * 9;
-};
-
-// 设置列宽
-const setColumnWidths = () => {
-  const widths: Record<string, string> = {};
-
-  // 固定列
-  // widths.deviceCode = flexColumnWidth(t('iotDevice.code'), 'deviceCode');
-  // widths.deviceName = flexColumnWidth(t('iotDevice.name'), 'deviceName');
-  widths.totalRunTime = flexColumnWidth(t('operationFillForm.sumTime'), 'totalRunTime');
-  widths.totalMileage = flexColumnWidth(t('operationFillForm.sumKil'), 'totalMileage');
-  widths.name = flexColumnWidth(t('mainPlan.MaintItems'), 'name');
-
-  // 动态列
-  if (showTimeColumns.value) {
-    widths.lastRunningTime = flexColumnWidth(t('mainPlan.lastMaintenanceOperationTime'), 'lastRunningTime');
-    widths.nextRunningTime = flexColumnWidth(t('mainPlan.RunTimeCycle'), 'nextRunningTime');
-    widths.timePeriod = flexColumnWidth(t('mainPlan.nextMaintTime'), 'timePeriod');
-    // 分组列宽度 = 子列宽度之和 + 边框补偿
-    widths.timeGroup = `${[
-      parseFloat(widths.lastRunningTime),
-      parseFloat(widths.nextRunningTime),
-      parseFloat(widths.timePeriod)
-    ].reduce((sum, w) => sum + w, 0) + 2}px`;
-  }
-
-  if (showMileageColumns.value) {
-    widths.lastRunningKilometers = flexColumnWidth(t('mainPlan.lastMaintenanceMileage'), 'lastRunningKilometers');
-    widths.nextRunningKilometers = flexColumnWidth(t('mainPlan.operatingMileageCycle'), 'nextRunningKilometers');
-    widths.kilometerCycle = flexColumnWidth(t('mainPlan.nextMaintKil'), 'kilometerCycle');
-    widths.mileageGroup = `${[
-      parseFloat(widths.lastRunningKilometers),
-      parseFloat(widths.nextRunningKilometers),
-      parseFloat(widths.kilometerCycle)
-    ].reduce((sum, w) => sum + w, 0) + 2}px`;
-  }
-
-  if (showNaturalDateColumns.value) {
-    widths.lastNaturalDate = flexColumnWidth(t('mainPlan.lastMaintenanceOperationTime'), 'lastNaturalDate'); // 固定日期选择器的宽度
-    widths.nextNaturalDate = flexColumnWidth(t('mainPlan.NaturalDailyCycle'), 'nextNaturalDate');
-    widths.naturalDatePeriod = flexColumnWidth(t('mainPlan.nextMaintDate'), 'naturalDatePeriod');
-    widths.dateGroup = `${[
-      parseFloat(widths.lastNaturalDate),
-      parseFloat(widths.nextNaturalDate),
-      parseFloat(widths.naturalDatePeriod)
-    ].reduce((sum, w) => sum + w, 0) + 2}px`;
-  }
-
-  columnWidths.value = widths;
-};
+  console.log('分页变化,当前页:', queryParams.pageNo)
+}
 
 // 分页计算属性
 const paginatedList = computed(() => {
-  const start = (queryParams.pageNo - 1) * queryParams.pageSize;
-  const end = start + queryParams.pageSize;
-  return list.value.slice(start, end);
-});
+  const start = (queryParams.pageNo - 1) * queryParams.pageSize
+  const end = start + queryParams.pageSize
+  return list.value.slice(start, end)
+})
 
 const open = async (id?: number, flag?: string, deviceInfo?: any) => {
   // 重置分页参数
   queryParams.pageNo = 1
   queryParams.pageSize = 10
-  list.value = []  // 清空列表避免显示旧数据
+  list.value = [] // 清空列表避免显示旧数据
   total.value = 0
 
   await nextTick() // 确保DOM更新完成
@@ -314,7 +220,7 @@ const open = async (id?: number, flag?: string, deviceInfo?: any) => {
     externalDeviceInfo.value = deviceInfo
     queryParams.deviceId = deviceInfo.deviceId // 如果需要的话
   }
-  if('workOrder' === flag) {
+  if ('workOrder' === flag) {
     // 加载保养工单 BOM
     queryParams.workOrderId = id
     queryParams.planId = undefined
@@ -334,7 +240,7 @@ const getWorkOrderList = async () => {
   try {
     const data = await IotMainWorkOrderBomApi.getWorkOrderBOMs(queryParams)
     // 格式化日期字段
-    data.forEach(item => {
+    data.forEach((item) => {
       if (item.lastNaturalDate) {
         // 将时间戳转换为 YYYY-MM-DD 格式
         item.lastNaturalDate = dayjs(item.lastNaturalDate).format('YYYY-MM-DD')
@@ -343,9 +249,9 @@ const getWorkOrderList = async () => {
         item.lastNaturalDate = ''
       }
       // 计算下次保养运行时长 H
-      item.timePeriod = calculateTimePeriod(item);
+      item.timePeriod = calculateTimePeriod(item)
       // 计算下次保养公里数 KM
-      item.kilometerCycle = calculateKiloPeriod(item);
+      item.kilometerCycle = calculateKiloPeriod(item)
       // 计算下次保养日期
       item.naturalDatePeriod = calculateNextNaturalDate(item)
     })
@@ -357,12 +263,11 @@ const getWorkOrderList = async () => {
 }
 
 const getPlanList = async () => {
-  widthCache.clear();
   loading.value = true
   try {
     const data = await IotMaintenanceBomApi.getMainPlanBOMs(queryParams)
     // 格式化日期字段
-    data.forEach(item => {
+    data.forEach((item) => {
       if (item.lastNaturalDate) {
         // 将时间戳转换为 YYYY-MM-DD 格式
         item.lastNaturalDate = dayjs(item.lastNaturalDate).format('YYYY-MM-DD')
@@ -371,9 +276,9 @@ const getPlanList = async () => {
         item.lastNaturalDate = ''
       }
       // 计算下次保养运行时长 H
-      item.timePeriod = calculateTimePeriod(item);
+      item.timePeriod = calculateTimePeriod(item)
       // 计算下次保养公里数 KM
-      item.kilometerCycle = calculateKiloPeriod(item);
+      item.kilometerCycle = calculateKiloPeriod(item)
       // 计算下次保养日期
       item.naturalDatePeriod = calculateNextNaturalDate(item)
     })
@@ -381,7 +286,6 @@ const getPlanList = async () => {
     total.value = data.length
   } finally {
     loading.value = false
-    nextTick(setColumnWidths); // 数据加载后计算列宽
   }
 }
 
@@ -389,18 +293,18 @@ const getPlanList = async () => {
 const deviceInfo = computed(() => {
   // 优先使用外部传入的设备信息
   if (externalDeviceInfo.value) {
-    return externalDeviceInfo.value;
+    return externalDeviceInfo.value
   }
   if (list.value.length > 0) {
-    const firstRecord = list.value[0];
+    const firstRecord = list.value[0]
     return {
       deviceCode: firstRecord.deviceCode,
       deviceName: firstRecord.deviceName,
       model: firstRecord.model // 确保列表数据中也有 model
-    };
+    }
   }
-  return null;
-});
+  return null
+})
 
 const handleClose = () => {
   // 重置状态避免多个弹窗出现
@@ -419,32 +323,30 @@ const handleClose = () => {
 // 计算 距离下次保养运行时长 H
 const calculateTimePeriod = (item: IotMaintenanceBomVO) => {
   if (item.runningTimeRule === 0) {
-    const totalRunVal = item.totalRunTime ?? item.tempTotalRunTime;
-    const next = Number(item.nextRunningTime) || 0;
-    const totalRun = totalRunVal != null ? Number(totalRunVal) : 0;
-    const lastRun = Number(item.lastRunningTime) || 0;
-    const result = next - (totalRun - lastRun);
-    return Number(result.toFixed(2));
+    const totalRunVal = item.totalRunTime ?? item.tempTotalRunTime
+    const next = Number(item.nextRunningTime) || 0
+    const totalRun = totalRunVal != null ? Number(totalRunVal) : 0
+    const lastRun = Number(item.lastRunningTime) || 0
+    const result = next - (totalRun - lastRun)
+    return Number(result.toFixed(2))
   }
-  return typeof item.timePeriod === 'number'
-    ? Number(item.timePeriod.toFixed(2))
-    : item.timePeriod;
-};
+  return typeof item.timePeriod === 'number' ? Number(item.timePeriod.toFixed(2)) : item.timePeriod
+}
 
 // 计算 距离下次保养公里数 KM
 const calculateKiloPeriod = (item: IotMaintenanceBomVO) => {
   if (item.mileageRule === 0) {
-    const totalRunVal = item.totalMileage ?? item.tempTotalMileage;
-    const next = Number(item.nextRunningKilometers) || 0;
-    const totalRun = totalRunVal != null ? Number(totalRunVal) : 0;
-    const lastRun = Number(item.lastRunningKilometers) || 0;
-    const result = next - (totalRun - lastRun);
-    return Number(result.toFixed(2));
+    const totalRunVal = item.totalMileage ?? item.tempTotalMileage
+    const next = Number(item.nextRunningKilometers) || 0
+    const totalRun = totalRunVal != null ? Number(totalRunVal) : 0
+    const lastRun = Number(item.lastRunningKilometers) || 0
+    const result = next - (totalRun - lastRun)
+    return Number(result.toFixed(2))
   }
   return typeof item.kilometerCycle === 'number'
     ? Number(item.kilometerCycle.toFixed(2))
-    : item.kilometerCycle;
-};
+    : item.kilometerCycle
+}
 
 // 计算下次保养日期
 const calculateNextNaturalDate = (item: IotMaintenanceBomVO): string => {
@@ -456,18 +358,18 @@ const calculateNextNaturalDate = (item: IotMaintenanceBomVO): string => {
 
 // 计算属性:控制时间相关列的显示
 const showTimeColumns = computed(() => {
-  return paginatedList.value.some(item => item.runningTimeRule === 0);
-});
+  return paginatedList.value.some((item) => item.runningTimeRule === 0)
+})
 
 // 计算属性:控制里程相关列的显示
 const showMileageColumns = computed(() => {
-  return paginatedList.value.some(item => item.mileageRule === 0);
-});
+  return paginatedList.value.some((item) => item.mileageRule === 0)
+})
 
 // 计算属性:自然日期相关列的显示
 const showNaturalDateColumns = computed(() => {
-  return paginatedList.value.some(item => item.naturalDateRule === 0);
-});
+  return paginatedList.value.some((item) => item.naturalDateRule === 0)
+})
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -486,192 +388,171 @@ const resetQuery = () => {
 
 // 判断是否为负数的辅助函数
 const isNegative = (value: any): boolean => {
-  if (value === null || value === undefined || value === '') return false;
-  const num = Number(value);
-  return !isNaN(num) && num < 0;
-};
-
-// 监听分页数据变化,重新设置列宽
-watch(paginatedList, () => {
-  nextTick(() => {
-    setColumnWidths();
-  });
-});
-
-// 监听动态列的变化
-watch([showTimeColumns, showMileageColumns, showNaturalDateColumns], () => {
-  nextTick(setColumnWidths);
-});
-
+  if (value === null || value === undefined || value === '') return false
+  const num = Number(value)
+  return !isNaN(num) && num < 0
+}
 </script>
 
 <style lang="scss" scoped>
+@media (width <= 768px) {
+  .dialog-title__subtitle {
+    display: none;
+  }
+
+  .device-info-grid {
+    grid-template-columns: minmax(0, 1fr);
+    padding: 12px;
+  }
+
+  .maintenance-section .table-container {
+    padding-right: 12px;
+    padding-left: 12px;
+  }
+}
 
-/* 设备信息卡片样式 */
 .device-info-card {
   display: flex;
-  flex-wrap: wrap;
-  gap: 24px; /* 项间距 */
-  margin-bottom: 16px;
   padding: 16px;
+  margin-bottom: 16px;
   background-color: #f8f9fa;
   border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
+  flex-wrap: wrap;
+  gap: 24px; /* 项间距 */
 
   .info-item {
     display: flex;
     align-items: center;
 
     .info-label {
+      margin-right: 8px;
       font-weight: 600;
       color: #606266;
-      margin-right: 8px;
       white-space: nowrap;
     }
 
     .info-value {
+      min-width: 200px;
+      padding: 4px 12px;
       font-weight: 500;
       color: #303133;
-      padding: 4px 12px;
-      background: #ffffff;
-      border-radius: 4px;
+      background: #fff;
       border: 1px solid #ebeef5;
-      min-width: 200px;
+      border-radius: 4px;
     }
   }
 }
 
-/* 分组表头样式 */
-:deep(.el-table .el-table__header .el-table-column--group) {
-  background-color: #f5f7fa;
-  font-weight: bold;
-
-  > .cell {
-    font-weight: bold;
-    color: #303133;
-  }
-
-  background-color: #f0f7ff;
-  border: 1px solid #409eff !important;
-  box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
-
-  > .cell {
-    font-weight: 700;
-    color: #1d63dc;
-  }
+/* 负数值样式 */
+.negative-value {
+  font-weight: 600;
+  color: #f56c6c;
 }
 
-/* 加深所有单元格边框 */
-:deep(.el-table__header th) {
-  border: 1px solid #c0c4cc !important;
+.dialog-title {
+  display: flex;
+  align-items: center;
+  min-width: 0;
 }
 
-/* 分组内部单元格特殊样式 */
-:deep(.el-table-column--group .el-table__cell) {
-  border-top: 1px dashed #a0cfff !important;
+.dialog-title__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 38px;
+  height: 38px;
+  margin-right: 12px;
+  font-size: 18px;
+  color: var(--el-color-primary);
+  background: var(--el-color-primary-light-9);
+  border: 1px solid var(--el-color-primary-light-7);
+  border-radius: 8px;
 }
 
-/* 分组间垂直线 */
-:deep(.el-table-column--group) {
-  position: relative;
-
-  &::after {
-    content: "";
-    position: absolute;
-    right: -1px;
-    top: 0;
-    bottom: 0;
-    width: 2px;
-    background: linear-gradient(to bottom, #409eff, #79bbff);
-  }
+.dialog-title__text {
+  font-size: 17px;
+  font-weight: 600;
+  line-height: 24px;
+  color: var(--el-text-color-primary);
 }
 
-/* 新版CSS解决方案 */
-.fixed-height-dialog {
-  :deep(.el-dialog) {
-    width: 1500px !important; /* 固定宽度 */
-    height: 85vh !important; /* 使用视口高度 */
-    display: flex;
-    flex-direction: column;
-    margin-top: 5vh !important; /* 垂直居中 */
-  }
-
-  /* 添加媒体查询,确保在小屏幕上也能完整显示 */
-  @media (max-width: 1500px) {
-    :deep(.el-dialog) {
-      width: 95% !important;
-      max-width: 1500px; /* 仍然限制最大宽度 */
-    }
-  }
+.dialog-title__subtitle {
+  margin-top: 1px;
+  font-size: 12px;
+  line-height: 18px;
+  color: var(--el-text-color-secondary);
+}
 
-  :deep(.el-dialog__header) {
-    padding: 20px;
-    flex-shrink: 0;
-  }
+.dialog-content {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+}
 
-  :deep(.el-dialog__body) {
-    flex: 1;
-    padding: 10px 20px;
-    display: flex;
-    flex-direction: column;
-    overflow: hidden; /* 防止内容溢出 */
-  }
+.device-info-section,
+.maintenance-section {
+  overflow: hidden;
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
 }
 
-/* 表格容器 */
-.table-container {
-  flex: 1;
-  overflow: auto;
-  position: relative;
-
-  .scrollable-table {
-    width: 100%; /* 自适应宽度 */
-
-    :deep(.el-table__header) {
-      th {
-        white-space: nowrap !important; /* 强制表头不换行 */
-        text-overflow: ellipsis;
-        overflow: hidden;
-        padding: 8px 0; /* 增加内边距 */
-      }
-    }
+.section-heading {
+  display: flex;
+  align-items: center;
+  height: 44px;
+  padding: 0 16px;
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+  background: var(--el-fill-color-extra-light);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+}
 
-    :deep(.el-table__body) {
-      td {
-        white-space: nowrap !important; /* 强制内容不换行 */
-        text-overflow: ellipsis;
-        overflow: hidden;
-        padding: 8px 0; /* 增加内边距 */
-      }
-    }
+.section-heading span {
+  width: 4px;
+  height: 17px;
+  margin-right: 9px;
+  background: var(--el-color-primary);
+  border-radius: 2px;
+}
 
-    :deep(.el-table__body-wrapper) {
-      overflow-x: hidden !important; /* 隐藏X轴滚动条 */
-    }
-  }
+.device-info-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 12px;
+  padding: 14px 16px;
 }
 
-/* 分页样式 */
-.pagination-footer {
-  margin-top: 15px;
-  flex-shrink: 0;
+.device-info-grid .info-item {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+  padding: 10px 12px;
+  background: var(--el-fill-color-extra-light);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 6px;
 }
 
-/* 响应式处理 - 确保在小屏幕上布局合理 */
-@media (max-width: 1500px) {
-  .fixed-height-dialog :deep(.el-dialog) {
-    width: 95% !important;
-    max-width: 98vw;
-  }
+.device-info-grid .info-label {
+  flex: 0 0 auto;
+  margin-right: 10px;
+  color: var(--el-text-color-secondary);
+  white-space: nowrap;
+}
 
-  .table-container {
-    overflow-x: auto;
-  }
+.device-info-grid .info-value {
+  min-width: 0;
+  overflow: hidden;
+  font-weight: 500;
+  color: var(--el-text-color-primary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
-/* 负数值样式 */
-.negative-value {
-  color: #f56c6c;
-  font-weight: 600;
+.maintenance-section .table-container {
+  padding: 14px 16px 0;
 }
+
+/* 设备信息卡片样式 */
 </style>

+ 17 - 25
src/views/pms/iotmainworkorder/IotDeviceMainAlarm.vue

@@ -208,8 +208,7 @@ onMounted(() => {
 
 <template>
   <div
-    class="device-main-alarm-page grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
-  >
+    class="device-main-alarm-page grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]">
     <DeptTreeSelect
       :top-id="rootDeptId"
       :deptId="deptId"
@@ -218,16 +217,14 @@ onMounted(() => {
       :show-title="false"
       request-api="getSimpleDeptList"
       class="device-main-alarm-tree row-span-2"
-      @node-click="handleDeptNodeClick"
-    />
+      @node-click="handleDeptNodeClick" />
 
     <el-form
       ref="queryFormRef"
       :model="queryParams"
       size="default"
-      label-width="68px"
-      class="device-main-alarm-query bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 py-3 min-w-0"
-    >
+      label-width="80px"
+      class="device-main-alarm-query bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 py-3 min-w-0">
       <div class="query-row">
         <el-form-item :label="t('iotDevice.code')" prop="deviceCode">
           <el-input
@@ -235,8 +232,7 @@ onMounted(() => {
             :placeholder="t('iotDevice.codeHolder')"
             clearable
             class="query-control"
-            @keyup.enter="handleQuery"
-          />
+            @keyup.enter="handleQuery" />
         </el-form-item>
         <el-form-item :label="t('iotDevice.name')" prop="deviceName">
           <el-input
@@ -244,8 +240,7 @@ onMounted(() => {
             :placeholder="t('iotDevice.nameHolder')"
             clearable
             class="query-control"
-            @keyup.enter="handleQuery"
-          />
+            @keyup.enter="handleQuery" />
         </el-form-item>
       </div>
 
@@ -261,8 +256,7 @@ onMounted(() => {
           plain
           :loading="exportLoading"
           @click="handleExport"
-          v-hasPermi="['rq:iot-device:export']"
-        >
+          v-hasPermi="['rq:iot-device:export']">
           <Icon icon="ep:download" class="mr-5px" />导出
         </el-button>
       </el-form-item>
@@ -278,15 +272,13 @@ onMounted(() => {
               :width="width"
               :height="height"
               :max-height="height"
-              show-border
-            >
+              show-border>
               <ZmTableColumn
                 type="index"
                 :label="t('monitor.serial')"
                 :width="70"
                 fixed="left"
-                hide-in-column-settings
-              />
+                hide-in-column-settings />
               <ZmTableColumn prop="deviceCode" :label="t('iotDevice.code')" fixed="left" />
               <ZmTableColumn prop="deviceName" :label="t('iotDevice.name')" fixed="left">
                 <template #default="{ row }">
@@ -309,14 +301,12 @@ onMounted(() => {
                 v-if="showRunTime"
                 prop="totalRunTime"
                 label="累计运行时长H"
-                min-width="140"
-              />
+                min-width="140" />
               <ZmTableColumn
                 v-if="showMileage"
                 prop="totalMileage"
                 label="累计运行里程KM"
-                min-width="150"
-              />
+                min-width="150" />
               <ZmTableColumn v-if="showMultiAttrs" label="多属性累计值" min-width="200">
                 <template #default="{ row }">
                   <template v-for="(value, key) in row.multiAttrsTotalRuntime" :key="key">
@@ -352,8 +342,7 @@ onMounted(() => {
                     link
                     type="primary"
                     @click="openBomForm(row)"
-                    v-hasPermi="['rq:iot-device:query']"
-                  >
+                    v-hasPermi="['rq:iot-device:query']">
                     {{ t('monitor.details') }}
                   </el-button>
                 </template>
@@ -374,8 +363,7 @@ onMounted(() => {
           :total="total"
           layout="total, sizes, prev, pager, next, jumper"
           @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
+          @current-change="handleCurrentChange" />
       </div>
     </div>
   </div>
@@ -401,6 +389,10 @@ onMounted(() => {
   min-width: 0;
 }
 
+.query-row :deep(.el-form-item__label) {
+  white-space: nowrap;
+}
+
 .query-actions {
   flex: 0 0 auto;
 }