Browse Source

Merge remote-tracking branch 'origin/master'

lipenghui 2 weeks ago
parent
commit
1df55decdb
2 changed files with 426 additions and 73 deletions
  1. 221 39
      src/views/pms/iotlockstock/index.vue
  2. 205 34
      src/views/pms/iotsapstock/index.vue

+ 221 - 39
src/views/pms/iotlockstock/index.vue

@@ -18,18 +18,7 @@
           />
         </el-select>
       </el-form-item>
-      <!--
-      <el-form-item label="库存地点" prop="storageLocationId">
-        <el-select v-model="queryParams.storageLocationId" clearable placeholder="请选择" class="!w-240px">
-          <el-option
-            v-for="item in storageLocationList"
-            :key="item.id"
-            :label="item.storageLocationName"
-            :value="item.id!"
-          />
-        </el-select>
-      </el-form-item>
-      -->
+
       <el-form-item :label="t('workOrderMaterial.costCenter')" prop="costCenterId" v-if="!shouldHideComponents">
         <el-select v-model="queryParams.costCenterId" clearable filterable :placeholder="t('faultForm.choose')" class="!w-240px">
           <el-option
@@ -110,35 +99,25 @@
   </ContentWrap>
 
   <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column :label="t('workOrderMaterial.factory')" align="center" prop="factory" />
-      <el-table-column :label="t('workOrderMaterial.costCenter')" align="center" prop="costCenter" />
-      <el-table-column :label="t('chooseMaintain.materialCode')" align="center" prop="materialCode" />
-      <el-table-column :label="t('chooseMaintain.materialName')" align="center" prop="materialName" />
-      <el-table-column :label="t('route.quantity')" align="center" prop="quantity" :formatter="erpPriceTableColumnFormatter" />
-      <el-table-column :label="t('workOrderMaterial.unitPrice')" align="center" prop="unitPrice" :formatter="erpPriceTableColumnFormatter" />
-      <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" />
+  <ContentWrap ref="tableContainerRef" class="table-container">
+    <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false" style="width: 100%">
+      <el-table-column :label="t('workOrderMaterial.factory')" align="center" prop="factory" :width="columnWidths.factory"/>
+      <el-table-column :label="t('workOrderMaterial.costCenter')" align="center" prop="costCenter" :width="columnWidths.costCenter"/>
+      <el-table-column :label="t('chooseMaintain.materialCode')" align="center" prop="materialCode" :width="columnWidths.materialCode"/>
+      <el-table-column :label="t('chooseMaintain.materialName')" align="left" prop="materialName" :width="columnWidths.materialName"/>
+      <el-table-column :label="t('route.quantity')" align="center" prop="quantity"
+                       :formatter="erpPriceTableColumnFormatter" :width="columnWidths.quantity"/>
+      <el-table-column :label="t('workOrderMaterial.unitPrice')" align="center" prop="unitPrice"
+                       :formatter="erpPriceTableColumnFormatter" :width="columnWidths.unitPrice"/>
+      <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" :width="columnWidths.unit"/>
       <el-table-column
         :label="t('stock.storageTime')"
         align="center"
         prop="storageTime"
         :formatter="dateFormatter"
-        width="180px"
+        :width="columnWidths.storageTime"
       />
-      <!--
-      <el-table-column label="操作" align="center" min-width="120px">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['pms:iot-lock-stock:update']"
-          >
-            编辑
-          </el-button>
-        </template>
-      </el-table-column> -->
+
     </el-table>
     <!-- 分页 -->
     <Pagination
@@ -160,9 +139,9 @@ import { IotLockStockApi, IotLockStockVO } from '@/api/pms/iotlockstock'
 import IotLockStockForm from './IotLockStockForm.vue'
 import {erpPriceTableColumnFormatter} from "@/utils";
 import { useUserStore } from '@/store/modules/user'
-import {SapOrgApi, SapOrgVO} from "@/api/system/saporg";
+import { SapOrgApi, SapOrgVO } from "@/api/system/saporg";
 import { checkRole } from '@/utils/permission'
-import { computed } from 'vue'
+import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
 
 /** PMS 本地 库存 列表 */
 defineOptions({ name: 'IotLockStock' })
@@ -209,10 +188,14 @@ const factoryList = ref([] as SapOrgVO[])   // 工厂列表
 const storageLocationList = ref([] as SapOrgVO[]) // 库存地点列表
 const costCenterList = ref([] as SapOrgVO[]) // SAP成本中心列表
 
