浏览代码

pms 保养工单 保养时累计时长

zhangcl 2 天之前
父节点
当前提交
054a7bd42e

+ 7 - 1
src/api/pms/iotmainworkorderbom/index.ts

@@ -51,7 +51,8 @@ export interface IotMainWorkOrderBomVO {
   totalRunTime: number  // 累计运行时间
   tempTotalMileage: number  // 临时 累计运行公里数
   tempTotalRunTime: number  // 临时 累计运行时间
-
+  mainRuntime: number  // 保养时累计运行公里数
+  mainMileage: number  // 保养时累计运行时间
   // 上次保养时间 不同于自然日保养规则下的 上次保养自然日期
   lastMaintenanceDate: Date
   // 下次保养公里数
@@ -82,6 +83,11 @@ export const IotMainWorkOrderBomApi = {
     return await request.get({ url: `/pms/iot-main-work-order-bom/getWorkOrderBOMs`, params })
   },
 
+  // 获得PMS 保养工单明细BOM列表 保养时累计公里数 时长
+  maintenanceCumulativeValue: async (params: any) => {
+    return await request.get({ url: `/pms/iot-main-work-order-bom/maintenanceCumulativeValue`, params })
+  },
+
   // 查询PMS 保养计划明细BOM详情
   getIotMainWorkOrderBom: async (id: number) => {
     return await request.get({ url: `/pms/iot-main-work-order-bom/get?id=` + id })

+ 4 - 1
src/locales/en.ts

@@ -574,6 +574,8 @@ export default {
     enterNumber: "Please fill number",
     enterContent: "Please fill content",
     exceedMax: "The input value cannot exceed{max}",
+    mainSumTime: 'Running Time(H)',
+    mainSumKil:'Running Kilometers(KM)',
   },
   modelTemplate:{
     name:'TemplateName',
@@ -1096,7 +1098,8 @@ export default {
     notGenerated: "Not Generated",
     generatedNotExecuted: "Not Executed",
     consumeMaterials: "Consumables",
-    mainStatus: "Upkeep Status"
+    mainStatus: "Upkeep Status",
+    mainRuntimeError: 'Main Runtime Error'
   },
   inspect:{
     InspectionItems:'InspectionItems',

+ 4 - 1
src/locales/ru.ts

@@ -516,6 +516,8 @@ export default {
     sumKil:'累计运行公里数(Km)',
     confirm:'确定',
     cancel:'取消',
+    mainSumTime: '保养时累计运行时间(H)',
+    mainSumKil:'保养时累计运行公里数(KM)',
   },
   modelTemplate:{
     name:'模板名称',
@@ -1001,7 +1003,8 @@ export default {
     notGenerated: "未生成工单",
     generatedNotExecuted: "已生成工单未执行",
     consumeMaterials: "消耗物料",
-    mainStatus: "保养状态"
+    mainStatus: "保养状态",
+    mainRuntimeError: '保养时累计时长错误!'
   },
   inspect:{
     InspectionItems:'巡检项',

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

@@ -563,7 +563,9 @@ export default {
   operationFillForm:{
     team:'所属队伍',
     sumTime:'累计运行时间(H)',
+    mainSumTime: '保养时累计运行时间(H)',
     sumKil:'累计运行公里数(KM)',
+    mainSumKil:'保养时累计运行公里数(KM)',
     confirm:'确定',
     cancel:'取消',
     alert:'以下数值取自PLC,如有不符请修改',
@@ -1092,7 +1094,8 @@ export default {
     notGenerated: "未生成工单",
     generatedNotExecuted: "已生成工单未执行",
     consumeMaterials: "消耗物料",
-    mainStatus: "保养状态"
+    mainStatus: "保养状态",
+    mainRuntimeError: '保养时累计运行时间错误!'
   },
   inspect:{
     InspectionItems:'巡检项',

+ 186 - 106
src/views/pms/iotmainworkorder/IotMainWorkOrderOptimize.vue

@@ -224,6 +224,36 @@
             {{ row.totalMileage ?? row.tempTotalMileage }}
           </template>
         </el-table-column>
+        <el-table-column :label="t('operationFillForm.mainSumTime')" align="center" prop="mainRuntime" v-if="hasTimeRuleInCurrentPage"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.mainRuntime">
+          <template #default="{ row }">
+            <el-tooltip
+              :disabled="!row.mainRuntimeError"
+              :content="row.mainRuntimeError"
+              placement="top"
+              effect="light"
+              popper-class="main-runtime-tooltip"
+              :show-after="0"
+            >
+              <div class="main-runtime-input-wrapper">
+                <el-input-number
+                  v-model="row.mainRuntime"
+                  :precision="2"
+                  :min="0"
+                  :controls="false"
+                  style="width: 100%"
+                  :disabled="row.status === 1"
+                  @change="validateMainRuntime(row)"
+                  @blur="validateMainRuntime(row)"
+                  :class="{
+                    'is-required-input': row.mainRuntimeError,
+                    'error-input': row.mainRuntimeError
+                  }"
+                />
+              </div>
+            </el-tooltip>
+          </template>
+        </el-table-column>
         <el-table-column :label="t('mainPlan.lastMaintenanceDate')" prop="lastMaintenanceDate" :width="columnWidths.lastMaintenanceDate">
           <template #default="{ row }">
             <div class="full-content-cell">
@@ -321,33 +351,9 @@
           </el-table-column>
         </el-table-column>
 
-        <!--
-        <el-table-column :label="t('iotMaintain.numberOfMaterials')" align="center" width="90">
-          <template #default="scope">
-            {{ getMaterialCount(scope.row.bomNodeId) }}
-          </template>
-        </el-table-column> -->
-
         <el-table-column :label="t('iotMaintain.operation')" align="center" prop="operation" :width="columnWidths.operation" fixed="right">
           <template #default="scope">
             <div class="horizontal-actions">
-              <!-- 查看当前保养项已经绑定的物料列表
-              <el-tooltip
-                v-if="scope.row.deviceBomMaterials && scope.row.deviceBomMaterials.length > 0"
-                effect="dark"
-                :content="t('mainPlan.deviceBomMaterials')"
-                placement="top"
-                :show-after="300"
-              >
-                <el-button
-                  type="primary"
-                  link
-                  :icon="View"
-                  @click="handleDropdownCommand({ action: 'deviceBomMaterials', row: scope.row })"
-                  class="action-button"
-                />
-              </el-tooltip> -->
-
               <!-- 延迟保养按钮 -->
               <el-tooltip
                 v-if="scope.row.status === 0"
@@ -365,38 +371,6 @@
                 />
               </el-tooltip>
 
-              <!-- 选择物料按钮
-              <el-tooltip
-                v-if="scope.row.status === 0"
-                effect="dark"
-                :content="t('stock.selectMaterial')"
-                placement="top"
-                :show-after="300"
-              >
-                <el-button
-                  type="primary"
-                  link
-                  :icon="Box"
-                  @click="handleDropdownCommand({ action: 'material', row: scope.row })"
-                  class="action-button"
-                />
-              </el-tooltip> -->
-
-              <!-- 物料详情按钮
-              <el-tooltip
-                effect="dark"
-                :content="t('bomList.materialDetail')"
-                placement="top"
-                :show-after="300"
-              >
-                <el-button
-                  type="primary"
-                  link
-                  :icon="Document"
-                  @click="handleDropdownCommand({ action: 'detail', row: scope.row })"
-                  class="action-button"
-                />
-              </el-tooltip> -->
             </div>
           </template>
         </el-table-column>
@@ -539,13 +513,6 @@
         </el-table-column>
         <el-table-column label="消耗数量" align="center" prop="quantity" width="120px">
           <template #default="{ row }">
-            <!-- 新增物料行:显示tooltip、必填红色边框、禁止输入0
-            <el-tooltip
-              effect=""
-              content=""
-              placement=""
-              :disabled="row.quantity > 0"
-            > -->
             <el-input-number
               v-model="row.quantity"
               :precision="4"
@@ -982,11 +949,6 @@ const toggleShowAllMaterials = () => {
   });
 };
 
-// 为表格行添加类名,实现高亮效果
-/* const tableRowClassName = ({ row }) => {
-  return row.isSelected ? 'highlight-row' : '';
-}; */
-
 // 分组合并计算逻辑
 const groupSpans = ref<Record<string, { span: number, index: number }>>({})
 
@@ -1276,6 +1238,13 @@ const handleSizeChange = (newSize: number) => {
 const handleStatusChange = (row: IotMainWorkOrderBomVO) => {
   // 如果是从未完成(0)切换到完成(1)
   if (row.status === 1 && row.initialStatus === 0) {
+    // 先校验 mainRuntime
+    if (!validateMainRuntime(row)) {
+      message.error(`${row.deviceCode}-${formatMaintItemName(row.name)} ${t('mainPlan.mainRuntimeError')}`);
+      row.status = 0; // 重置状态
+      return;
+    }
+
     // 检查消耗物料规则:如果设置为不消耗物料(rule=1),则跳过物料校验
     if (row.rule === 1) {
       console.log(`保养项 ${row.name} 设置为不消耗物料,跳过物料校验`);
@@ -1323,6 +1292,40 @@ const validateAllRunningTimes = (): boolean => {
   return isValid;
 };
 
+// 校验 mainRuntime 的方法
+const validateMainRuntime = (row: IotMainWorkOrderBomVO) => {
+  // 清除之前的错误
+  row.mainRuntimeError = '';
+
+  const mainRuntime = Number(row.mainRuntime) || 0;
+  const totalRunTime = Number(row.totalRunTime ?? row.tempTotalRunTime) || 0;
+  const lastRunningTime = Number(row.lastRunningTime) || 0;
+
+  // 校验规则:lastRunningTime ≤ mainRuntime ≤ totalRunTime
+  if (mainRuntime < lastRunningTime) {
+    row.mainRuntimeError = `保养时累计运行时间不能小于上次保养时长(${lastRunningTime})`;
+    return false;
+  }
+
+  if (mainRuntime > totalRunTime) {
+    row.mainRuntimeError = `保养时累计运行时间不能大于累计运行时间(${totalRunTime})`;
+    return false;
+  }
+
+  return true;
+};
+
+// 全局校验所有 mainRuntime
+const validateAllMainRuntimes = (): boolean => {
+  let isValid = true;
+  list.value.forEach(row => {
+    if (!validateMainRuntime(row)) {
+      isValid = false;
+    }
+  });
+  return isValid;
+};
+
 /** 新增物料 */
 const handleAddMaterial = () => {
   // 检查是否选中保养项
@@ -1783,6 +1786,12 @@ const submitForm = async () => {
     return; // 校验失败则终止提交
   }
 
+  // 保养时 累计运行时长 mainRuntime 全局校验
+  if (!validateAllMainRuntimes()) {
+    message.error(t('mainPlan.mainRuntimeError'));
+    return;
+  }
+
   // 校验所有设置为完成状态的保养项的物料数据
   const invalidBomItems = [];
 
@@ -2095,6 +2104,11 @@ const calculateAllColumnsWidth = () => {
       label: t('operationFillForm.sumKil'),
       getValue: (row) => row.totalMileage ?? row.tempTotalMileage
     },
+    {
+      prop: 'mainRuntime',
+      label: t('operationFillForm.mainSumTime'),
+      getValue: (row) => row.mainRuntime
+    },
     { prop: 'name', label: t('bomList.bomNode') },
     { prop: 'lastMaintenanceDate', label: t('mainPlan.lastMaintenanceDate') },
     { prop: 'mileageRule', label: t('main.mileage') },
@@ -2198,6 +2212,64 @@ const tableHeaderStyle = ({ row, rowIndex }) => {
   }
 }
 
+// 更新列表数据的函数
+const updateListWithCumulativeData = (cumulativeData: any[]) => {
+  cumulativeData.forEach(item => {
+    // 根据 bomNodeId 找到对应的行
+    const targetRow = list.value.find(row => row.bomNodeId === item.bomNodeId)
+    if (targetRow) {
+      // 更新累计运行时间到 mainRuntime 字段
+      targetRow.mainRuntime = item.mainRuntime
+    }
+  })
+}
+
+const fetchCumulativeData = async () => {
+  if (!formData.value.actualStartTime) {
+    console.warn('actualStartTime is empty, skip fetching cumulative data');
+    return;
+  }
+
+  try {
+    // 确保时间戳格式正确
+    const startTime = Number(formData.value.actualStartTime);
+    if (isNaN(startTime)) {
+      console.error('Invalid actualStartTime:', formData.value.actualStartTime);
+      return;
+    }
+
+    const queryParams = {
+      workOrderId: id,
+      actualStartTime: dayjs(startTime).format('YYYY-MM-DD')
+    }
+
+    console.log('Fetching cumulative data with params:', queryParams);
+
+    const response = await IotMainWorkOrderBomApi.maintenanceCumulativeValue(queryParams)
+
+    if (response && Array.isArray(response)) {
+      updateListWithCumulativeData(response);
+      message.success('累计运行数据更新成功');
+    }
+  } catch (error) {
+    console.error('获取累计运行数据失败:', error);
+    message.error('获取累计运行数据失败');
+  }
+}
+
+// 监听保养开始时间变化
+watch(
+  () => formData.value.actualStartTime,
+  async (newVal, oldVal) => {
+    console.log('actualStartTime changed:', { newVal, oldVal });
+
+    if (newVal && newVal !== oldVal) {
+      await fetchCumulativeData();
+    }
+  },
+  { immediate: false, deep: true }
+);
+
 // 原有代码保持不变,新增“选择物料”按钮的点击处理方法
 const handleSelectMaterial = () => {
   // 校验是否已选中保养项(避免无选中项时点击按钮报错)
@@ -2693,49 +2765,15 @@ const handleRowClick = (row) => {
   border-bottom: none; /* 移除默认边框 */
 }
 
-/* 添加选中行高亮样式 - 增强版
-:deep(.el-table .highlight-row) {
-  background-color: #d1eaff !important;
-}
-
-:deep(.el-table .highlight-row:hover>td) {
-  background-color: #b8dfff !important;
-}
-
-:deep(.el-table .highlight-row.current-row>td) {
-  background-color: #99ccff !important;
-  border-bottom: 2px solid #409eff !important;
-} */
-
 /* 增强高亮行样式的特异性,确保覆盖Element UI的默认样式 */
 :deep(.el-table__body tr.current-row>td) {
   background-color: #d1eaff !important;
 }
 
-/*
-:deep(.el-table__body tr.highlight-row:hover td) {
-  background-color: #d9ecff !important;
-}
-
-:deep(.el-table__body tr.highlight-row.current-row td) {
-  background-color: #c6e2ff !important;
-  border-bottom: 2px solid #409eff !important;
-} */
-
-/* 针对斑马纹表格的特殊处理
-:deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped td) {
-  background-color: #d1eaff !important;
-} */
-
 :deep(.el-table__body tr.current-row:hover>td) {
   background-color: #b8dfff !important;
 }
 
-/*
-:deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped.current-row td) {
-  background-color: #99ccff !important;
-} */
-
 /* 物料列表操作区域样式 */
 .material-list-header {
   display: flex;
@@ -2826,8 +2864,10 @@ const handleRowClick = (row) => {
   border-color: #f56c6c !important; /* Element错误色 */
   box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.4) !important; /* 错误阴影 */
 }
-:deep(.is-required-input .el-input-number__input) {
+:deep(.is-required-input .el-input-number__input),
+:deep(.error-input .el-input-number__input) {
   border-color: #f56c6c !important;
+  background-color: #fef0f0 !important;
   box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.4) !important;
 }
 
@@ -2835,11 +2875,28 @@ const handleRowClick = (row) => {
 :deep(.is-required-input .el-input__inner:hover) {
   border-color: #f56c6c !important;
 }
-:deep(.is-required-input .el-input-number__input:hover) {
+:deep(.is-required-input .el-input-number__input:hover),
+:deep(.error-input .el-input-number__input:hover) {
   border-color: #f56c6c !important;
+  background-color: #fef0f0 !important;
 }
 
-/* Tooltip提示样式优化 */
+:deep(.is-required-input .el-input-number__input:focus),
+:deep(.error-input .el-input-number__input:focus) {
+  border-color: #f56c6c !important;
+  background-color: #fef0f0 !important;
+  box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.2) !important;
+}
+
+/* 调整tooltip样式
+:deep(.el-tooltip__popper) {
+  max-width: 300px;
+  background-color: #fff0f0;
+  border: 1px solid #f56c6c;
+  color: #f56c6c;
+} */
+
+/* Tooltip提示样式优化
 :deep(.el-tooltip__popper.is-warning) {
   background-color: #fff3cd !important;
   border-color: #ffeeba !important;
@@ -2848,7 +2905,7 @@ const handleRowClick = (row) => {
 :deep(.el-tooltip__popper.is-warning .el-tooltip__arrow) {
   border-top-color: #ffeeba !important;
   border-bottom-color: #ffeeba !important;
-}
+} */
 
 /* 状态列容器样式 - 水平排列 */
 .status-container {
@@ -2896,4 +2953,27 @@ const handleRowClick = (row) => {
   border-color: #e6a23c;
 }
 
+/* 自定义淡红色背景的 tooltip */
+:deep(.main-runtime-tooltip) {
+  background: #fef0f0 !important;
+  border: 1px solid #fbc4c4 !important;
+  color: #f56c6c !important;
+  max-width: 300px;
+  font-size: 12px;
+  padding: 8px 12px;
+}
+
+/* 隐藏 Tooltip 菱形箭头(核心需求) */
+:deep(.main-runtime-tooltip .el-tooltip__arrow),
+:deep(.main-runtime-tooltip .el-tooltip__arrow::before) {
+  display: none !important; /* 完全隐藏箭头及伪元素 */
+  width: 0 !important;
+  height: 0 !important;
+}
+
+/* 新增包装层样式,确保tooltip正确触发 */
+.main-runtime-input-wrapper {
+  width: 100%;
+  position: relative;
+}
 </style>