Browse Source

pms 保养计划 选择设备 列宽自适应

zhangcl 2 weeks ago
parent
commit
0185708954
1 changed files with 274 additions and 24 deletions
  1. 274 24
      src/views/pms/maintenance/MainPlanDeviceList.vue

+ 274 - 24
src/views/pms/maintenance/MainPlanDeviceList.vue

@@ -45,11 +45,13 @@
           :data="list"
           :stripe="true"
           ref="tableRef"
-          :show-overflow-tooltip="true"
+          :show-overflow-tooltip="false"
           @row-click="handleRowClick"
           :max-height="effectiveTableHeight"
+          style="width: 100%; min-width: 100%"
+          class="fixed-layout-table"
         >
-          <el-table-column width="70" :label="t('workOrderMaterial.select')">
+          <el-table-column width="60" :label="t('workOrderMaterial.select')">
             <template #default="{ row }">
               <el-checkbox
                 :model-value="selectedRows.some(item => item.id === row.id)"
@@ -58,11 +60,17 @@
               />
             </template>
           </el-table-column>
-          <el-table-column :label="t('chooseMaintain.deviceCode')" align="center" prop="deviceCode" />
-          <el-table-column :label="t('deviceList.deviceName')" align="center" prop="deviceName" />
+          <el-table-column :label="t('chooseMaintain.deviceCode')" align="center" prop="deviceCode" :min-width="flexColumnMinWidths.deviceCode"/>
+          <el-table-column :label="t('deviceList.deviceName')" align="center" prop="deviceName" :min-width="flexColumnMinWidths.deviceName">
+            <template #default="{ row }">
+              <div class="flex-cell">
+                {{ row.deviceName }}
+              </div>
+            </template>
+          </el-table-column>
           <el-table-column :label="t('faultForm.deptId')" align="center" prop="deptId" v-if="false"/>
-          <el-table-column :label="t('iotDevice.dept')" align="center" prop="deptName" />
-          <el-table-column :label="t('iotDevice.status')" align="center" prop="deviceStatus">
+          <el-table-column :label="t('iotDevice.dept')" align="center" prop="deptName" :min-width="flexColumnMinWidths.deptName"/>
+          <el-table-column :label="t('iotDevice.status')" align="center" prop="deviceStatus" :min-width="flexColumnMinWidths.deviceStatus">
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
             </template>
@@ -71,9 +79,14 @@
             :label="t('deviceList.createTime')"
             align="center"
             prop="createTime"
-            width="180"
-            :formatter="dateFormatter"
-          />
+            :min-width="flexColumnMinWidths.createTime"
+          >
+            <template #default="{ row }">
+              <div class="date-cell">
+                {{ formatDateTime(row.createTime) }}
+              </div>
+            </template>
+          </el-table-column>
         </el-table>
       </div>
 
@@ -122,13 +135,22 @@ const queryParams = reactive({
   code: undefined
 })
 
-// 新增响应式变量
+// 响应式变量
 const dialogWidth = ref('1100px');
 const tableMinHeight = ref(400); // 初始最小高度
 const effectiveTableHeight = ref<number | null>(null); // 单一高度变量
-const tableDynamicHeight = ref('auto'); // 新增动态高度变量
 const dialogTopRef = ref<HTMLElement | null>(null);
 const showPagination = ref(false); // 控制分页显示