-// 新增统计变量
+// 统计变量
 const totalQuantity = ref(0)   // 总数量
 const totalAmount = ref(0)     // 总金额
 
+// 表格容器和表格的引用
+const tableContainerRef = ref()
+const tableRef = ref()
+
 const shouldHideComponents = computed(() => {
   // 检查用户是否拥有 'A' 或 'B' 角色
   return checkRole(['小队队长', '操作员'])
@@ -223,6 +206,142 @@ const selectedFactoryReqVO = ref({
   factoryCodes: [] // 已经选择的SAP工厂code 列表
 })
 
+// 列宽度配置
+const columnWidths = ref({
+  factory: '120px',
+  costCenter: '120px',
+  materialCode: '120px',
+  materialName: '200px', // 初始宽度,会被计算覆盖
+  quantity: '100px',
+  unitPrice: '100px',
+  unit: '100px',
+  storageTime: '180px'
+})
+
+/** 获取滚动条宽度 */
+const getScrollbarWidth = () => {
+  const outer = document.createElement('div');
+  outer.style.visibility = 'hidden';
+  outer.style.overflow = 'scroll';
+  document.body.appendChild(outer);
+
+  const inner = document.createElement('div');
+  outer.appendChild(inner);
+
+  const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
+  outer.parentNode?.removeChild(outer);
+
+  return scrollbarWidth;
+};
+
+/** 计算文本宽度 */
+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 calculateColumnWidths = () => {
+  const MIN_WIDTH = 80; // 最小列宽
+  const PADDING = 25; // 列内边距
+  const FLEXIBLE_COLUMN = 'materialName'; // 可伸缩列
+  const scrollbarWidth = getScrollbarWidth(); // 动态获取滚动条宽度
+
+  if (!tableContainerRef.value?.$el || list.value.length === 0) return;
+
+  const containerWidth = tableContainerRef.value.$el.clientWidth;
+
+  // 需要自适应的列配置
+  const autoColumns = [
+    { key: 'factory', label: t('workOrderMaterial.factory'), getValue: (row) => row.factory },
+    { key: 'costCenter', label: t('workOrderMaterial.costCenter'), getValue: (row) => row.costCenter },
+    { key: 'materialCode', label: t('chooseMaintain.materialCode'), getValue: (row) => row.materialCode },
+    { key: 'materialName', label: t('chooseMaintain.materialName'), getValue: (row) => row.materialName },
+    {
+      key: 'quantity',
+      label: t('route.quantity'),
+      getValue: (row) => erpPriceTableColumnFormatter(null, null, row.quantity, null)
+    },
+    {
+      key: 'unitPrice',
+      label: t('workOrderMaterial.unitPrice'),
+      getValue: (row) => erpPriceTableColumnFormatter(null, null, row.unitPrice, null)
+    },
+    { key: 'unit', label: t('workOrderMaterial.unit'), getValue: (row) => row.unit },
+    {
+      key: 'storageTime',
+      label: t('stock.storageTime'),
+      getValue: (row) => dateFormatter(null, null, row.storageTime)
+    }
+  ];
+
+  const newWidths: Record<string, string> = {};
+  let totalFixedWidth = 0; // 所有固定列的总宽度
+
+  // 计算除可伸缩列外的所有列宽度
+  autoColumns.forEach(col => {
+    if (col.key === FLEXIBLE_COLUMN) return;
+
+    const headerText = col.label;
+    const headerWidth = getTextWidth(headerText) * 1.3; // 表头宽度(加粗效果增加20%)
+
+    // 计算内容最大宽度
+    let contentMaxWidth = 0;
+    list.value.forEach(row => {
+      const text = col.getValue ? String(col.getValue(row)) : String(row[col.key] || '');
+      const textWidth = getTextWidth(text);
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+    });
+
+    // 取表头宽度、内容最大宽度和最小宽度的最大值
+    const finalWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
+    newWidths[col.key] = `${finalWidth}px`;
+    totalFixedWidth += finalWidth;
+  });
+
+  // 处理可伸缩列(materialName)
+  const flexibleCol = autoColumns.find(col => col.key === FLEXIBLE_COLUMN);
+  if (flexibleCol) {
+    const headerText = flexibleCol.label;
+    const headerWidth = getTextWidth(headerText) * 1.3;
+
+    let contentMaxWidth = 0;
+    list.value.forEach(row => {
+      const text = flexibleCol.getValue ? String(flexibleCol.getValue(row)) : String(row[flexibleCol.key] || '');
+      const textWidth = getTextWidth(text);
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+    });
+
+    const baseWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
+
+    // 剩余空间 = 容器宽度 - 其他列总宽度 - 垂直滚动条宽度(17px)
+    const remainingWidth = containerWidth - totalFixedWidth - scrollbarWidth;
+
+    // 可伸缩列的宽度取剩余空间和基础宽度的最大值
+    const flexibleWidth = Math.max(remainingWidth, baseWidth);
+    newWidths[FLEXIBLE_COLUMN] = `${flexibleWidth}px`;
+  }
+
+  // 更新列宽度
+  columnWidths.value = newWidths;
+
+  // 重新布局表格
+  nextTick(() => {
+    tableRef.value?.doLayout();
+  });
+};
+
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
@@ -240,6 +359,10 @@ const getList = async () => {
       totalQuantity.value = 0
       totalAmount.value = 0
     }
+    // 数据加载完成后计算列宽
+    nextTick(() => {
+      calculateColumnWidths()
+    })
   } finally {
     loading.value = false
   }
@@ -248,7 +371,7 @@ const getList = async () => {
   await loadOrgData()
 }
 
-// 新增:单独封装组织数据加载方法
+// 单独封装组织数据加载方法
 const loadOrgData = async () => {
   const deptId = useUserStore().getUser.deptId
   if (typeof deptId === 'number' && !isNaN(deptId) && deptId > 0) {
@@ -364,7 +487,20 @@ const handleExport = async () => {
 /** 初始化 **/
 onMounted(() => {
   getList()
+  // 添加窗口大小变化监听
+  window.addEventListener('resize', calculateColumnWidths)
+})
+
+onUnmounted(() => {
+  // 移除窗口大小变化监听
+  window.removeEventListener('resize', calculateColumnWidths)
 })
+
+// 监听列表数据变化,重新计算列宽
+watch(list, () => {
+  nextTick(calculateColumnWidths)
+}, { deep: true })
+
 </script>
 
 <style scoped>
@@ -396,4 +532,50 @@ onMounted(() => {
   font-weight: bold;
   color: #409EFF;
 }
+
+/* 表格容器样式 - 确保可以水平滚动 */
+.table-container {
+  overflow-x: auto;
+}
+
+/* 防止表格内容换行 */
+:deep(.el-table) .cell {
+  white-space: nowrap !important;
+  overflow: visible !important;
+  text-overflow: unset !important;
+}
+
+/* 确保表格行不换行 */
+:deep(.el-table__row) {
+  white-space: nowrap;
+}
+
+
+/* 防止表格内容换行 */
+:deep(.el-table .cell) {
+  white-space: nowrap !important;
+}
+
+/* 表头特别处理 */
+:deep(.el-table__header) {
+  .cell {
+    display: inline-block;
+    white-space: nowrap;
+    width: auto !important;
+  }
+}
+
+/* 表格整体布局优化 */
+:deep(.el-table__inner-wrapper) {
+  min-width: 100% !important;
+  width: auto !important;
+}
+
+/* 单元格内容完全显示 */
+:deep(.el-table__body-wrapper) .el-table__cell .cell {
+  display: block;
+  overflow: visible;
+  text-overflow: unset;
+}
+
 </style>

+ 205 - 34
src/views/pms/iotsapstock/index.vue

@@ -75,15 +75,6 @@
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('operationFill.search') }}</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />  {{ t('operationFill.reset') }}</el-button>
-        <!--
-        <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-          v-hasPermi="['pms:iot-sap-stock:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}
-        </el-button> -->
         <el-button
           type="success"
           plain
@@ -114,37 +105,26 @@
   </ContentWrap>
 
   <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column :label="t('workOrderMaterial.factory')" align="center" prop="factory" />
-      <el-table-column :label="t('workOrderMaterial.storageLocation')" align="center" prop="projectDepartment" />
-      <el-table-column :label="t('chooseMaintain.materialCode')" align="center" prop="materialCode" />
-      <el-table-column :label="t('chooseMaintain.materialName')" align="center" prop="materialName" />
-      <el-table-column :label="t('route.quantity')" align="center" prop="quantity" />
-      <el-table-column :label="t('workOrderMaterial.unitPrice')" align="center" prop="unitPrice" />
-      <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" />
-      <el-table-column :label="t('route.safetyStock')" align="center" prop="safetyStock" />
+  <ContentWrap ref="tableContainerRef" class="table-container">
+    <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false" style="width: 100%">
+      <el-table-column :label="t('workOrderMaterial.factory')" align="center" prop="factory" :width="columnWidths.factory"/>
+      <el-table-column :label="t('workOrderMaterial.storageLocation')" align="center"
+                       prop="projectDepartment" :width="columnWidths.projectDepartment"/>
+      <el-table-column :label="t('chooseMaintain.materialCode')" align="center"
+                       prop="materialCode" :width="columnWidths.materialCode"/>
+      <el-table-column :label="t('chooseMaintain.materialName')" align="left"
+                       prop="materialName" :width="columnWidths.materialName"/>
+      <el-table-column :label="t('route.quantity')" align="center" prop="quantity" :width="columnWidths.quantity"/>
+      <el-table-column :label="t('workOrderMaterial.unitPrice')" align="center" prop="unitPrice" :width="columnWidths.unitPrice"/>
+      <el-table-column :label="t('workOrderMaterial.unit')" align="center" prop="unit" :width="columnWidths.unit"/>
+      <el-table-column :label="t('route.safetyStock')" align="center" prop="safetyStock" :width="columnWidths.safetyStock"/>
       <el-table-column
         :label="t('chooseMaintain.createTime')"
         align="center"
         prop="createTime"
         :formatter="dateFormatter"
-        width="180px"
+        :width="columnWidths.createTime"
       />
-      <!--
-      <el-table-column label="操作" align="center" min-width="120px">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['pms:iot-sap-stock:update']"
-            v-if="false"
-          >
-            安全库存
-          </el-button>
-        </template>
-      </el-table-column> -->
     </el-table>
     <!-- 分页 -->
     <Pagination
@@ -212,6 +192,140 @@ const queryParams = reactive({
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
+// 表格容器和表格的引用
+const tableContainerRef = ref()
+const tableRef = ref()
+
+// 列宽度配置
+const columnWidths = ref({
+  factory: '120px',
+  projectDepartment: '120px',
+  materialCode: '120px',
+  materialName: '200px', // 初始宽度,会被计算覆盖
+  quantity: '100px',
+  unitPrice: '100px',
+  unit: '100px',
+  safetyStock: '120px',
+  createTime: '180px'
+})
+
+/** 获取滚动条宽度 */
+const getScrollbarWidth = () => {
+  const outer = document.createElement('div');
+  outer.style.visibility = 'hidden';
+  outer.style.overflow = 'scroll';
+  document.body.appendChild(outer);
+
+  const inner = document.createElement('div');
+  outer.appendChild(inner);
+
+  const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
+  outer.parentNode?.removeChild(outer);
+
+  return scrollbarWidth;
+};
+
+/** 计算文本宽度 */
+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 calculateColumnWidths = () => {
+  const MIN_WIDTH = 80; // 最小列宽
+  const PADDING = 25; // 列内边距
+  const FLEXIBLE_COLUMN = 'materialName'; // 可伸缩列
+  const scrollbarWidth = getScrollbarWidth(); // 动态获取滚动条宽度
+
+  if (!tableContainerRef.value?.$el || list.value.length === 0) return;
+
+  const containerWidth = tableContainerRef.value.$el.clientWidth;
+
+  // 需要自适应的列配置
+  const autoColumns = [
+    { key: 'factory', label: t('workOrderMaterial.factory'), getValue: (row) => row.factory },
+    { key: 'projectDepartment', label: t('workOrderMaterial.storageLocation'), getValue: (row) => row.projectDepartment },
+    { key: 'materialCode', label: t('chooseMaintain.materialCode'), getValue: (row) => row.materialCode },
+    { key: 'materialName', label: t('chooseMaintain.materialName'), getValue: (row) => row.materialName },
+    { key: 'quantity', label: t('route.quantity'), getValue: (row) => String(row.quantity) },
+    { key: 'unitPrice', label: t('workOrderMaterial.unitPrice'), getValue: (row) => String(row.unitPrice) },
+    { key: 'unit', label: t('workOrderMaterial.unit'), getValue: (row) => row.unit },
+    { key: 'safetyStock', label: t('route.safetyStock'), getValue: (row) => String(row.safetyStock) },
+    {
+      key: 'createTime',
+      label: t('chooseMaintain.createTime'),
+      getValue: (row) => dateFormatter(null, null, row.createTime)
+    }
+  ];
+
+  const newWidths: Record<string, string> = {};
+  let totalFixedWidth = 0; // 所有固定列的总宽度
+
+  // 计算除可伸缩列外的所有列宽度
+  autoColumns.forEach(col => {
+    if (col.key === FLEXIBLE_COLUMN) return;
+
+    const headerText = col.label;
+    const headerWidth = getTextWidth(headerText) * 1.3; // 表头宽度(加粗效果增加30%)
+
+    // 计算内容最大宽度
+    let contentMaxWidth = 0;
+    list.value.forEach(row => {
+      const text = col.getValue ? String(col.getValue(row)) : String(row[col.key] || '');
+      const textWidth = getTextWidth(text);
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+    });
+
+    // 取表头宽度、内容最大宽度和最小宽度的最大值
+    const finalWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
+    newWidths[col.key] = `${finalWidth}px`;
+    totalFixedWidth += finalWidth;
+  });
+
+  // 处理可伸缩列(materialName)
+  const flexibleCol = autoColumns.find(col => col.key === FLEXIBLE_COLUMN);
+  if (flexibleCol) {
+    const headerText = flexibleCol.label;
+    const headerWidth = getTextWidth(headerText) * 1.3;
+
+    let contentMaxWidth = 0;
+    list.value.forEach(row => {
+      const text = flexibleCol.getValue ? String(flexibleCol.getValue(row)) : String(row[flexibleCol.key] || '');
+      const textWidth = getTextWidth(text);
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+    });
+
+    const baseWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
+
+    // 剩余空间 = 容器宽度 - 其他列总宽度 - 垂直滚动条宽度
+    const remainingWidth = containerWidth - totalFixedWidth - scrollbarWidth;
+
+    // 可伸缩列的宽度取剩余空间和基础宽度的最大值
+    const flexibleWidth = Math.max(remainingWidth, baseWidth);
+    newWidths[FLEXIBLE_COLUMN] = `${flexibleWidth}px`;
+  }
+
+  // 更新列宽度
+  columnWidths.value = newWidths;
+
+  // 重新布局表格
+  nextTick(() => {
+    tableRef.value?.doLayout();
+  });
+};
+
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
@@ -229,6 +343,10 @@ const getList = async () => {
       totalQuantity.value = 0
       totalAmount.value = 0
     }
+    // 数据加载完成后计算列宽
+    nextTick(() => {
+      calculateColumnWidths()
+    })
   } finally {
     loading.value = false
   }
@@ -324,7 +442,20 @@ const handleExport = async () => {
 /** 初始化 **/
 onMounted(() => {
   getList()
+  // 添加窗口大小变化监听
+  window.addEventListener('resize', calculateColumnWidths)
 })
+
+onUnmounted(() => {
+  // 移除窗口大小变化监听
+  window.removeEventListener('resize', calculateColumnWidths)
+})
+
+// 监听列表数据变化,重新计算列宽
+watch(list, () => {
+  nextTick(calculateColumnWidths)
+}, { deep: true })
+
 </script>
 
 <style scoped>
@@ -356,4 +487,44 @@ onMounted(() => {
   font-weight: bold;
   color: #409EFF;
 }
+
+/* 表格容器样式 - 确保可以水平滚动 */
+.table-container {
+  overflow-x: auto;
+}
+
+/* 防止表格内容换行 */
+:deep(.el-table) .cell {
+  white-space: nowrap !important;
+  overflow: visible !important;
+  text-overflow: unset !important;
+}
+
+/* 确保表格行不换行 */
+:deep(.el-table__row) {
+  white-space: nowrap;
+}
+
+/* 表头特别处理 */
+:deep(.el-table__header) {
+  .cell {
+    display: inline-block;
+    white-space: nowrap;
+    width: auto !important;
+  }
+}
+
+/* 表格整体布局优化 */
+:deep(.el-table__inner-wrapper) {
+  min-width: 100% !important;
+  width: auto !important;
+}
+
+/* 单元格内容完全显示 */
+:deep(.el-table__body-wrapper) .el-table__cell .cell {
+  display: block;
+  overflow: visible;
+  text-overflow: unset;
+}
+
 </style>