workmanship.vue 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366
  1. <template>
  2. <div class="workmanship">
  3. <el-card shadow="never">
  4. <el-form
  5. label-width="77px"
  6. class="ele-form-search"
  7. @keyup.enter.native="search"
  8. @submit.native.prevent
  9. >
  10. <el-row type="flex" :gutter="24">
  11. <el-col :span="6">
  12. <el-form-item label="工序编码:" label-width="100px">
  13. <el-input
  14. clearable
  15. v-model.trim="where.code"
  16. placeholder="请输入"
  17. />
  18. </el-form-item>
  19. </el-col>
  20. <el-col :span="6">
  21. <el-form-item label="工序名称:" label-width="100px">
  22. <el-input
  23. clearable
  24. v-model.trim="where.name"
  25. placeholder="请输入"
  26. />
  27. </el-form-item>
  28. </el-col>
  29. <el-col :span="6">
  30. <el-form-item label="所属工作中心" label-width="100px">
  31. <el-input
  32. clearable
  33. v-model.trim="where.workCenterName"
  34. placeholder="请输入"
  35. />
  36. </el-form-item>
  37. </el-col>
  38. <el-col :span="4">
  39. <div class="ele-form-actions">
  40. <el-button
  41. native-type="button"
  42. type="primary"
  43. class="ele-btn-icon"
  44. size="small"
  45. @click="search"
  46. >
  47. 查询
  48. </el-button>
  49. <el-button
  50. native-type="button"
  51. size="small"
  52. plain
  53. @click="resetSearch"
  54. >
  55. 重置
  56. </el-button>
  57. </div>
  58. </el-col>
  59. </el-row>
  60. </el-form>
  61. <!-- 数据表格 -->
  62. <ele-pro-table
  63. class="table_list"
  64. ref="table"
  65. :columns="columns"
  66. :datasource="datasource"
  67. :need-page="false"
  68. :row-key="getRowKey"
  69. :span-method="spanMethod"
  70. :row-class-name="rowClassName"
  71. >
  72. <!-- 工艺路线分组标题 -->
  73. <template v-slot:routeGroup="{ row }">
  74. <div
  75. v-if="isGroupHeader(row)"
  76. class="workmanship-route-header"
  77. :class="{ 'is-collapsed': !isRouteExpanded(row) }"
  78. @click.stop="toggleRouteGroup(row)"
  79. >
  80. <i
  81. class="el-icon-arrow-down workmanship-route-header__arrow"
  82. ></i>
  83. <i class="el-icon-collection-tag"></i>
  84. <span class="workmanship-route-header__label">
  85. 工艺路线:
  86. </span>
  87. <span class="workmanship-route-header__name">
  88. {{ formatRouteHeader(row) }}
  89. </span>
  90. </div>
  91. </template>
  92. <!-- 表头工具栏 -->
  93. <!-- <template v-slot:toolbar>
  94. <el-button
  95. v-if="attributeData.status != 1"
  96. size="small"
  97. type="primary"
  98. icon="el-icon-plus"
  99. class="ele-btn-icon"
  100. @click="openEdit()"
  101. >
  102. 添加工序
  103. </el-button>
  104. </template> -->
  105. <!-- 展开内容 -->
  106. <template v-slot:expand="{ row, $index }">
  107. <div
  108. style="width: 100%; min-height: 60px"
  109. v-if="row.categoryParamStepList"
  110. >
  111. <ele-pro-table
  112. :toolbar="false"
  113. toolsTheme="none"
  114. ref="table2"
  115. :need-page="false"
  116. :datasource="row.categoryParamStepList"
  117. :columns="columns2"
  118. :key="row.code + '-' + $index"
  119. >
  120. <template v-slot:sort="{ row }">
  121. <el-input
  122. @input="(e) => validateNumber(e, row)"
  123. v-model.number="row.sort"
  124. ></el-input>
  125. </template>
  126. <!-- 默认值 -->
  127. <!-- <template v-slot:defaultValue="{ row }">
  128. <el-input
  129. v-if="
  130. row.textType == 1 || row.textType == 4 || row.textType == 5
  131. "
  132. v-model="row.defaultValue"
  133. placeholder="请输入"
  134. ></el-input>
  135. <el-select
  136. v-if="row.textType == 2"
  137. v-model="row.defaultValue"
  138. placeholder="请选择"
  139. >
  140. <el-option label="TRUE" :value="'TRUE'" />
  141. <el-option label="FALSE" :value="'FALSE'" />
  142. </el-select>
  143. </template> -->
  144. <template v-slot:name="{ row }">
  145. <el-input v-model="row.name" placeholder="请输入"></el-input>
  146. </template>
  147. <template v-slot:defaultValue="{ row }">
  148. <el-input
  149. v-model="row.defaultValue"
  150. placeholder="请输入"
  151. ></el-input>
  152. </template>
  153. <!-- 操作列 -->
  154. <template v-slot:action="{ row }">
  155. <!-- <el-link
  156. type="primary"
  157. :underline="false"
  158. icon="el-icon-edit"
  159. @click="editWorkingStep(row)"
  160. >
  161. 修改
  162. </el-link> -->
  163. <el-link
  164. type="primary"
  165. :underline="false"
  166. icon="el-icon-house"
  167. @click="saveWorking(row)"
  168. >
  169. 保存
  170. </el-link>
  171. <el-link
  172. type="primary"
  173. :underline="false"
  174. icon="el-icon-view"
  175. @click="openStepConfigure(row, true)"
  176. >
  177. 详情
  178. </el-link>
  179. <!-- <el-link
  180. type="primary"
  181. :underline="false"
  182. icon="el-icon-setting"
  183. @click="openStepConfigure(row)"
  184. >
  185. 配置
  186. </el-link> -->
  187. <el-popconfirm
  188. class="ele-action"
  189. title="确定要删除当前参数吗?"
  190. @confirm="remove2(row)"
  191. >
  192. <template v-slot:reference>
  193. <el-link
  194. type="danger"
  195. :underline="false"
  196. icon="el-icon-delete"
  197. >
  198. 删除
  199. </el-link>
  200. </template>
  201. </el-popconfirm>
  202. </template>
  203. </ele-pro-table>
  204. </div>
  205. </template>
  206. <!-- 操作列 -->
  207. <template v-slot:action="{ row }">
  208. <!-- <el-link
  209. type="primary"
  210. :underline="false"
  211. icon="el-icon-edit"
  212. @click="editWorkingProcedure(row)"
  213. v-if="attributeData.status != 1"
  214. >
  215. 修改
  216. </el-link> -->
  217. <el-link
  218. type="primary"
  219. :underline="false"
  220. icon="el-icon-view"
  221. @click="openConfigure(row, true)"
  222. v-if="!row._isRouteOnly"
  223. >
  224. 详情
  225. </el-link>
  226. <el-link
  227. type="primary"
  228. :underline="false"
  229. icon="el-icon-setting"
  230. @click="openStepSetting(row)"
  231. v-if="attributeData.status != 1 && !row._isRouteOnly"
  232. >
  233. 工步
  234. </el-link>
  235. <el-link
  236. type="primary"
  237. :underline="false"
  238. icon="el-icon-plus"
  239. @click="openCreateStep(row)"
  240. v-if="attributeData.status != 1 && !row._isRouteOnly"
  241. >
  242. 创建工步
  243. </el-link>
  244. <el-link
  245. type="primary"
  246. :underline="false"
  247. icon="el-icon-setting"
  248. @click="openConfigure(row)"
  249. v-if="!row._isRouteOnly"
  250. >
  251. 配置
  252. </el-link>
  253. <span v-if="row._isRouteOnly" class="workmanship-route-only-tip">
  254. 未配置
  255. </span>
  256. <!-- <el-popconfirm
  257. class="ele-action"
  258. title="确定要删除当前工序吗?"
  259. @confirm="remove(row)"
  260. >
  261. <template v-slot:reference>
  262. <el-link v-if="attributeData.status != 1" type="danger" :underline="false" icon="el-icon-delete">
  263. 删除
  264. </el-link>
  265. </template>
  266. </el-popconfirm> -->
  267. </template>
  268. </ele-pro-table>
  269. </el-card>
  270. <ProcessModal
  271. ref="processModalRef"
  272. @chooseProcess="chooseProcess"
  273. ></ProcessModal>
  274. <ParamModal ref="paramRefs" @chooseModal="chooseModal"></ParamModal>
  275. <StepModal ref="stepModalRefs" @chooseModal="chooseStepModal"></StepModal>
  276. <workingProcedure
  277. ref="workingProcedureRef"
  278. @chooseProcess="chooseProcess"
  279. ></workingProcedure>
  280. <workingStep
  281. ref="workingStepRef"
  282. @chooseProcess="chooseProcess"
  283. ></workingStep>
  284. <user-edit
  285. :visible.sync="showEdit"
  286. :data="current"
  287. :controlList="controlList"
  288. :isView="isView"
  289. @done="handleStepEditDone"
  290. ref="userEdit"
  291. :typeList="typeList"
  292. />
  293. </div>
  294. </template>
  295. <script>
  296. import {
  297. workingProcedureUpdate,
  298. getStepListById,
  299. workingStepSave,
  300. workingSingleUpdate
  301. } from '@/api/material/BOM';
  302. import ProcessModal from './processModal.vue';
  303. import ParamModal from './ParamModal.vue';
  304. import StepModal from './StepModal1.vue';
  305. import workingProcedure from './workingProcedure.vue';
  306. import workingStep from './workingStep.vue';
  307. import { getMbomPage, workingStepDelete } from '@/api/material/BOM';
  308. import control from '@/api/technology/control';
  309. import stepManagement from '@/api/technology/stepManagement';
  310. import route from '@/api/technology/route';
  311. import UserEdit from '@/views/technology/stepManagement/components/user-edit.vue';
  312. // 未关联工艺路线的分组标识
  313. const UNGROUPED_ROUTE_ID = '__ungrouped_route__';
  314. export default {
  315. name: 'process',
  316. components: {
  317. ProcessModal,
  318. ParamModal,
  319. workingProcedure,
  320. StepModal,
  321. workingStep,
  322. UserEdit
  323. },
  324. props: {
  325. // bom bomCategoryId
  326. resourceBomId: String,
  327. // bom 信息
  328. taskParam: Object,
  329. // bom 信息 和 taskParam是一样的
  330. attributeData: {
  331. type: Object,
  332. default: {}
  333. }
  334. },
  335. watch: {
  336. attributeData: {
  337. handler(val) {
  338. this.$nextTick(() => {
  339. this.reload();
  340. });
  341. },
  342. deep: true,
  343. immediate: true
  344. }
  345. },
  346. data() {
  347. return {
  348. // 表格列配置
  349. columns: [
  350. {
  351. width: 45,
  352. columnKey: 'routeGroup',
  353. align: 'center',
  354. slot: 'routeGroup',
  355. className: 'workmanship-group-cell'
  356. },
  357. {
  358. width: 45,
  359. type: 'expand',
  360. columnKey: 'categoryParamStepList',
  361. align: 'center',
  362. slot: 'expand',
  363. className: ''
  364. },
  365. {
  366. prop: 'sort',
  367. label: '排序号',
  368. showOverflowTooltip: true,
  369. align: 'center',
  370. minWidth: 110
  371. },
  372. {
  373. prop: 'code',
  374. label: '工序编码',
  375. showOverflowTooltip: true,
  376. align: 'center',
  377. minWidth: 110
  378. },
  379. {
  380. prop: 'name',
  381. label: '工序名称',
  382. showOverflowTooltip: true,
  383. align: 'center',
  384. minWidth: 110
  385. },
  386. {
  387. align: 'center',
  388. prop: 'controlName',
  389. label: '工序控制码',
  390. showOverflowTooltip: true,
  391. minWidth: 110
  392. },
  393. {
  394. prop: 'workCenterName',
  395. label: '所属工作中心',
  396. align: 'center',
  397. showOverflowTooltip: true,
  398. minWidth: 110
  399. },
  400. {
  401. columnKey: 'action',
  402. label: '操作',
  403. width: 300,
  404. align: 'center',
  405. resizable: false,
  406. slot: 'action',
  407. showOverflowTooltip: true
  408. }
  409. ],
  410. // 表格列配置
  411. columns2: [
  412. {
  413. prop: 'sort',
  414. slot: 'sort',
  415. label: '排序',
  416. minWidth: 60
  417. },
  418. {
  419. prop: 'code',
  420. label: '工步编码',
  421. showOverflowTooltip: true,
  422. align: 'center',
  423. minWidth: 110
  424. },
  425. {
  426. prop: 'name',
  427. slot: 'name',
  428. label: '工步名称',
  429. showOverflowTooltip: true,
  430. align: 'center',
  431. minWidth: 110
  432. },
  433. {
  434. slot: 'defaultValue',
  435. prop: 'defaultValue',
  436. label: '工序内容',
  437. showOverflowTooltip: true,
  438. align: 'center',
  439. minWidth: 110
  440. },
  441. {
  442. prop: 'status',
  443. slot: 'status',
  444. label: '状态',
  445. align: 'center',
  446. showOverflowTooltip: true,
  447. formatter: (_row) => {
  448. return ['生效', '失效'][_row.status];
  449. }
  450. },
  451. {
  452. columnKey: 'action',
  453. label: '操作',
  454. align: 'center',
  455. resizable: false,
  456. slot: 'action',
  457. minWidth: 200,
  458. showOverflowTooltip: true
  459. }
  460. ],
  461. processData: [],
  462. removeList: [],
  463. where: {
  464. code: '',
  465. name: '',
  466. workCenterName: ''
  467. },
  468. currentTableData: [],
  469. typeList: [
  470. {
  471. value: 1,
  472. label: '普通工步'
  473. },
  474. {
  475. value: 2,
  476. label: '抽样质检工步'
  477. },
  478. {
  479. value: 3,
  480. label: '普通质检工步'
  481. },
  482. {
  483. value: 4,
  484. label: '包装工步'
  485. },
  486. {
  487. value: 5,
  488. label: '入库工步'
  489. },
  490. {
  491. value: 6,
  492. label: '一工一检(常规质检)'
  493. }
  494. ],
  495. showEdit: false,
  496. current: null,
  497. isView: false,
  498. controlList: [],
  499. createStepTargetProcess: null,
  500. // 工艺路线分组展开状态:{ [routeId]: true/false },默认全部展开
  501. expandedRouteMap: {},
  502. // 完整分组数据(含分组标题行),用于按展开状态过滤显示
  503. rawGroupedList: [],
  504. // 表格数据源请求序号(只用于比较,不参与渲染)
  505. loadSeq: 0
  506. };
  507. },
  508. methods: {
  509. getRowKey(row) {
  510. if (row && row._rowKey != null) return row._rowKey;
  511. if (row && row.id != null) return String(row.id);
  512. if (row && row.code != null) return 'c_' + row.code;
  513. return '';
  514. },
  515. // 判断是否为工艺路线分组标题行
  516. isGroupHeader(row) {
  517. return row && row._isGroupHeader === true;
  518. },
  519. // 合并单元格:分组标题行横跨所有列;普通行的分组占位列不显示
  520. spanMethod({ row, columnIndex }) {
  521. if (this.isGroupHeader(row)) {
  522. if (columnIndex === 0) {
  523. return { rowspan: 1, colspan: this.columns.length };
  524. }
  525. return { rowspan: 0, colspan: 0 };
  526. }
  527. return { rowspan: 1, colspan: 1 };
  528. },
  529. // 分组标题行应用特殊样式
  530. rowClassName({ row }) {
  531. if (this.isGroupHeader(row)) {
  532. return 'workmanship-group-header';
  533. }
  534. return '';
  535. },
  536. // 工艺路线分组标题展示文本
  537. formatRouteHeader(row) {
  538. const info = row._routeInfo || {};
  539. const name = info.name || '未命名工艺路线';
  540. const code = info.code || '';
  541. const version = info.version || '';
  542. let text = name;
  543. if (code) text = `${code} ${text}`;
  544. if (version) text = `${text}(${version})`;
  545. return text;
  546. },
  547. // 判断工艺路线分组是否处于展开状态
  548. isRouteExpanded(row) {
  549. const routeId = row && row._routeId;
  550. if (routeId == null) return true;
  551. return this.expandedRouteMap[routeId] !== false;
  552. },
  553. // 切换工艺路线分组的展开/收起
  554. toggleRouteGroup(row) {
  555. const routeId = row && row._routeId;
  556. if (routeId == null) return;
  557. this.$set(
  558. this.expandedRouteMap,
  559. routeId,
  560. !this.isRouteExpanded(row)
  561. );
  562. this.refreshTableView();
  563. },
  564. // 根据展开状态过滤出当前可见的行
  565. buildVisibleRows() {
  566. if (!Array.isArray(this.rawGroupedList) || !this.rawGroupedList.length) {
  567. return [];
  568. }
  569. return this.rawGroupedList.filter((item) => {
  570. if (this.isGroupHeader(item)) return true;
  571. return this.expandedRouteMap[item._routeId] !== false;
  572. });
  573. },
  574. // 判断工序行是否匹配 where 过滤条件(前端自过滤)
  575. matchSearchFilter(item) {
  576. if (!item || this.isGroupHeader(item)) return false;
  577. const where = this.where || {};
  578. const code = (where.code || '').trim().toLowerCase();
  579. const name = (where.name || '').trim();
  580. const workCenterName = (where.workCenterName || '').trim();
  581. if (!code && !name && !workCenterName) return true;
  582. if (code && !(item.code || '').toLowerCase().includes(code)) {
  583. return false;
  584. }
  585. if (name && !(item.name || '').includes(name)) {
  586. return false;
  587. }
  588. if (
  589. workCenterName &&
  590. !(item.workCenterName || '').includes(workCenterName)
  591. ) {
  592. return false;
  593. }
  594. return true;
  595. },
  596. // 根据 where 在 rawGroupedList 上做前端搜索过滤
  597. buildSearchedRows() {
  598. const where = this.where || {};
  599. const hasFilter =
  600. (where.code || '').trim() ||
  601. (where.name || '').trim() ||
  602. (where.workCenterName || '').trim();
  603. if (!Array.isArray(this.rawGroupedList) || !this.rawGroupedList.length) {
  604. return [];
  605. }
  606. if (!hasFilter) {
  607. return this.rawGroupedList;
  608. }
  609. const result = [];
  610. // pendingHeader 表示当前组的标题;一旦该组出现匹配工序,就立即把标题推入结果并清空,
  611. // 避免同一标题被多次推入产生重复行。
  612. let pendingHeader = null;
  613. for (const item of this.rawGroupedList) {
  614. if (this.isGroupHeader(item)) {
  615. pendingHeader = item;
  616. } else if (this.matchSearchFilter(item)) {
  617. if (pendingHeader) {
  618. result.push(pendingHeader);
  619. pendingHeader = null;
  620. }
  621. result.push(item);
  622. }
  623. }
  624. return result;
  625. },
  626. // 刷新表格展示:先按搜索过滤,再按展开/收起过滤
  627. refreshTableView() {
  628. const searched = this.buildSearchedRows();
  629. const visible = searched.filter((item) => {
  630. if (this.isGroupHeader(item)) return true;
  631. return this.expandedRouteMap[item._routeId] !== false;
  632. });
  633. if (
  634. this.$refs.table &&
  635. typeof this.$refs.table.setData === 'function'
  636. ) {
  637. this.$refs.table.setData([...visible]);
  638. }
  639. },
  640. // 查询:前端自过滤,不再调后端
  641. search() {
  642. this.refreshTableView();
  643. },
  644. // 重置:清空过滤条件并展示全部
  645. resetSearch() {
  646. this.where = {
  647. code: '',
  648. name: '',
  649. workCenterName: ''
  650. };
  651. this.$nextTick(() => {
  652. this.refreshTableView();
  653. });
  654. },
  655. // 根据工艺路线对工序数据进行分组:
  656. // 分组依据是"工艺路线接口返回的工序列表",即每条工艺路线展示它自己的完整工序;
  657. // 多条工艺路线共用同一道工序(sourceTaskId 相同)时,各分组下各展示一份,不做过滤。
  658. async groupTaskParamByRoute(dataList, routes) {
  659. if (!Array.isArray(dataList) || dataList.length === 0) {
  660. return [];
  661. }
  662. if (!Array.isArray(routes) || routes.length === 0) {
  663. // 无工艺路线时,强制用一个"未关联工艺路线"分组标题行包裹所有工序,
  664. // 保证表格始终有分组标题行,便于搜索/展开等联动
  665. const result = [
  666. {
  667. _rowKey: 'group_header_ungrouped_default',
  668. _isGroupHeader: true,
  669. _routeId: UNGROUPED_ROUTE_ID,
  670. _routeInfo: { name: '未关联工艺路线' }
  671. }
  672. ];
  673. dataList.forEach((item) => {
  674. item._routeId = UNGROUPED_ROUTE_ID;
  675. result.push(item);
  676. });
  677. return result;
  678. }
  679. // 1) 按工艺路线逐条查询工序:N 次请求的结果各自保留、互不覆盖,
  680. // 任何一条路线的返回都不能把其它路线的结果顶掉。
  681. const routeEntryList = routes.map((r) => ({
  682. route: r,
  683. routeKey: String(r.id),
  684. items: []
  685. }));
  686. await Promise.all(
  687. routeEntryList.map(async (entry) => {
  688. try {
  689. const res = await route.taskinstanceList({
  690. routingId: entry.route.id,
  691. isDetail: true,
  692. pageNum: 1,
  693. size: -1
  694. });
  695. const list = (res && res.list) || [];
  696. entry.items = list
  697. .map((it, idx) => {
  698. const detail = it.detail || it;
  699. const stid =
  700. detail.sourceTaskId != null
  701. ? detail.sourceTaskId
  702. : it.sourceTaskId;
  703. if (stid == null) {
  704. return null;
  705. }
  706. return {
  707. sourceTaskId: stid,
  708. detail,
  709. orderNum: it.orderNum != null ? it.orderNum : idx + 1
  710. };
  711. })
  712. .filter((it) => it != null);
  713. } catch (e) {
  714. console.log(e, 'get route task list err');
  715. }
  716. })
  717. );
  718. // 2) MBOM 工序记录索引:sourceTaskId -> 记录列表(保持 mBomPage 返回的顺序)
  719. const recordBySourceTaskId = new Map();
  720. dataList.forEach((item) => {
  721. if (item.sourceTaskId == null) {
  722. return;
  723. }
  724. const key = String(item.sourceTaskId);
  725. if (!recordBySourceTaskId.has(key)) {
  726. recordBySourceTaskId.set(key, []);
  727. }
  728. recordBySourceTaskId.get(key).push(item);
  729. });
  730. // 已被前面分组优先占用的记录(只影响分配优先级,不阻止后续路线复用同一条记录)
  731. const occupiedRecords = new Set();
  732. // 取某条路线上某道工序对应的 MBOM 记录:
  733. // 1) 记录自身 routingId 就是本路线的最精确,优先;
  734. // 2) 其次取还没被别组占用的记录;
  735. // 3) 都被占用时复用其中一条 —— 共用工序要在每条路线下各显示一份,不能过滤掉。
  736. const pickRecord = (routeId, sourceTaskId) => {
  737. const list = recordBySourceTaskId.get(String(sourceTaskId));
  738. if (!list || !list.length) {
  739. return null;
  740. }
  741. const own = list.find(
  742. (rec) =>
  743. rec.routingId != null && String(rec.routingId) === String(routeId)
  744. );
  745. if (own) {
  746. return own;
  747. }
  748. return list.find((rec) => !occupiedRecords.has(rec)) || list[0];
  749. };
  750. // 3) 按工艺路线顺序组装分组数据
  751. const renderedRecords = new Set();
  752. const result = [];
  753. routeEntryList.forEach((entry, gIdx) => {
  754. const rows = [];
  755. const pushRecordRow = (record, seq) => {
  756. // 同一条 MBOM 工序可能同时出现在多条工艺路线分组下,
  757. // 必须复制出行对象,否则 _routeId/_rowKey 会互相覆盖,分组展开状态会错乱
  758. const row = Object.assign({}, record);
  759. row._routeId = entry.route.id;
  760. row._rowKey = `route_${entry.routeKey}_${
  761. record.id != null ? 'id_' + record.id : 'idx_' + seq
  762. }_${seq}`;
  763. rows.push(row);
  764. renderedRecords.add(record);
  765. };
  766. entry.items.forEach((item, idx) => {
  767. const record = pickRecord(entry.route.id, item.sourceTaskId);
  768. if (record) {
  769. occupiedRecords.add(record);
  770. pushRecordRow(record, idx);
  771. return;
  772. }
  773. // 该路线有这道工序、但 MBOM 里还没有对应记录:用接口明细补一行展示,
  774. // 保证"路线里有就要显示",不因为没配置就被过滤掉(该行不可配置)
  775. rows.push({
  776. _rowKey: `routeonly_${entry.routeKey}_${String(
  777. item.sourceTaskId
  778. )}_${idx}`,
  779. _routeId: entry.route.id,
  780. _isRouteOnly: true,
  781. code: item.detail.code,
  782. name: item.detail.name,
  783. controlName: item.detail.controlName,
  784. workCenterName: item.detail.workCenterName,
  785. routingId: entry.route.id,
  786. sourceTaskId: item.sourceTaskId,
  787. sort: null,
  788. categoryParamStepList: []
  789. });
  790. });
  791. // 接口没返回数据(或该路线确实没工序)时的兜底:
  792. // 把 routingId 属于本路线、且还没出现在任何分组里的记录补进来,避免丢数据
  793. if (rows.length === 0) {
  794. dataList.forEach((record) => {
  795. if (
  796. record.routingId != null &&
  797. String(record.routingId) === entry.routeKey &&
  798. !renderedRecords.has(record)
  799. ) {
  800. occupiedRecords.add(record);
  801. pushRecordRow(record, rows.length);
  802. }
  803. });
  804. }
  805. if (rows.length === 0) {
  806. return;
  807. }
  808. result.push({
  809. _rowKey: `group_header_${entry.routeKey}_${gIdx}`,
  810. _isGroupHeader: true,
  811. _routeId: entry.route.id,
  812. _routeInfo: entry.route
  813. });
  814. rows.forEach((row) => result.push(row));
  815. });
  816. // 4) MBOM 里有、但所有工艺路线接口都没返回的工序 → 归入"未关联工艺路线"
  817. const ungrouped = dataList.filter(
  818. (record) => !renderedRecords.has(record)
  819. );
  820. if (ungrouped.length > 0) {
  821. result.push({
  822. _rowKey: `group_header_ungrouped_${routeEntryList.length}`,
  823. _isGroupHeader: true,
  824. _routeId: UNGROUPED_ROUTE_ID,
  825. _routeInfo: { name: '未关联工艺路线' }
  826. });
  827. ungrouped.forEach((item) => {
  828. item._routeId = UNGROUPED_ROUTE_ID;
  829. result.push(item);
  830. });
  831. }
  832. return result;
  833. },
  834. // 单个工步数据保存
  835. saveWorking(row) {
  836. if (!row.name) {
  837. this.$message.warning('请输入工步名称');
  838. return;
  839. }
  840. if (!row.sort) {
  841. this.$message.warning('请输入工步排序');
  842. return;
  843. }
  844. let data = {
  845. stepName: row.name,
  846. id: row.id,
  847. defaultValue: row.defaultValue,
  848. sort: row.sort
  849. };
  850. workingSingleUpdate(data)
  851. .then((res) => {
  852. if (res.data) {
  853. this.$message.success('保存成功');
  854. } else {
  855. this.$message.error('保存失败');
  856. }
  857. })
  858. .catch((err) => {
  859. console.log(err, 'err');
  860. });
  861. },
  862. validateNumber(e, row) {
  863. if (e == 0) {
  864. row.sort = '';
  865. return;
  866. }
  867. let value = '';
  868. value = e.replace(/[^\d]/g, '');
  869. if (value.startsWith('0') && value.length > 1) {
  870. value = value.replace(/^0+/, '');
  871. }
  872. row.sort = value;
  873. },
  874. // expandChange(row, rows) {
  875. // if (rows.length > 0) {
  876. // // 展开
  877. // console.log('展开');
  878. // getStepListById({
  879. // paramId: row.id,
  880. // bomCategoryId: this.taskParam.id
  881. // }).then((data) => {
  882. // row.produceList = data;
  883. // });
  884. // }
  885. // console.log(row);
  886. // console.log(rows);
  887. // },
  888. openConfigure(row, isView) {
  889. this.$refs.workingProcedureRef.open(
  890. row,
  891. this.taskParam,
  892. this.currentTableData,
  893. !!isView,
  894. this.attributeData,
  895. this.resourceBomId
  896. );
  897. },
  898. openStepConfigure(row, isView) {
  899. console.log('123123123', row);
  900. // this.$refs.workingStepRef.open(row, this.taskParam, !!isView);
  901. this.createStepTargetProcess = null;
  902. this.isView = !!isView;
  903. this.getControlList();
  904. this.current = row;
  905. this.showEdit = true;
  906. this.$refs.userEdit.$refs.form &&
  907. this.$refs.userEdit.$refs.form.clearValidate();
  908. },
  909. openCreateStep(row) {
  910. this.createStepTargetProcess = row;
  911. this.isView = false;
  912. this.getControlList();
  913. this.current = null;
  914. this.showEdit = true;
  915. this.$refs.userEdit.$refs.form &&
  916. this.$refs.userEdit.$refs.form.clearValidate();
  917. },
  918. async handleStepEditDone(stepData, formData) {
  919. if (this.createStepTargetProcess && !this.current) {
  920. const currentProcess = this.createStepTargetProcess;
  921. this.createStepTargetProcess = null;
  922. const currentStep = await this.getCreatedStep(stepData, formData);
  923. if (currentStep && currentStep.id) {
  924. this.chooseStepModal([currentStep], currentProcess);
  925. return;
  926. }
  927. this.openStepSetting(currentProcess);
  928. return;
  929. }
  930. this.createStepTargetProcess = null;
  931. this.reload();
  932. },
  933. async getCreatedStep(stepData, formData) {
  934. let stepId = null;
  935. if (stepData && typeof stepData === 'object' && stepData.id) {
  936. stepId = stepData.id;
  937. } else if (
  938. stepData &&
  939. (typeof stepData === 'number' || typeof stepData === 'string')
  940. ) {
  941. stepId = stepData;
  942. }
  943. if (!stepId && formData && formData.name) {
  944. const res = await stepManagement.list({
  945. pageNum: 1,
  946. size: 50,
  947. name: formData.name
  948. });
  949. const stepList = Array.isArray(res)
  950. ? res
  951. : Array.isArray(res?.list)
  952. ? res.list
  953. : [];
  954. const matched = stepList
  955. .filter((item) => item.name === formData.name)
  956. .sort((a, b) => Number(b.id || 0) - Number(a.id || 0));
  957. if (matched.length) {
  958. stepId = matched[0].id;
  959. }
  960. }
  961. if (!stepId) {
  962. return null;
  963. }
  964. try {
  965. const detail = await stepManagement.getById(stepId);
  966. if (detail && detail.id) {
  967. return detail;
  968. }
  969. } catch (e) {
  970. console.log(e, 'getById err');
  971. }
  972. return null;
  973. },
  974. async loadStepList(process) {
  975. if (!process || !process.id || !this.taskParam || !this.taskParam.id) {
  976. return [];
  977. }
  978. const data = await getStepListById({
  979. paramId: process.id,
  980. bomCategoryId: this.taskParam.id
  981. });
  982. return Array.isArray(data) ? data : [];
  983. },
  984. async fillStepLists(dataList) {
  985. if (!Array.isArray(dataList) || !dataList.length) {
  986. return dataList;
  987. }
  988. await Promise.all(
  989. dataList.map(async (item) => {
  990. try {
  991. item.categoryParamStepList = await this.loadStepList(item);
  992. } catch (e) {
  993. console.log(e, 'get step list err');
  994. item.categoryParamStepList = Array.isArray(
  995. item.categoryParamStepList
  996. )
  997. ? item.categoryParamStepList
  998. : [];
  999. }
  1000. })
  1001. );
  1002. return dataList;
  1003. },
  1004. async refreshStepList(current) {
  1005. const data = await this.loadStepList(current);
  1006. const tableList = this.$refs.table.getData() || [];
  1007. const clonedArray = JSON.parse(JSON.stringify(tableList));
  1008. clonedArray.forEach((item) => {
  1009. if (item.id == current.id) {
  1010. item.categoryParamStepList = data;
  1011. }
  1012. });
  1013. this.$refs.table.setData([...clonedArray]);
  1014. this.expandCurrentProcess(current.id);
  1015. },
  1016. expandCurrentProcess(processId) {
  1017. this.$nextTick(() => {
  1018. const tableRef = this.$refs.table;
  1019. const tableList = tableRef.getData ? tableRef.getData() : [];
  1020. const currentRow = tableList.find((item) => item.id == processId);
  1021. if (!currentRow) {
  1022. this.$forceUpdate();
  1023. return;
  1024. }
  1025. tableRef.toggleRowExpansionAll(false);
  1026. if (tableRef.toggleRowExpansion) {
  1027. tableRef.toggleRowExpansion(currentRow, true);
  1028. }
  1029. this.$forceUpdate();
  1030. });
  1031. },
  1032. getControlList() {
  1033. const params = {
  1034. pageNum: 1,
  1035. size: -1
  1036. };
  1037. control.list().then((res) => {
  1038. this.controlList = res.list;
  1039. });
  1040. },
  1041. /*配置工艺参数 */
  1042. openSetting(row) {
  1043. this.current = row;
  1044. this.$refs.paramRefs.open(row.produceList, this.current);
  1045. },
  1046. openStepSetting(row) {
  1047. getStepListById({
  1048. paramId: row.id,
  1049. bomCategoryId: this.taskParam.id
  1050. }).then((data) => {
  1051. this.$refs.stepModalRefs.open(data, row, '新增');
  1052. });
  1053. },
  1054. /* 表格数据源 */
  1055. async datasource({ page, limit, where }) {
  1056. // 请求序号:datasource 可能被并发触发多次(attributeData 深度监听 + 切换 tab 都会 reload),
  1057. // 只允许最新一次查询写回表格数据,避免先发后到的旧数据覆盖新数据
  1058. const loadSeq = (this.loadSeq || 0) + 1;
  1059. this.loadSeq = loadSeq;
  1060. let data = await getMbomPage({
  1061. ...where,
  1062. bomCategoryId: this.resourceBomId,
  1063. categoryCode: this.attributeData.categoryCode,
  1064. pageNum: page,
  1065. size: limit
  1066. });
  1067. if (loadSeq !== this.loadSeq) {
  1068. return this.buildVisibleRows();
  1069. }
  1070. if (data?.length > 0) {
  1071. this.currentTableData = data[0];
  1072. let dataList = [];
  1073. if (data[0].taskParam.length != 0) {
  1074. dataList = data[0].taskParam.sort(
  1075. (it, ie) => Number(it.sort) - Number(ie.sort)
  1076. );
  1077. }
  1078. dataList.forEach((item, idx) => {
  1079. if (item._rowKey == null) {
  1080. item._rowKey =
  1081. (item.id != null ? 'id_' + item.id : 'idx_' + idx) +
  1082. '_' +
  1083. (item.code != null ? item.code : 'nocode') +
  1084. '_' +
  1085. idx;
  1086. }
  1087. });
  1088. // 按工艺路线对工序进行分组展示
  1089. const routes =
  1090. (data[0].processRoute &&
  1091. Array.isArray(data[0].processRoute.list) &&
  1092. data[0].processRoute.list) ||
  1093. [];
  1094. const groupedList = await this.groupTaskParamByRoute(
  1095. dataList,
  1096. routes
  1097. );
  1098. // 分组过程包含异步请求,这里再校验一次请求序号,确保只有最新一次查询写回
  1099. if (loadSeq !== this.loadSeq) {
  1100. return this.buildVisibleRows();
  1101. }
  1102. // 保存完整分组数据(含标题行),便于按展开状态过滤
  1103. this.rawGroupedList = groupedList;
  1104. // 为新增的工艺路线初始化为展开状态(已存在的路线保留之前的展开/收起状态)
  1105. routes.forEach((r) => {
  1106. if (!(r.id in this.expandedRouteMap)) {
  1107. this.$set(this.expandedRouteMap, r.id, true);
  1108. }
  1109. });
  1110. const processList = groupedList.filter(
  1111. (item) => !this.isGroupHeader(item)
  1112. );
  1113. // 同一道 MBOM 工序可能同时出现在多条工艺路线分组下,
  1114. // 工步只请求一次,再把结果共享给各分组里的同名行
  1115. const stepRowMap = new Map();
  1116. const stepRowList = [];
  1117. processList.forEach((row) => {
  1118. if (row._isRouteOnly) {
  1119. return;
  1120. }
  1121. const key = row.id != null ? String(row.id) : row._rowKey;
  1122. if (stepRowMap.has(key)) {
  1123. return;
  1124. }
  1125. stepRowMap.set(key, row);
  1126. stepRowList.push(row);
  1127. });
  1128. await this.fillStepLists(stepRowList);
  1129. processList.forEach((row) => {
  1130. if (row._isRouteOnly) {
  1131. return;
  1132. }
  1133. const key = row.id != null ? String(row.id) : row._rowKey;
  1134. const source = stepRowMap.get(key);
  1135. if (source && source !== row) {
  1136. row.categoryParamStepList = source.categoryParamStepList;
  1137. }
  1138. });
  1139. return this.buildVisibleRows();
  1140. // return data[0].taskParam;
  1141. } else {
  1142. return [];
  1143. }
  1144. },
  1145. editWorkingProcedure(row) {
  1146. this.$refs.processModalRef.open(
  1147. this.taskParam,
  1148. this.currentTableData,
  1149. '修改',
  1150. row
  1151. );
  1152. },
  1153. openEdit() {
  1154. this.$refs.processModalRef.open(
  1155. this.taskParam,
  1156. this.currentTableData,
  1157. '新增'
  1158. );
  1159. },
  1160. editWorkingStep(row) {
  1161. this.$refs.stepModalRefs.open([], row, '修改');
  1162. },
  1163. reload(where) {
  1164. this.$refs.table.reload({ where });
  1165. },
  1166. chooseProcess(data) {
  1167. // let _arr = [...data, ...this.$refs.table.getData()];
  1168. // this.$refs.table.setData([...data, ...this.$refs.table.getData()]);
  1169. // this.$emit('chooseProcess', _arr);
  1170. this.reload();
  1171. },
  1172. chooseModal(data, current) {
  1173. data.map((m) => {
  1174. m.parentId = current.id;
  1175. return {
  1176. ...m
  1177. };
  1178. });
  1179. let tableList = [];
  1180. let clonedArray = [];
  1181. tableList = this.$refs.table.getData();
  1182. clonedArray = JSON.parse(JSON.stringify(tableList));
  1183. clonedArray.forEach((e) => {
  1184. if (e.id == current.id) {
  1185. if (e.hasOwnProperty('produceList')) {
  1186. e.produceList = [...e.produceList, ...data];
  1187. } else {
  1188. e.produceList = [...data];
  1189. }
  1190. }
  1191. });
  1192. this.$refs.table.setData([...clonedArray]);
  1193. this.$emit('chooseProcess', clonedArray);
  1194. this.expandCurrentProcess(current.id);
  1195. },
  1196. chooseStepModal(data, current) {
  1197. console.log(data, 'data');
  1198. console.log(current, 'current');
  1199. if (data?.length > 0) {
  1200. let params = data.map((item) => {
  1201. const itemData = {
  1202. ...item,
  1203. paramId: current.id,
  1204. stepId: item.id,
  1205. categoryId: this.taskParam.categoryId,
  1206. bomCategoryId: this.taskParam.id,
  1207. categoryCode: this.taskParam.categoryCode
  1208. };
  1209. // itemData.type = item.type.code;
  1210. return itemData;
  1211. });
  1212. workingStepSave(params).then((data) => {
  1213. this.$message.success('保存成功');
  1214. this.refreshStepList(current);
  1215. });
  1216. }
  1217. // this.reload();
  1218. console.log(data);
  1219. console.log(current);
  1220. },
  1221. /* 删除 */
  1222. remove(row) {
  1223. workingProcedureUpdate({
  1224. id: this.currentTableData.id,
  1225. categoryId: this.taskParam.categoryId,
  1226. bomCategoryId: this.taskParam.id,
  1227. categoryCode: this.taskParam.categoryCode,
  1228. taskParam: this.currentTableData.taskParam.filter(
  1229. (item) => item.id !== row.id
  1230. )
  1231. }).then((data) => {
  1232. this.$message.success('删除成功');
  1233. this.reload();
  1234. });
  1235. },
  1236. remove2(row) {
  1237. workingStepDelete([row.id]).then(() => {
  1238. this.$message.success('删除成功');
  1239. this.reload();
  1240. });
  1241. this.$forceUpdate();
  1242. }
  1243. }
  1244. };
  1245. </script>
  1246. <style lang="scss" scoped>
  1247. .workmanship {
  1248. height: 100%;
  1249. .el-card {
  1250. height: 100%;
  1251. .el-card__body {
  1252. height: 100%;
  1253. display: flex;
  1254. flex-direction: column;
  1255. overflow: auto;
  1256. .table_list {
  1257. flex: 1;
  1258. display: flex;
  1259. flex-direction: column;
  1260. .el-table {
  1261. flex: 1;
  1262. overflow-y: auto !important;
  1263. display: flex;
  1264. flex-direction: column;
  1265. .el-table__body-wrapper {
  1266. flex: 1;
  1267. }
  1268. }
  1269. }
  1270. }
  1271. }
  1272. }
  1273. </style>
  1274. <style lang="scss" scoped>
  1275. // 工艺路线分组标题行样式(需要非 scoped 才能作用到 el-table 内部)
  1276. .workmanship {
  1277. ::v-deep .el-table {
  1278. tr.workmanship-group-header {
  1279. td {
  1280. background: #ecf5ff !important;
  1281. border-bottom: 1px solid #d9ecff !important;
  1282. padding: 10px 12px !important;
  1283. }
  1284. td.workmanship-group-cell {
  1285. padding: 0 !important;
  1286. background: #ecf5ff !important;
  1287. // 强制让跨列单元格撑满整行,避免被首列 45px 限制
  1288. width: auto !important;
  1289. min-width: 100% !important;
  1290. max-width: 100% !important;
  1291. }
  1292. .workmanship-route-header {
  1293. display: flex;
  1294. align-items: center;
  1295. padding: 4px 0;
  1296. color: #303133;
  1297. font-weight: 600;
  1298. cursor: pointer;
  1299. user-select: none;
  1300. white-space: nowrap;
  1301. &__arrow {
  1302. margin-right: 6px;
  1303. color: #409eff;
  1304. font-size: 14px;
  1305. transition: transform 0.2s ease;
  1306. }
  1307. &.is-collapsed &__arrow {
  1308. transform: rotate(-90deg);
  1309. }
  1310. i.el-icon-collection-tag {
  1311. margin-right: 6px;
  1312. color: #409eff;
  1313. font-size: 14px;
  1314. }
  1315. &__label {
  1316. margin-right: 4px;
  1317. color: #606266;
  1318. }
  1319. &__name {
  1320. color: #303133;
  1321. }
  1322. &:hover {
  1323. color: #409eff;
  1324. }
  1325. }
  1326. .workmanship-route-only-tip {
  1327. color: #e6a23c;
  1328. font-size: 12px;
  1329. }
  1330. }
  1331. }
  1332. }
  1333. </style>