+const tableContainerWidth = ref(0) // 表格容器宽度
+
+// 弹性列最小宽度记录
+const flexColumnMinWidths = reactive({
+  deviceCode: 0,
+  deviceName: 0,
+  deptName: 0,
+  deviceStatus: 0,
+  createTime: 0
+})
 
 // 点击整行选中
 const handleRowClick = (row) => {
@@ -163,6 +185,145 @@ const open = async () => {
 defineExpose({ open })
 const { wsCache } = useCache()
 
+// 文本测量工具函数
+const measureText = (text: string, fontSize: string = '14px',
+                     fontWeight: string = 'normal', extraStyles: Record<string, string> = {}): number => {
+  const span = document.createElement('span')
+  span.style.visibility = 'hidden'
+  span.style.position = 'absolute'
+  span.style.fontSize = fontSize
+  span.style.fontWeight = fontWeight
+  span.style.fontFamily = "'Microsoft YaHei', sans-serif"
+  span.style.whiteSpace = 'nowrap'
+  span.textContent = text
+
+  // 应用额外样式
+  Object.keys(extraStyles).forEach(key => {
+    span.style[key as any] = extraStyles[key];
+  });
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+};
+
+// 自定义日期时间格式化函数
+const formatDateTime = (dateString: string | Date) => {
+  if (!dateString) return '';
+
+  const date = new Date(dateString);
+
+  // 确保日期有效
+  if (isNaN(date.getTime())) {
+    return String(dateString).replace(/[\r\n]/g, '');;
+  }
+
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0');
+  const day = String(date.getDate()).padStart(2, '0');
+  const hours = String(date.getHours()).padStart(2, '0');
+  const minutes = String(date.getMinutes()).padStart(2, '0');
+  const seconds = String(date.getSeconds()).padStart(2, '0');
+
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`.replace(/[\r\n]/g, '');
+};
+
+// 列宽计算函数
+const calculateColumnWidths = async () => {
+  await nextTick()
+
+  if (!tableRef.value || !list.value.length) return
+
+  const table = tableRef.value
+  const columns = table.columns
+  const rows = list.value
+
+  // 获取表格容器宽度
+  const tableContainer = tableRef.value?.$el?.closest('.table-wrapper')
+  if (tableContainer) {
+    tableContainerWidth.value = tableContainer.clientWidth
+  }
+
+  // 计算各列所需最小宽度
+  const columnMinWidths = columns.map((col, colIndex) => {
+    if (colIndex === 0) return 60 // 选择列固定宽度
+
+    // 测量表头宽度
+    // 测量表头宽度 - 使用表头实际样式
+    const headerWidth = col.label
+      ? measureText(col.label, '14px', 'bold', {
+      padding: '0 12px', // 表头实际内边距
+      boxSizing: 'border-box'
+    }) + 0 // 安全边距
+      : 0;
+
+    // 测量单元格内容宽度
+    let maxCellWidth = 0
+    rows.forEach(row => {
+      let cellValue = ''
+      let cellWidth = 0
+      if (col.property === 'createTime') {
+        // 特殊处理:使用格式化后的日期文本
+        cellValue = formatDateTime(row[col.property])
+        // 使用完整时间样本确保宽度足够
+        const sampleText = '2024-12-31 23:59:59';
+        const sampleWidth = measureText(sampleText, '14px', 'normal', {
+          padding: '0 12px',
+          boxSizing: 'border-box'
+        });
+        // 时间列额外增加安全边距
+        cellWidth = Math.max(
+          measureText(cellValue, '14px', 'normal', {
+            padding: '0 12px',
+            boxSizing: 'border-box'
+          }),
+          sampleWidth
+        ) + 0; // 安全边距
+        // if (cellWidth > maxCellWidth) maxCellWidth = cellWidth
+      } else if (col.property === 'deviceStatus') {
+        // 特殊处理:字典标签文本
+        const statusText = row.deviceStatusLabel || row[col.property] || '';
+        // 模拟el-tag样式进行测量
+        cellWidth = measureText(statusText, '12px', 'normal', {
+          padding: '2px 7px',
+          border: '1px solid #dcdfe6',
+          borderRadius: '4px',
+          display: 'inline-block',
+          lineHeight: '1.5',
+          boxSizing: 'border-box'
+        }) + 0; // 20px安全边距
+      } else {
+        cellValue = row[col.property] || ''
+        cellWidth = measureText(cellValue, '14px', 'normal', {
+          padding: '0 12px',
+          boxSizing: 'border-box'
+        }) + 0; // 安全边距
+      }
+      if (cellWidth > maxCellWidth) maxCellWidth = cellWidth
+    })
+
+    // 取标题和内容的最大值,并设置最小宽度
+    let minWidth = 100; // 默认最小宽度
+    if (col.property === 'createTime') {
+      minWidth = 100; // 时间列最小宽度设为180px
+    }
+    // 取标题和内容的最大值
+    return Math.max(headerWidth, maxCellWidth, minWidth)
+  })
+
+  // 记录弹性列的最小宽度
+  flexColumnMinWidths.deviceCode = columnMinWidths[1]
+  flexColumnMinWidths.deviceName = columnMinWidths[2]
+  flexColumnMinWidths.deptName = columnMinWidths[4]
+  flexColumnMinWidths.deviceStatus = columnMinWidths[5]
+  flexColumnMinWidths.createTime = columnMinWidths[6]
+
+  // 触发表格重新布局
+  table.doLayout()
+};
+
 const getList = async () => {
   loading.value = true
   try {
@@ -177,8 +338,12 @@ const getList = async () => {
     // 数据加载完成后重新计算高度
     await nextTick();
     calculateTableHeight();
+    calculateColumnWidths()
   } finally {
     loading.value = false
+    // 数据加载完成后计算列宽
+    await nextTick();
+    calculateColumnWidths();
   }
 }
 
@@ -292,21 +457,25 @@ const resetQuery = () => {
 // 监听列表变化动态调整高度
 watch(list, () => {
   calculateTableHeight();
+  if (dialogVisible.value) {
+    calculateColumnWidths();
+  }
 });
 
 // 监听窗口大小变化
 onMounted(() => {
   window.addEventListener('resize', calculateTableHeight);
+  window.addEventListener('resize', calculateColumnWidths);
 });
 
 onUnmounted(() => {
   window.removeEventListener('resize', calculateTableHeight);
+  window.removeEventListener('resize', calculateColumnWidths);
 });
 
 </script>
 <style lang="scss" scoped>
 
-/* 新增样式 */
 .device-select-dialog {
   display: flex;
   flex-direction: column;
@@ -334,16 +503,22 @@ onUnmounted(() => {
 
 /* 列宽度保证 - 防止挤压 */
 .el-table {
-  ::v-deep(.el-table__cell) {
-    min-width: 80px;
+  /* 确保所有列头文字居中 */
+  ::v-deep(.el-table__header) th > .cell {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    text-align: center;
+  }
 
-    &:first-child {
-      min-width: 60px;
-    }
+  /* 所有内容列居中对齐 */
+  ::v-deep(.el-table__body) td {
+    text-align: center !important;
 
-    &:nth-child(2),
-    &:nth-child(3) {
-      min-width: 120px;
+    .cell {
+      display: flex;
+      justify-content: center;
+      align-items: center;
     }
   }
 
@@ -353,6 +528,80 @@ onUnmounted(() => {
   }
 }
 
+/* 列宽自适应样式 */
+::v-deep(.el-table) {
+  .el-table__header th {
+    padding: 0 !important;
+    .cell {
+      padding: 0 0px !important;
+      box-sizing: border-box !important;
+      font-weight: bold !important;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 100%;
+    }
+  }
+
+  .el-table__body td {
+    padding: 0 !important;
+    .cell {
+      padding: 0 0px !important;
+      box-sizing: border-box !important;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 100%;
+    }
+  }
+
+  /* 特定列额外修正 */
+  .el-table__cell:nth-child(4), /* 部门列 */
+  .el-table__cell:nth-child(5), /* 状态列 */
+  .el-table__cell:nth-child(6)  /* 时间列 */ {
+    .date-cell {
+      white-space: nowrap !important;
+      overflow: visible !important;
+      text-overflow: clip !important;
+      justify-content: center;
+    }
+  }
+}
+
+/* 固定表格布局 */
+.fixed-layout-table {
+  ::v-deep(table) {
+    table-layout: fixed !important; /* 固定列宽 */
+    width: auto !important;
+    min-width: 100%;
+  }
+
+  ::v-deep(.el-table__header),
+  ::v-deep(.el-table__body) {
+    width: auto !important; /* 允许宽度扩展 */
+  }
+
+  ::v-deep(.el-table__cell) {
+    overflow: visible !important;   /* 禁用裁剪 */
+    text-overflow: unset !important; /* 移除省略号 */
+    white-space: nowrap !important;  /* 禁止换行 */
+
+    &:nth-child(4) { /* 部门 */
+      min-width: v-bind('flexColumnMinWidths.deptName + "px"');
+      text-align: center;
+    }
+    &:nth-child(5) { /* 状态 */
+      min-width: v-bind('flexColumnMinWidths.deviceStatus + "px"');
+      text-align: center;
+    }
+    &:nth-child(6) { /* 创建时间 */
+      min-width: v-bind('(flexColumnMinWidths.createTime) + "px"');
+      overflow: visible !important;
+      text-align: center;
+    }
+  }
+}
+
 .dialog-top {
   flex-shrink: 0;
   margin-bottom: 15px;
@@ -370,19 +619,20 @@ onUnmounted(() => {
   max-height: 100%;
 
   .table-wrapper {
-    overflow: visible !important;
+    overflow: auto !important;
     .el-table {
       // 表格自带的滚动机制
       ::v-deep(.el-table__body-wrapper) {
-        overflow-y: auto !important;
+        overflow: auto !important;
       }
     }
   }
 }
 
 .table-wrapper {
-  flex: 1;
-  overflow-y: auto; /* 内部滚动 */
+  width: 100%;
+  overflow-y: hidden;
+  overflow-x: auto !important;
 }
 
 .no-label-radio .el-radio__label {