浏览代码

pms 保养工单 明细 样式 取消 设备编码 设备名称 填报保养工单 自动绑定本地库存中已有的 BOM节点关联的物料

zhangcl 4 周之前
父节点
当前提交
c4336fd1a4
共有 2 个文件被更改,包括 206 次插入26 次删除
  1. 191 18
      src/views/pms/iotmainworkorder/IotMainWorkOrder.vue
  2. 15 8
      src/views/pms/iotmainworkorder/WorkOrderMaterial.vue

+ 191 - 18
src/views/pms/iotmainworkorder/IotMainWorkOrder.vue

@@ -90,14 +90,14 @@
           fixed="left"
         />
         <el-table-column :label="t('bomList.bomNode')" align="center" prop="bomNodeId" v-if="false"/>
-        <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" :width="columnWidths.deviceCode" fixed="left">
+        <el-table-column :label="t('iotDevice.code')" align="center" prop="deviceCode" v-if="false">
           <template #default="{ row }">
             <div class="full-content-cell"> <!-- 自定义样式 -->
               {{ row.deviceCode }}
             </div>
           </template>
         </el-table-column>
-        <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName" :width="columnWidths.deviceName" fixed="left">
+        <el-table-column :label="t('iotDevice.name')" align="center" prop="deviceName" v-if="false">
           <template #default="{ row }">
             <div class="full-content-cell"> <!-- 自定义样式 -->
               {{ row.deviceName }}
@@ -107,9 +107,16 @@
         <el-table-column :label="t('mainPlan.MaintItems')" align="center" prop="name"
                          :show-overflow-tooltip="false" :width="columnWidths.name" fixed="left">
           <template #default="{ row }">
-            <div class="full-content-cell"> <!-- 自定义样式 -->
-              {{ row.name }}
-            </div>
+            <el-tooltip
+              effect="dark"
+              :content="`设备编码:${row.deviceCode}\n设备名称:${row.deviceName}`"
+              placement="top"
+              popper-class="custom-tooltip"
+            >
+              <div class="full-content-cell"> <!-- 自定义样式 -->
+                {{ row.name }}
+              </div>
+            </el-tooltip>
           </template>
         </el-table-column>
         <el-table-column :label="t('main.mileage')" key="mileageRule" align="center"
@@ -354,6 +361,51 @@
       </div>
     </ContentWrap>
 
