Bladeren bron

pms bom物料更新数量

zhangcl 1 maand geleden
bovenliggende
commit
9852f5abd0

+ 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));
 };
 
 // 计算下次保养日期(通用函数)