ソースを参照

feat(workmanship): 添加工艺路线分组功能及搜索重置功能

xieyong 2 日 前
コミット
e94df474b7
1 ファイル変更372 行追加8 行削除
  1. 372 8
      src/views/material/BOMmanage/components/workmanship.vue

+ 372 - 8
src/views/material/BOMmanage/components/workmanship.vue

@@ -39,14 +39,22 @@
           <el-col :span="4">
             <div class="ele-form-actions">
               <el-button
+                native-type="button"
                 type="primary"
                 class="ele-btn-icon"
                 size="small"
-                @click="reload"
+                @click="search"
               >
                 查询
               </el-button>
-              <el-button size="small" plain>重置</el-button>
+              <el-button
+                native-type="button"
+                size="small"
+                plain
+                @click="resetSearch"
+              >
+                重置
+              </el-button>
             </div>
           </el-col>
         </el-row>
@@ -59,7 +67,29 @@
         :datasource="datasource"
         :need-page="false"
         :row-key="getRowKey"
+        :span-method="spanMethod"
+        :row-class-name="rowClassName"
       >
+        <!-- 工艺路线分组标题 -->
+        <template v-slot:routeGroup="{ row }">
+          <div
+            v-if="isGroupHeader(row)"
+            class="workmanship-route-header"
+            :class="{ 'is-collapsed': !isRouteExpanded(row) }"
+            @click.stop="toggleRouteGroup(row)"
+          >
+            <i
+              class="el-icon-arrow-down workmanship-route-header__arrow"
+            ></i>
+            <i class="el-icon-collection-tag"></i>
+            <span class="workmanship-route-header__label">
+              工艺路线:
+            </span>
+            <span class="workmanship-route-header__name">
+              {{ formatRouteHeader(row) }}
+            </span>
+          </div>
+        </template>
         <!-- 表头工具栏 -->
         <!-- <template v-slot:toolbar>
           <el-button
@@ -280,7 +310,12 @@
   import { getMbomPage, workingStepDelete } from '@/api/material/BOM';
   import control from '@/api/technology/control';
   import stepManagement from '@/api/technology/stepManagement';
+  import route from '@/api/technology/route';
   import UserEdit from '@/views/technology/stepManagement/components/user-edit.vue';