+    <ContentWrap v-show="showMaterialBill">
+      <el-table
+        :data="materialBills"
+        :stripe="true"
+        :row-class-name="tableRowClassName"
+        border
+      >
+        <el-table-column type="index" :label="t('maintain.serial')" align="center" width="60" />
+        <el-table-column label="工厂" align="center" prop="factory">
+          <template #default="{ row }">
+            {{ row.factory || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="成本中心" align="center" prop="costCenter">
+          <template #default="{ row }">
+            {{ row.costCenter || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="物料编码" align="center" prop="code" >
+          <template #default="{ row }">
+            <span :class="{ 'red-text': !row.factory || !row.costCenter }">
+              {{ row.code }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="物料名称" align="center" prop="name" >
+          <template #default="{ row }">
+            <span :class="{ 'red-text': !row.factory || !row.costCenter }">
+              {{ row.name }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="库存数量" align="center" prop="stockQuantity">
+          <template #default="{ row }">
+            {{ row.stockQuantity ?? '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="BOM物料数量" align="center" prop="quantity">
+          <template #default="{ row }">
+            {{ row.quantity ?? '-' }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </ContentWrap>
+
     <!-- 选择的物料列表 -->
     <ContentWrap>
       <el-table v-loading="false" :data="materialList" :stripe="true" :show-overflow-tooltip="true" v-if="false">
@@ -372,16 +424,26 @@
         <el-table-column label="总库存数量" align="center" prop="totalInventoryQuantity" />
       </el-table>
     </ContentWrap>
-
   </ContentWrap>
+
   <ContentWrap>
     <el-form>
+      <el-form-item style="float: left">
+        <el-button
+          type="primary"
+          @click="toggleMaterialBill"
+          style="margin-bottom: 10px;"
+        >
+          {{ showMaterialBill ? '隐藏物料清单' : '显示物料清单' }}
+        </el-button>
+      </el-form-item>
       <el-form-item style="float: right">
         <el-button @click="submitForm" type="primary" :disabled="formLoading">{{t('common.save')}}</el-button>
         <el-button @click="close">{{t('common.cancel')}}</el-button>
       </el-form-item>
     </el-form>
   </ContentWrap>
+
   <!-- 新增配置对话框 -->
   <el-dialog
     v-model="configDialog.visible"
@@ -611,6 +673,7 @@
       <el-button type="primary" @click="saveConfig">{{ t('common.ok')}}</el-button>
     </template>
   </el-dialog>
+
   <!-- 表单弹窗:添加/修改 -->
   <WorkOrderMaterial ref="materialFormRef" @choose="selectChoose" />
   <!-- 设备BOM节点绑定的物料列表 -->
@@ -685,6 +748,9 @@ const lastNaturalDateWatchers = ref(new Map())
 
 const columnWidths = ref<Record<string, string>>({});
 
+const showMaterialBill = ref(false); // 控制物料清单显示
+const materialBills = ref<any[]>([]); // 物料清单数据
+
 const formData = ref({
   id: undefined,
   deptId: undefined,
@@ -804,6 +870,33 @@ const configDialog = reactive({
   }
 })
 
+// 生成物料清单数据
+const generateMaterialBills = () => {
+  materialBills.value = [];
+
+  list.value.forEach(item => {
+    if (item.deviceBomMaterials && item.deviceBomMaterials.length > 0) {
+      item.deviceBomMaterials.forEach((material: any) => {
+        materialBills.value.push({
+          ...material,
+          // 添加bomNodeId用于跟踪来源
+          bomNodeId: item.bomNodeId
+        });
+      });
+    }
+  });
+};
+
+// 切换物料清单显示状态
+const toggleMaterialBill = () => {
+  showMaterialBill.value = !showMaterialBill.value;
+
+  // 当显示时生成物料清单数据
+  if (showMaterialBill.value && materialBills.value.length === 0) {
+    generateMaterialBills();
+  }
+};
+
 // 打开配置对话框
 const openConfigDialog = (row: IotMainWorkOrderBomVO) => {
   configDialog.current = row
@@ -1334,8 +1427,8 @@ const calculateAllColumnsWidth = () => {
   // 需要自适应的列配置
   const autoColumns = [
     { prop: 'serial', label: t('iotDevice.serial') },
-    { prop: 'deviceCode', label: t('iotMaintain.deviceCode') },
-    { prop: 'deviceName', label: t('iotMaintain.deviceName') },
+    /* { prop: 'deviceCode', label: t('iotMaintain.deviceCode') },
+    { prop: 'deviceName', label: t('iotMaintain.deviceName') }, */
     {
       prop: 'totalRunTime',
       label: t('operationFillForm.sumTime'),
@@ -1407,7 +1500,7 @@ const calculateAllColumnsWidth = () => {
   });
 
   // 固定列特殊处理 - 增加额外空间
-  ['serial', 'deviceCode', 'deviceName', 'name'].forEach(prop => {
+  ['serial', 'name'].forEach(prop => {
     if (newWidths[prop]) {
       newWidths[prop] += FIXED_COLUMN_PADDING;
     }
@@ -1598,9 +1691,68 @@ onMounted(async () => {
     formData.value = workOrder
     // 查询保养工单 明细数据
     const data = await IotMainWorkOrderBomApi.getWorkOrderBOMs(queryParams);
+
+    // 查询当前保养工单已经关联的所有物料
+    const materials = await IotMainWorkOrderBomMaterialApi.getWorkOrderBomMaterials(queryParams);
+    const tobeBoundedMaterials = data.flatMap(item =>
+      item.deviceBomMaterials?.map(mat => ({
+        ...mat,
+        bomNodeId: item.bomNodeId // 确保bomNodeId关联
+      })) || []
+    ).filter(Boolean);
+
+    // 创建唯一键生成函数
+    const getMaterialKey = (mat: any) => {
+      return `${mat.factoryId}_${mat.costCenterId}_${mat.bomNodeId}_${mat.materialCode}`;
+    };
+
+    // 创建合并映射表
+    const materialMap = new Map<string, any>();
+
+    // 先添加接口A的物料(优先级高)
+    materials.forEach(mat => {
+      if (mat.factoryId && mat.costCenterId) { // 基础校验
+        const key = getMaterialKey(mat);
+        materialMap.set(key, {
+          ...mat,
+        });
+      }
+    });
+
+    // 5. 再添加接口B的物料(仅添加不存在的)
+    tobeBoundedMaterials.forEach(mat => {
+      if (!mat.factory || !mat.costCenter) {
+        console.warn(`物料${mat.code}缺少工厂或成本中心信息,已跳过`);
+        return;
+      }
+
+      // 字段映射转换
+      const mappedMat = {
+        ...mat,
+        materialCode: mat.code,
+        materialName: mat.name,
+        totalInventoryQuantity: mat.stockQuantity,
+        materialSource: '本地库存',
+        // 保留原始字段用于后续验证
+        factory: mat.factory,
+        costCenter: mat.costCenter
+      };
+
+      const key = getMaterialKey(mappedMat);
+
+      // 仅当不存在时才添加
+      if (!materialMap.has(key)) {
+        materialMap.set(key, mappedMat);
+      }
+    });
+
+    // 将映射表转为数组
+    materialList.value = Array.from(materialMap.values());
+
     list.value = []
     if (Array.isArray(data)) {
       list.value = data.map(item => {
+
         if (item.mileageRule === 0) {
           item.nextMaintenanceKm = calculateNextMaintenanceKm(item);
           item.remainKm = calculateRemainKm(item);
@@ -1620,15 +1772,7 @@ onMounted(async () => {
           }
         })
     }
-    // 查询当前保养工单已经关联的所有物料
-    const materials = await IotMainWorkOrderBomMaterialApi.getWorkOrderBomMaterials(queryParams);
-    materialList.value = []
-    if (Array.isArray(materials)) {
-      materialList.value = materials
-        .map(item => ({
-          ...item,
-        }))
-    }
+
   } catch (error) {
     console.error('数据加载失败:', error)
     message.error('数据加载失败,请重试')
@@ -1683,6 +1827,11 @@ onUnmounted(async () => {
   margin-top: 5px;
 }
 
+/* 确保表格填满容器 */
+:deep(.el-table) {
+  width: 100% !important;
+}
+
 :deep(.el-table__body) {
   .el-table__cell {
     .cell {
@@ -1719,4 +1868,28 @@ onUnmounted(async () => {
 :deep(.el-table__header .el-table__cell:not(.is-group)) {
   border-top: 1px solid #dcdfe6 !important; /* 添加顶部边框连接分组标题 */
 }
+
+/* Tooltip样式 */
+:deep(.custom-tooltip) {
+  white-space: pre-line;
+  line-height: 1.5;
+  text-align: left;
+  max-width: 300px;
+  background-color: #333;
+  color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
+}
+
+/* 添加淡红色行样式 */
+:deep(.el-table .warning-row) {
+  background-color: #fff6f6;
+}
+
+.red-text {
+  color: red;
+  font-weight: bold;
+}
+
 </style>

+ 15 - 8
src/views/pms/iotmainworkorder/WorkOrderMaterial.vue

@@ -20,18 +20,18 @@
     </div>
 
     <!-- 可折叠的区域 展示当前保养项 已经绑定的物料-->
-    <div style="margin-bottom: 15px;">
+    <div v-if="showFoldArea" style="margin-bottom: 10px;">
       <el-button
         type="text"
         @click="showMaterialArea = !showMaterialArea"
-        style="padding-left: 20px; margin-bottom: 10px;"
+        style="padding-left: 10px; margin-bottom: 10px;"
       >
         {{ showMaterialArea ? '隐藏保养项物料' : '显示保养项物料' }}
       </el-button>
 
       <div
         v-show="showMaterialArea"
-        style="background: #f5f7fa; padding: 12px 20px; border-radius: 4px;"
+        style="background: #f5f7fa; padding: 12px 10px; border-radius: 4px;"
       >
         <template v-if="deviceBomMaterials.length === 0">
           <div style="text-align: center; padding: 10px; color: #999;">
@@ -43,9 +43,9 @@
           <div
             style="
             display: grid;
-            grid-template-columns: 1fr 1fr 1fr;
+            grid-template-columns: 1fr 1fr 1fr 1fr;
             gap: 15px;
-            margin-bottom: 12px;
+            margin-bottom: 10px;
             font-weight: bold;
             border-bottom: 1px solid #e4e7ed;
             padding-bottom: 8px;
@@ -54,16 +54,16 @@
             <div>物料编码</div>
             <div>物料名称</div>
             <div>数量</div>
+            <div>单位</div>
           </div>
-
           <!-- 内容区 -->
           <div
             v-for="(item, index) in deviceBomMaterials"
             :key="index"
             style="
             display: grid;
-            grid-template-columns: 1fr 1fr 1fr;
-            gap: 15px;
+            grid-template-columns: 1fr 1fr 1fr 1fr;
+            gap: 10px;
             padding: 8px 0;
             border-bottom: 1px dashed #ebeef5;
           "
@@ -71,6 +71,7 @@
             <div style="word-break: break-all;">{{ item.code || '-' }}</div>
             <div style="word-break: break-all;">{{ item.name || '-' }}</div>
             <div>{{ item.quantity !== undefined ? item.quantity : '-' }}</div>
+            <div style="word-break: break-all;">{{ item.unit || '-' }}</div>
           </div>
         </template>
       </div>
@@ -261,6 +262,9 @@ const deviceCode = ref('')    // 设备编码
 const bomNodeName = ref('')   // 保养项 or 维修项 名称
 const bomNodeLabel = ref('')   // 保养项 or 维修项 名称
 
+// 控制折叠区域的响应式变量
+const showFoldArea = ref(false)
+
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -324,6 +328,9 @@ const open = async (deptId: number, bomNodeId: number, row: any, type: string) =
   console.log('传递过来的数据:', row.deviceId)
   Object.assign(queryParams, defaultQueryParams)
 
+  // 根据type设置折叠区域显隐
+  showFoldArea.value = type === 'maintenance'
+
   selectedRows.value = []
   dialogVisible.value = true
   queryParams.deptId = deptId