|
|
@@ -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>
|