+
+  // 未关联工艺路线的分组标识
+  const UNGROUPED_ROUTE_ID = '__ungrouped_route__';
+
   export default {
     name: 'process',
     components: {
@@ -318,6 +353,13 @@
       return {
         // 表格列配置
         columns: [
+          {
+            width: 45,
+            columnKey: 'routeGroup',
+            align: 'center',
+            slot: 'routeGroup',
+            className: 'workmanship-group-cell'
+          },
           {
             width: 45,
             type: 'expand',
@@ -429,7 +471,11 @@
 
         processData: [],
         removeList: [],
-        where: {},
+        where: {
+          code: '',
+          name: '',
+          workCenterName: ''
+        },
         currentTableData: [],
         typeList: [
           {
@@ -461,7 +507,11 @@
         current: null,
         isView: false,
         controlList: [],
-        createStepTargetProcess: null
+        createStepTargetProcess: null,
+        // 工艺路线分组展开状态:{ [routeId]: true/false },默认全部展开
+        expandedRouteMap: {},
+        // 完整分组数据(含分组标题行),用于按展开状态过滤显示
+        rawGroupedList: []
       };
     },
     methods: {
@@ -471,6 +521,240 @@
         if (row && row.code != null) return 'c_' + row.code;
         return '';
       },
+      // 判断是否为工艺路线分组标题行
+      isGroupHeader(row) {
+        return row && row._isGroupHeader === true;
+      },
+      // 合并单元格:分组标题行横跨所有列;普通行的分组占位列不显示
+      spanMethod({ row, columnIndex }) {
+        if (this.isGroupHeader(row)) {
+          if (columnIndex === 0) {
+            return { rowspan: 1, colspan: this.columns.length };
+          }
+          return { rowspan: 0, colspan: 0 };
+        }
+        return { rowspan: 1, colspan: 1 };
+      },
+      // 分组标题行应用特殊样式
+      rowClassName({ row }) {
+        if (this.isGroupHeader(row)) {
+          return 'workmanship-group-header';
+        }
+        return '';
+      },
+      // 工艺路线分组标题展示文本
+      formatRouteHeader(row) {
+        const info = row._routeInfo || {};
+        const name = info.name || '未命名工艺路线';
+        const code = info.code || '';
+        const version = info.version || '';
+        let text = name;
+        if (code) text = `${code} ${text}`;
+        if (version) text = `${text}(${version})`;
+        return text;
+      },
+      // 判断工艺路线分组是否处于展开状态
+      isRouteExpanded(row) {
+        const routeId = row && row._routeId;
+        if (routeId == null) return true;
+        return this.expandedRouteMap[routeId] !== false;
+      },
+      // 切换工艺路线分组的展开/收起
+      toggleRouteGroup(row) {
+        const routeId = row && row._routeId;
+        if (routeId == null) return;
+        this.$set(
+          this.expandedRouteMap,
+          routeId,
+          !this.isRouteExpanded(row)
+        );
+        this.refreshTableView();
+      },
+      // 根据展开状态过滤出当前可见的行
+      buildVisibleRows() {
+        if (!Array.isArray(this.rawGroupedList) || !this.rawGroupedList.length) {
+          return [];
+        }
+        return this.rawGroupedList.filter((item) => {
+          if (this.isGroupHeader(item)) return true;
+          return this.expandedRouteMap[item._routeId] !== false;
+        });
+      },
+      // 判断工序行是否匹配 where 过滤条件(前端自过滤)
+      matchSearchFilter(item) {
+        if (!item || this.isGroupHeader(item)) return false;
+        const where = this.where || {};
+        const code = (where.code || '').trim().toLowerCase();
+        const name = (where.name || '').trim();
+        const workCenterName = (where.workCenterName || '').trim();
+        if (!code && !name && !workCenterName) return true;
+        if (code && !(item.code || '').toLowerCase().includes(code)) {
+          return false;
+        }
+        if (name && !(item.name || '').includes(name)) {
+          return false;
+        }
+        if (
+          workCenterName &&
+          !(item.workCenterName || '').includes(workCenterName)
+        ) {
+          return false;
+        }
+        return true;
+      },
+      // 根据 where 在 rawGroupedList 上做前端搜索过滤
+      buildSearchedRows() {
+        const where = this.where || {};
+        const hasFilter =
+          (where.code || '').trim() ||
+          (where.name || '').trim() ||
+          (where.workCenterName || '').trim();
+        if (!Array.isArray(this.rawGroupedList) || !this.rawGroupedList.length) {
+          return [];
+        }
+        if (!hasFilter) {
+          return this.rawGroupedList;
+        }
+        const result = [];
+        // pendingHeader 表示当前组的标题;一旦该组出现匹配工序,就立即把标题推入结果并清空,
+        // 避免同一标题被多次推入产生重复行。
+        let pendingHeader = null;
+        for (const item of this.rawGroupedList) {
+          if (this.isGroupHeader(item)) {
+            pendingHeader = item;
+          } else if (this.matchSearchFilter(item)) {
+            if (pendingHeader) {
+              result.push(pendingHeader);
+              pendingHeader = null;
+            }
+            result.push(item);
+          }
+        }
+        return result;
+      },
+      // 刷新表格展示:先按搜索过滤,再按展开/收起过滤
+      refreshTableView() {
+        const searched = this.buildSearchedRows();
+        const visible = searched.filter((item) => {
+          if (this.isGroupHeader(item)) return true;
+          return this.expandedRouteMap[item._routeId] !== false;
+        });
+        if (
+          this.$refs.table &&
+          typeof this.$refs.table.setData === 'function'
+        ) {
+          this.$refs.table.setData([...visible]);
+        }
+      },
+      // 查询:前端自过滤,不再调后端
+      search() {
+        this.refreshTableView();
+      },
+      // 重置:清空过滤条件并展示全部
+      resetSearch() {
+        this.where = {
+          code: '',
+          name: '',
+          workCenterName: ''
+        };
+        this.$nextTick(() => {
+          this.refreshTableView();
+        });
+      },
+      // 根据工艺路线对工序数据进行分组
+      async groupTaskParamByRoute(dataList, routes) {
+        if (!Array.isArray(dataList) || dataList.length === 0) {
+          return [];
+        }
+        if (!Array.isArray(routes) || routes.length === 0) {
+          // 无工艺路线时,强制用一个"未关联工艺路线"分组标题行包裹所有工序,
+          // 保证表格始终有分组标题行,便于搜索/展开等联动
+          const result = [
+            {
+              _rowKey: 'group_header_ungrouped_default',
+              _isGroupHeader: true,
+              _routeId: UNGROUPED_ROUTE_ID,
+              _routeInfo: { name: '未关联工艺路线' }
+            }
+          ];
+          dataList.forEach((item) => {
+            item._routeId = UNGROUPED_ROUTE_ID;
+            result.push(item);
+          });
+          return result;
+        }
+        // 通过路由查询工序,建立 sourceTaskId -> routingId 的映射
+        const taskRouteMap = {};
+        await Promise.all(
+          routes.map(async (r) => {
+            try {
+              const res = await route.taskinstanceList({
+                routingId: r.id,
+                isDetail: true,
+                pageNum: 1,
+                size: -1
+              });
+              const list = (res && res.list) || [];
+              list.forEach((it) => {
+                const detail = it.detail || it;
+                const stid =
+                  detail.sourceTaskId != null
+                    ? detail.sourceTaskId
+                    : it.sourceTaskId;
+                if (stid != null) {
+                  taskRouteMap[stid] = r.id;
+                }
+              });
+            } catch (e) {
+              console.log(e, 'get route task list err');
+            }
+          })
+        );
+        // 按工艺路线顺序分组,剩余未匹配到路线的归入"未分组"
+        const groups = routes.map((r) => ({ route: r, processes: [] }));
+        const ungrouped = [];
+        dataList.forEach((item) => {
+          const routeId =
+            taskRouteMap[item.sourceTaskId] != null
+              ? taskRouteMap[item.sourceTaskId]
+              : item.routingId != null
+              ? item.routingId
+              : null;
+          const matchedGroup = routeId
+            ? groups.find((g) => g.route.id === routeId)
+            : null;
+          if (matchedGroup) {
+            item._routeId = matchedGroup.route.id;
+            matchedGroup.processes.push(item);
+          } else {
+            item._routeId = UNGROUPED_ROUTE_ID;
+            ungrouped.push(item);
+          }
+        });
+        const result = [];
+        groups.forEach((group, gIdx) => {
+          if (group.processes.length === 0) {
+            return;
+          }
+          result.push({
+            _rowKey: `group_header_${group.route.id}_${gIdx}`,
+            _isGroupHeader: true,
+            _routeId: group.route.id,
+            _routeInfo: group.route
+          });
+          group.processes.forEach((item) => result.push(item));
+        });
+        if (ungrouped.length > 0) {
+          result.push({
+            _rowKey: `group_header_ungrouped_${groups.length}`,
+            _isGroupHeader: true,
+            _routeId: UNGROUPED_ROUTE_ID,
+            _routeInfo: { name: '未关联工艺路线' }
+          });
+          ungrouped.forEach((item) => result.push(item));
+        }
+        return result;
+      },
       // 单个工步数据保存
       saveWorking(row) {
         if (!row.name) {
@@ -679,7 +963,6 @@
           this.controlList = res.list;
         });
       },
-      search() {},
       /*配置工艺参数 */
       openSetting(row) {
         this.current = row;
@@ -720,9 +1003,33 @@
                 idx;
             }
           });
-          await this.fillStepLists(dataList);
 
-          return dataList;
+          // 按工艺路线对工序进行分组展示
+          const routes =
+            (data[0].processRoute &&
+              Array.isArray(data[0].processRoute.list) &&
+              data[0].processRoute.list) ||
+            [];
+          const groupedList = await this.groupTaskParamByRoute(
+            dataList,
+            routes
+          );
+
+          // 保存完整分组数据(含标题行),便于按展开状态过滤
+          this.rawGroupedList = groupedList;
+          // 为新增的工艺路线初始化为展开状态(已存在的路线保留之前的展开/收起状态)
+          routes.forEach((r) => {
+            if (!(r.id in this.expandedRouteMap)) {
+              this.$set(this.expandedRouteMap, r.id, true);
+            }
+          });
+
+          const processList = groupedList.filter(
+            (item) => !this.isGroupHeader(item)
+          );
+          await this.fillStepLists(processList);
+
+          return this.buildVisibleRows();
 
           // return data[0].taskParam;
         } else {
@@ -836,7 +1143,7 @@
     }
   };
 </script>
-<style lang="scss">
+<style lang="scss" scoped>
   .workmanship {
     height: 100%;
     .el-card {
@@ -864,3 +1171,60 @@
     }
   }
 </style>
+
+<style lang="scss" scoped>
+  // 工艺路线分组标题行样式(需要非 scoped 才能作用到 el-table 内部)
+  .workmanship {
+    ::v-deep .el-table {
+      tr.workmanship-group-header {
+        td {
+          background: #ecf5ff !important;
+          border-bottom: 1px solid #d9ecff !important;
+          padding: 10px 12px !important;
+        }
+        td.workmanship-group-cell {
+          padding: 0 !important;
+          background: #ecf5ff !important;
+          // 强制让跨列单元格撑满整行,避免被首列 45px 限制
+          width: auto !important;
+          min-width: 100% !important;
+          max-width: 100% !important;
+        }
+        .workmanship-route-header {
+          display: flex;
+          align-items: center;
+          padding: 4px 0;
+          color: #303133;
+          font-weight: 600;
+          cursor: pointer;
+          user-select: none;
+          white-space: nowrap;
+          &__arrow {
+            margin-right: 6px;
+            color: #409eff;
+            font-size: 14px;
+            transition: transform 0.2s ease;
+          }
+          &.is-collapsed &__arrow {
+            transform: rotate(-90deg);
+          }
+          i.el-icon-collection-tag {
+            margin-right: 6px;
+            color: #409eff;
+            font-size: 14px;
+          }
+          &__label {
+            margin-right: 4px;
+            color: #606266;
+          }
+          &__name {
+            color: #303133;
+          }
+          &:hover {
+            color: #409eff;
+          }
+        }
+      }
+    }
+  }
+</style>