statistics.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. <template>
  2. <div>
  3. <ele-pro-table
  4. v-if="
  5. statisticsType == 1 &&
  6. localDetails[0] &&
  7. localDetails[0].statisticsValue
  8. "
  9. ref="table"
  10. :columns="typeOneColumns"
  11. :datasource="localDetails[0].statisticsValue"
  12. cacheKey="mes-statistics-type-1-2511041946"
  13. :needPage="false"
  14. show-summary
  15. :summary-method="getSummaries"
  16. >
  17. <template v-slot:toolkit>
  18. <el-button
  19. v-if="localDetails[0].statisticsValue[0]"
  20. type="primary"
  21. @click="addRow"
  22. >
  23. 添加
  24. </el-button>
  25. </template>
  26. <template
  27. v-for="(item, index) in localDetails[0].statisticsValue[0]"
  28. v-slot:[`column_${index}`]="{ row }"
  29. >
  30. <div :key="`column_${index}`" class="column_content">
  31. <!-- 文本 -->
  32. <div v-if="row[index].paramType == 7 || row[index].isOnlyShow">
  33. {{ row[index].value }}
  34. </div>
  35. <!-- 数值 -->
  36. <template v-else-if="row[index].paramType == 1">
  37. <div style="display: flex">
  38. <el-input-number
  39. v-model="row[index].value"
  40. :placeholder="
  41. row[index].formula
  42. ? row[index].formula.replace(/[\[\]]/g, '')
  43. : `请输入${row[index].title}`
  44. "
  45. :disabled="Boolean(row[index].formula)"
  46. :controls="false"
  47. @change="handleDataChange(row)"
  48. ></el-input-number>
  49. <div style="width: 80px; height: 100%; flex-shrink: 0">
  50. <DictSelection
  51. v-model="row[index].unit"
  52. dictName="工艺参数单位"
  53. placeholder="单位"
  54. >
  55. </DictSelection
  56. ></div>
  57. </div>
  58. </template>
  59. <!-- 公式 -->
  60. <template v-else-if="row[index].paramType == 9">
  61. <div style="display: flex">
  62. <el-input
  63. v-model="row[index].value"
  64. :placeholder="
  65. row[index].formula
  66. ? row[index].formula.replace(/[\[\]]/g, '')
  67. : `请输入${row[index].title}`
  68. "
  69. disabled
  70. @input="handleDataChange(row)"
  71. ></el-input>
  72. <div style="width: 80px; height: 100%; flex-shrink: 0">
  73. <DictSelection
  74. v-model="row[index].unit"
  75. dictName="工艺参数单位"
  76. placeholder="单位"
  77. >
  78. </DictSelection
  79. ></div>
  80. </div>
  81. </template>
  82. <!-- 时间 -->
  83. <el-date-picker
  84. v-else-if="row[index].paramType == 5"
  85. v-model="row[index].value"
  86. type="datetime"
  87. placeholder="选择日期时间"
  88. style="width: 100%"
  89. >
  90. </el-date-picker>
  91. <!-- 其他 -->
  92. <el-input
  93. v-else
  94. v-model="row[index].value"
  95. placeholder="请输入内容"
  96. ></el-input>
  97. </div>
  98. </template>
  99. <template v-slot:operation="{ row }">
  100. <el-button type="text" size="small" @click="deleteRow(row)"
  101. >删除</el-button
  102. >
  103. </template>
  104. </ele-pro-table>
  105. <div
  106. v-if="
  107. statisticsType == 1 &&
  108. localDetails[0] &&
  109. localDetails[0].statisticsValue
  110. "
  111. class=""
  112. >
  113. </div>
  114. <ele-pro-table
  115. v-if="
  116. statisticsType == 2 &&
  117. localDetails[1] &&
  118. localDetails[1].statisticsValue
  119. "
  120. ref="table"
  121. :columns="typeTwoColumns"
  122. :datasource="typeTwoDataSource"
  123. cacheKey="mes-statistics-type-2-2511041946"
  124. :needPage="false"
  125. >
  126. <template v-slot:toolkit>
  127. <div>
  128. <el-input
  129. v-model="keyword"
  130. placeholder="请输入物料名称或编码"
  131. ></el-input>
  132. </div>
  133. </template>
  134. <template
  135. v-for="(item, index) in localDetails[1].statisticsValue[0]"
  136. v-slot:[`column_${index}`]="{ row }"
  137. >
  138. <div :key="`column_${index}`" class="column_content">
  139. <!-- 文本 -->
  140. <div v-if="row[index].paramType == 7 || row[index].isOnlyShow">
  141. {{ row[index].value }}
  142. </div>
  143. <!-- 数值 -->
  144. <template v-else-if="row[index].paramType == 1">
  145. <div style="display: flex">
  146. <el-input-number
  147. v-model="row[index].value"
  148. :placeholder="
  149. row[index].formula
  150. ? row[index].formula.replace(/[\[\]]/g, '')
  151. : `请输入${row[index].title}`
  152. "
  153. :disabled="Boolean(row[index].formula)"
  154. :controls="false"
  155. @change="handleDataChange(row)"
  156. ></el-input-number>
  157. <div style="width: 80px; height: 100%; flex-shrink: 0">
  158. <DictSelection
  159. v-model="row[index].unit"
  160. dictName="工艺参数单位"
  161. placeholder="单位"
  162. >
  163. </DictSelection
  164. ></div>
  165. </div>
  166. </template>
  167. <!-- 公式 -->
  168. <template v-else-if="row[index].paramType == 9">
  169. <div style="display: flex">
  170. <el-input
  171. v-model="row[index].value"
  172. :placeholder="
  173. row[index].formula
  174. ? row[index].formula.replace(/[\[\]]/g, '')
  175. : `请输入${row[index].title}`
  176. "
  177. disabled
  178. @input="handleDataChange(row)"
  179. ></el-input>
  180. <div style="width: 80px; height: 100%; flex-shrink: 0">
  181. <DictSelection
  182. v-model="row[index].unit"
  183. dictName="工艺参数单位"
  184. placeholder="单位"
  185. >
  186. </DictSelection
  187. ></div>
  188. </div>
  189. </template>
  190. <!-- 时间 -->
  191. <el-date-picker
  192. v-else-if="row[index].paramType == 5"
  193. v-model="row[index].value"
  194. type="datetime"
  195. placeholder="选择日期时间"
  196. style="width: 100%"
  197. >
  198. </el-date-picker>
  199. <!-- 其他 -->
  200. <el-input
  201. v-else
  202. v-model="row[index].value"
  203. placeholder="请输入内容"
  204. ></el-input>
  205. </div>
  206. </template>
  207. </ele-pro-table>
  208. <ele-pro-table
  209. v-if="
  210. statisticsType == 3 &&
  211. localDetails[2] &&
  212. localDetails[2].statisticsValue
  213. "
  214. ref="table"
  215. :columns="typeThreeColumns"
  216. :datasource="typeThreeDataSource"
  217. cacheKey="mes-statistics-type-3-2511051037"
  218. :needPage="false"
  219. >
  220. <template v-slot:toolkit>
  221. <el-select
  222. v-model="activeProductTaskName"
  223. placeholder="请选择"
  224. style="margin-right: 20px"
  225. >
  226. <el-option
  227. v-for="item in productTaskNameList"
  228. :key="item"
  229. :label="item"
  230. :value="item"
  231. >
  232. </el-option>
  233. </el-select>
  234. </template>
  235. <template
  236. v-for="(item, index) in localDetails[2].statisticsValue[0]"
  237. v-slot:[`column_${index}`]="{ row }"
  238. >
  239. <div :key="`column_${index}`" class="column_content">
  240. <!-- 文本 -->
  241. <div v-if="row[index].paramType == 7 || row[index].isOnlyShow">
  242. {{ row[index].value }}
  243. </div>
  244. <!-- 数值 -->
  245. <template v-else-if="row[index].paramType == 1">
  246. <div style="display: flex">
  247. <el-input
  248. v-model="row[index].value"
  249. :placeholder="
  250. row[index].formula
  251. ? row[index].formula.replace(/[\[\]]/g, '')
  252. : `请输入${row[index].title}`
  253. "
  254. :disabled="Boolean(row[index].formula)"
  255. :controls="false"
  256. @change="handleDataChange(row)"
  257. ></el-input>
  258. <div style="width: 80px; height: 100%; flex-shrink: 0">
  259. <DictSelection
  260. v-model="row[index].unit"
  261. dictName="工艺参数单位"
  262. placeholder="单位"
  263. >
  264. </DictSelection
  265. ></div>
  266. </div>
  267. </template>
  268. <!-- 公式 -->
  269. <template v-else-if="row[index].paramType == 9">
  270. <div style="display: flex">
  271. <el-input
  272. v-model="row[index].value"
  273. :placeholder="
  274. row[index].formula
  275. ? row[index].formula.replace(/[\[\]]/g, '')
  276. : `请输入${row[index].title}`
  277. "
  278. disabled
  279. @input="handleDataChange(row)"
  280. ></el-input>
  281. <div style="width: 80px; height: 100%; flex-shrink: 0">
  282. <DictSelection
  283. v-model="row[index].unit"
  284. dictName="工艺参数单位"
  285. placeholder="单位"
  286. >
  287. </DictSelection
  288. ></div>
  289. </div>
  290. </template>
  291. <!-- 时间 -->
  292. <el-date-picker
  293. v-else-if="row[index].paramType == 5"
  294. v-model="row[index].value"
  295. type="datetime"
  296. placeholder="选择日期时间"
  297. style="width: 100%"
  298. >
  299. </el-date-picker>
  300. <!-- 其他 -->
  301. <el-input
  302. v-else
  303. v-model="row[index].value"
  304. placeholder="请输入内容"
  305. ></el-input>
  306. </div>
  307. </template>
  308. </ele-pro-table>
  309. </div>
  310. </template>
  311. <script>
  312. import {
  313. listProduceTaskAndFormNum,
  314. getPickAndFeed,
  315. inStoreageRecord
  316. } from '@/api/producetaskrecordrulesrecord/index';
  317. export default {
  318. props: {
  319. details: {
  320. type: Array,
  321. default: () => []
  322. },
  323. // 规则详情列表
  324. rulesDetailList: {
  325. type: Array,
  326. default: () => []
  327. },
  328. statisticsType: {
  329. type: String,
  330. default: '1' // 1-成品统计,2-物料统计,3-工序统计
  331. },
  332. // 工艺路线id
  333. routingId: {
  334. type: [String, Number],
  335. default: ''
  336. },
  337. // workOrderId 工单id
  338. workOrderId: {
  339. type: [String, Number],
  340. default: ''
  341. },
  342. // bomCategoryId
  343. bomCategoryId: {
  344. type: [String, Number],
  345. default: ''
  346. },
  347. //ruleId
  348. ruleId: {
  349. type: [String, Number],
  350. default: ''
  351. },
  352. // 单位
  353. unit: {
  354. type: String,
  355. default: ''
  356. },
  357. // 要求生产数量
  358. formingNum: {
  359. type: [Number],
  360. default: 0
  361. }
  362. },
  363. watch: {
  364. details: {
  365. deep: true,
  366. immediate: true,
  367. handler(val) {
  368. // 已构建过统计数据
  369. if (
  370. val[0] &&
  371. val[0].statisticsValue &&
  372. val[0].statisticsValue.length > 0
  373. ) {
  374. this.localDetails = JSON.parse(JSON.stringify(val));
  375. } else {
  376. this.buildDetials(val);
  377. }
  378. }
  379. }
  380. },
  381. computed: {
  382. // 成品统计表头
  383. typeOneColumns() {
  384. if (
  385. this.localDetails.length > 0 &&
  386. this.localDetails[0].statisticsValue &&
  387. this.localDetails[0].statisticsType == 1 &&
  388. this.localDetails[0].statisticsValue[0]
  389. ) {
  390. const cList = this.localDetails[0].statisticsValue[0].map(
  391. (item, index) => {
  392. return {
  393. label: item.title,
  394. slot: `column_${index}`,
  395. align: 'center',
  396. minWidth: 200
  397. };
  398. }
  399. );
  400. cList.unshift({
  401. width: 50,
  402. type: 'index',
  403. columnKey: 'index',
  404. align: 'center'
  405. });
  406. cList.push({
  407. width: 70,
  408. label: '操作',
  409. slot: 'operation',
  410. fixed: 'right'
  411. });
  412. return cList;
  413. } else {
  414. return [];
  415. }
  416. },
  417. // 物料统计表头
  418. typeTwoColumns() {
  419. if (
  420. this.localDetails.length > 1 &&
  421. this.localDetails[1].statisticsValue &&
  422. this.localDetails[1].statisticsType == 2 &&
  423. this.localDetails[1].statisticsValue[0]
  424. ) {
  425. const cList = this.localDetails[1].statisticsValue[0].map(
  426. (item, index) => {
  427. return {
  428. label: item.title,
  429. slot: `column_${index}`,
  430. align: 'center',
  431. minWidth: 200
  432. };
  433. }
  434. );
  435. cList.unshift({
  436. width: 45,
  437. type: 'index',
  438. columnKey: 'index',
  439. align: 'center'
  440. });
  441. return cList;
  442. } else {
  443. return [];
  444. }
  445. },
  446. // 工序统计表头
  447. typeThreeColumns() {
  448. if (
  449. this.localDetails.length > 2 &&
  450. this.localDetails[2].statisticsValue &&
  451. this.localDetails[2].statisticsType == 3 &&
  452. this.localDetails[2].statisticsValue[0]
  453. ) {
  454. const cList = this.localDetails[2].statisticsValue[0].map(
  455. (item, index) => {
  456. return {
  457. label: item.title,
  458. slot: `column_${index}`,
  459. align: 'center',
  460. minWidth: 200
  461. };
  462. }
  463. );
  464. cList.unshift({
  465. width: 45,
  466. type: 'index',
  467. columnKey: 'index',
  468. align: 'center'
  469. });
  470. return cList;
  471. } else {
  472. return [];
  473. }
  474. },
  475. // 工序名称列表
  476. productTaskNameList() {
  477. if (this.localDetails[2]?.statisticsValue) {
  478. const list = this.localDetails[2].statisticsValue.map((rows) => {
  479. return rows.find((i) => i.title === '工序名称')?.value || '';
  480. });
  481. list.unshift('全部'); // 全部选项
  482. return list;
  483. } else {
  484. return [];
  485. }
  486. },
  487. typeTwoDataSource() {
  488. if (this.keyword.trim() === '') {
  489. return this.localDetails[1].statisticsValue;
  490. } else {
  491. return this.localDetails[1].statisticsValue.filter((rows) => {
  492. const nameItem = rows.find((i) => i.title === '物料名称');
  493. const codeItem = rows.find((i) => i.title === '物料编码');
  494. return (
  495. (nameItem && nameItem.value.includes(this.keyword)) ||
  496. (codeItem && codeItem.value.includes(this.keyword))
  497. );
  498. });
  499. }
  500. },
  501. typeThreeDataSource() {
  502. if (this.activeProductTaskName === '全部') {
  503. return this.localDetails[2].statisticsValue;
  504. } else {
  505. return this.localDetails[2].statisticsValue.filter((rows) => {
  506. const nameItem = rows.find((i) => i.title === '工序名称');
  507. return nameItem && nameItem.value === this.activeProductTaskName;
  508. });
  509. }
  510. }
  511. },
  512. data() {
  513. return {
  514. localDetails: [
  515. {
  516. ruleId: this.ruleId,
  517. statisticsType: 1,
  518. statisticsValue: []
  519. },
  520. {
  521. ruleId: this.ruleId,
  522. statisticsType: 2,
  523. statisticsValue: []
  524. },
  525. {
  526. ruleId: this.ruleId,
  527. statisticsType: 3,
  528. statisticsValue: []
  529. }
  530. ],
  531. activeProductTaskName: '全部',
  532. keyword: '',
  533. _emitChangeTimer: null
  534. };
  535. },
  536. methods: {
  537. // 通知父组件数据变更(防抖 500ms)
  538. emitChange() {
  539. if (this._emitChangeTimer) clearTimeout(this._emitChangeTimer);
  540. this._emitChangeTimer = setTimeout(() => {
  541. this.$emit('update:details', this.localDetails);
  542. }, 500);
  543. },
  544. // 构建统计数据
  545. async buildDetials(detials) {
  546. if (detials.length === 0) {
  547. console.log('重置了');
  548. this.localDetails = [
  549. {
  550. ruleId: this.ruleId,
  551. statisticsType: 1,
  552. statisticsValue: []
  553. },
  554. {
  555. ruleId: this.ruleId,
  556. statisticsType: 2,
  557. statisticsValue: []
  558. },
  559. {
  560. ruleId: this.ruleId,
  561. statisticsType: 3,
  562. statisticsValue: []
  563. }
  564. ];
  565. return;
  566. }
  567. console.log('this.localDetails -- 开始构建', this.localDetails);
  568. if (
  569. (this.localDetails[0].statisticsValue &&
  570. this.localDetails[0].statisticsValue.length == 0) ||
  571. this.localDetails[0].ruleId != this.ruleId
  572. ) {
  573. const list = this.rulesDetailList
  574. .filter((i) => i.statisticsType == 1)
  575. .map((i) => {
  576. return {
  577. title: i.paramValue,
  578. value: i.defaultValue,
  579. formula: i.formula,
  580. unit: i.unitName || this.unit,
  581. paramType: i.paramType
  582. };
  583. });
  584. // 已构建过统计数据
  585. this.localDetails[0] = {
  586. ...this.localDetails[0],
  587. ruleId: this.ruleId,
  588. statisticsType: 1,
  589. statisticsValue: [list]
  590. };
  591. }
  592. // 构建物料和工序数据
  593. this.rebuildMaterialAndProcessData();
  594. },
  595. // 构建物料和工序数据
  596. async rebuildMaterialAndProcessData() {
  597. if (this.unit) {
  598. // 存在单位则 更新statisticsType为1的单位
  599. this.localDetails[0].statisticsValue.forEach((rows) => {
  600. rows.forEach((item) => {
  601. if (!item.unit) {
  602. item.unit = this.unit;
  603. }
  604. });
  605. });
  606. }
  607. let routingTaskList = [];
  608. let pickAndFeed = [];
  609. if (this.workOrderId) {
  610. // 并发获取工序任务列表和物料数据
  611. let [routingTaskReslut, pickAndFeedList, inStoreageRecordList] =
  612. await Promise.all([
  613. listProduceTaskAndFormNum({ workOrderId: this.workOrderId }),
  614. getPickAndFeed({
  615. workOrderId: this.workOrderId,
  616. bomCategoryId: this.bomCategoryId
  617. }),
  618. inStoreageRecord({
  619. workOrderId: this.workOrderId
  620. })
  621. ]);
  622. pickAndFeed = pickAndFeedList || [];
  623. routingTaskList = routingTaskReslut || [];
  624. console.log('inStoreageRecordList', inStoreageRecordList);
  625. if (inStoreageRecordList && inStoreageRecordList.length > 0) {
  626. // 成品入库一行的记录
  627. const rows = this.rulesDetailList
  628. .filter((i) => i.statisticsType == 1)
  629. .map((i) => {
  630. return {
  631. title: i.paramValue,
  632. value: i.defaultValue,
  633. formula: i.formula,
  634. unit: i.unitName || this.unit,
  635. paramType: i.paramType
  636. };
  637. });
  638. if (this.rulesDetailList.length > 0) {
  639. const list = inStoreageRecordList.map((record) => {
  640. const rowsCopy = JSON.parse(JSON.stringify(rows));
  641. rowsCopy.forEach((item) => {
  642. if (item.title.includes('成品入库数')) {
  643. item.value = record.totalCount;
  644. } else if (
  645. item.title === '成品入库时间' ||
  646. item.title === '入库时间' ||
  647. item.title === '生产日期'
  648. ) {
  649. item.value = record.storageTime;
  650. }
  651. item.unit = record.measuringUnit || this.unit;
  652. });
  653. return rowsCopy;
  654. });
  655. // 赋值
  656. this.localDetails[0].statisticsValue = list;
  657. console.log(
  658. 'this.localDetails[0].statisticsValue 重新',
  659. this.localDetails[0].statisticsValue
  660. );
  661. }
  662. }
  663. }
  664. console.log('routingTaskList 工序数据', routingTaskList);
  665. console.log('物料数据 pickAndFeed', pickAndFeed);
  666. this.localDetails[1] = {
  667. ...this.localDetails[1],
  668. ruleId: this.ruleId,
  669. statisticsType: 2,
  670. statisticsValue: pickAndFeed.map((pick) => {
  671. const data = this.rulesDetailList
  672. .filter((i) => i.statisticsType == 2)
  673. .map((i) => {
  674. return {
  675. title: i.paramValue,
  676. value: i.defaultValue,
  677. formula: i.formula,
  678. unit: i.unitName || this.unit,
  679. paramType: i.paramType
  680. };
  681. });
  682. data.unshift({
  683. title: '领用量',
  684. value: pick.pickQuantity,
  685. formula: '',
  686. unit: pick.pickUnit,
  687. // 1数值类型
  688. paramType: 1
  689. });
  690. data.unshift({
  691. title: '使用量',
  692. value: pick.feedQuantity,
  693. formula: '',
  694. unit: pick.feedUnit,
  695. // 1数值类型
  696. paramType: 1
  697. });
  698. data.unshift({
  699. title: '物料编码',
  700. value: pick.categoryCode,
  701. formula: '',
  702. unit: '',
  703. // 仅展示不计算和输入
  704. isOnlyShow: true,
  705. // 7文本类型
  706. paramType: 7
  707. });
  708. data.unshift({
  709. title: '物料名称',
  710. value: pick.categoryName,
  711. formula: '',
  712. unit: '',
  713. // 仅展示不计算和输入
  714. isOnlyShow: true,
  715. // produceTaskName 工序名称
  716. produceTaskName: pick.produceTaskName,
  717. // 7文本类型
  718. paramType: 7
  719. });
  720. return data;
  721. })
  722. };
  723. this.localDetails[2] = {
  724. ...this.localDetails[2],
  725. ruleId: this.ruleId,
  726. statisticsType: 3,
  727. statisticsValue: routingTaskList.map((task) => {
  728. const data = this.rulesDetailList
  729. .filter((i) => i.statisticsType == 3)
  730. .map((i) => {
  731. return {
  732. title: i.paramValue,
  733. value: i.defaultValue,
  734. formula: i.formula,
  735. unit: i.unitName || this.unit,
  736. paramType: i.paramType
  737. };
  738. });
  739. data.unshift({
  740. title: '工序名称',
  741. value: task.name || task.produceTaskName,
  742. formula: '',
  743. unit: '',
  744. // 仅展示不计算和输入
  745. isOnlyShow: true,
  746. // 7文本类型
  747. paramType: 7
  748. });
  749. // 接收
  750. if (!isNaN(task.formedNum)) {
  751. const item = data.find(
  752. (i) => i.title === '接收量' || i.title === '接收数量'
  753. );
  754. if (item) {
  755. item.value = task.formedNum;
  756. }
  757. }
  758. // 不合格品
  759. if (!isNaN(task.noQualifiedSum)) {
  760. const item = data.find(
  761. (i) => i.title == '不合格品量' || i.title == '不合格品数量'
  762. );
  763. if (item) {
  764. item.value = task.noQualifiedSum;
  765. }
  766. }
  767. // 合格品量
  768. if (!isNaN(task.qualified)) {
  769. const item = data.find(
  770. (i) => i.title == '合格品量' || i.title == '合格品数量'
  771. );
  772. if (item) {
  773. item.value = task.qualified;
  774. }
  775. }
  776. return data;
  777. })
  778. };
  779. console.log('this.localDetails', this.localDetails);
  780. // 通知父组件数据变更
  781. this.emitChange();
  782. },
  783. // 当数据变化时计算公式
  784. handleDataChange(row) {
  785. console.log('row 触发', row);
  786. // todo 计算公式
  787. // 寻找当前行的所有可计算项,进行计算更新
  788. const formulaItmes = row.filter((i) => i.formula);
  789. formulaItmes.forEach((formulaItem) => {
  790. // 简单示例 公式为 [(][合格品量][+][取样量][)][/][接收量]
  791. const tokens = formulaItem.formula
  792. ? formulaItem.formula.match(/\[([^\]]+)\]/g) || []
  793. : [];
  794. let expr = '';
  795. let invalid = false;
  796. for (const token of tokens) {
  797. if (invalid) break;
  798. const content = token.slice(1, -1).trim();
  799. if (['+', '-', '*', '/', '(', ')', '%'].includes(content)) {
  800. expr += content;
  801. } else {
  802. const target = row.find((r) => r.title === content);
  803. // 特殊处理:当标题为“要求生产数量”且当前行未找到对应项时,用 this.formingNum 参与计算
  804. if (!target && content === '要求生产数量') {
  805. const formingVal = Number(this.formingNum);
  806. if (!Number.isFinite(formingVal)) {
  807. invalid = true;
  808. expr = '';
  809. break;
  810. }
  811. expr += formingVal;
  812. continue;
  813. }
  814. // 若对应的项不存在 或 value为空 则取消计算
  815. if (
  816. !target ||
  817. target.value === '' ||
  818. target.value === null ||
  819. target.value === undefined
  820. ) {
  821. invalid = true;
  822. expr = '';
  823. break;
  824. }
  825. const num = Number(target.value);
  826. if (!Number.isFinite(num)) {
  827. invalid = true;
  828. expr = '';
  829. break;
  830. }
  831. expr += num;
  832. }
  833. }
  834. // 若无效则直接跳过后续计算 (expr 已被置空)
  835. if (expr && /^[0-9+\-*/().\s]+$/.test(expr)) {
  836. try {
  837. const result = Function(`"use strict";return (${expr})`)();
  838. let value =
  839. Number.isFinite(result) && !Number.isNaN(result) ? result : '';
  840. if (value !== '' && typeof value === 'number') {
  841. const dec = value.toString().split('.')[1];
  842. if (dec && dec.length > 4) {
  843. const intPart = Math.trunc(value);
  844. const truncated = dec.slice(0, 4);
  845. value = Number(intPart + '.' + truncated);
  846. }
  847. }
  848. formulaItem.value = value;
  849. } catch (e) {
  850. formulaItem.value = '';
  851. }
  852. } else {
  853. formulaItem.value = '';
  854. }
  855. });
  856. // 通知父组件数据变更
  857. this.emitChange();
  858. },
  859. // 验证所有行/列(排除仅展示项),收集缺失项并提示;返回布尔值
  860. validateStatisticsFilled() {
  861. return true; // 暂时不验证
  862. if (
  863. !Array.isArray(this.localDetails) ||
  864. this.localDetails.length === 0
  865. ) {
  866. this.$message.error('统计数据为空');
  867. return false;
  868. }
  869. const typeNameMap = {
  870. 1: '成品统计',
  871. 2: '物料统计',
  872. 3: '工序统计'
  873. };
  874. const missing = [];
  875. this.localDetails.forEach((detail, detailIndex) => {
  876. if (!detail || !detail.statisticsValue) return;
  877. // 统一转成多行结构
  878. const rows =
  879. detail.statisticsType === 1
  880. ? [detail.statisticsValue[0] || []]
  881. : detail.statisticsValue || [];
  882. rows.forEach((row, rowIndex) => {
  883. // 记录行的标识(物料名称 / 工序名称)
  884. let rowLabel = '';
  885. if (detail.statisticsType === 2) {
  886. const nameItem = row.find((i) => i && i.title === '物料名称');
  887. if (nameItem)
  888. rowLabel = `(${nameItem.value || '物料行' + (rowIndex + 1)})`;
  889. } else if (detail.statisticsType === 3) {
  890. const procItem = row.find((i) => i && i.title === '工序名称');
  891. if (procItem)
  892. rowLabel = `(${procItem.value || '工序行' + (rowIndex + 1)})`;
  893. }
  894. row.forEach((item, colIndex) => {
  895. if (!item || item.isOnlyShow) return;
  896. const val = item.value;
  897. if (val === '' || val === null || val === undefined) {
  898. missing.push(
  899. `${typeNameMap[detail.statisticsType]}${rowLabel} - 第${
  900. colIndex + 1
  901. }列【${item.title}】未填写`
  902. );
  903. }
  904. });
  905. });
  906. });
  907. if (missing.length > 0) {
  908. // 过多时截断
  909. const displayList =
  910. missing.length > 1
  911. ? missing.slice(0, 1).concat(`, 共有 ${missing.length} 项未填写`)
  912. : missing;
  913. this.$message.warning(`请完善统计数据:\n${displayList.join('\n')}`);
  914. return false;
  915. }
  916. return true;
  917. },
  918. getSummaries() {
  919. const sums = [];
  920. // 优化:缓存公式解析、统一数值获取,减少重复正则与遍历
  921. if (
  922. !this.localDetails[0] ||
  923. !this.localDetails[0].statisticsValue ||
  924. this.localDetails[0].statisticsValue.length === 0
  925. ) {
  926. return sums;
  927. }
  928. const rows = this.localDetails[0].statisticsValue;
  929. const firstRow = rows[0] || [];
  930. sums.push('合计');
  931. // 缓存结构:{ formulaString: [{type:'op'|'num'|'ref', value:'+'|number|'列标题'}] }
  932. if (!this._formulaCache) this._formulaCache = Object.create(null);
  933. const parseFormula = (formula) => {
  934. if (!formula) return [];
  935. if (this._formulaCache[formula]) return this._formulaCache[formula];
  936. // 提取 [xxx] 片段
  937. const rawTokens = formula.match(/\[([^\]]+)\]/g) || [];
  938. const tokens = [];
  939. rawTokens.forEach((token) => {
  940. const content = token.slice(1, -1).trim();
  941. // 运算符/括号
  942. if (['+', '-', '*', '/', '(', ')', '%'].includes(content)) {
  943. tokens.push({ type: 'op', value: content });
  944. } else {
  945. // 引用列标题或特殊内置
  946. tokens.push({ type: 'ref', value: content });
  947. }
  948. });
  949. this._formulaCache[formula] = tokens;
  950. return tokens;
  951. };
  952. // 辅助:安全数值
  953. const toNum = (v) => {
  954. const n = Number(v);
  955. return Number.isFinite(n) ? n : 0;
  956. };
  957. // 先累加所有“非公式数值列”
  958. rows.forEach((row) => {
  959. row.forEach((cell, colIndex) => {
  960. const targetIndex = colIndex + 1;
  961. if (sums[targetIndex] === undefined) sums[targetIndex] = '';
  962. if (cell.isOnlyShow || cell.paramType !== 1) {
  963. return;
  964. }
  965. if (cell.formula) {
  966. // 公式列占位为 0,后续再算
  967. if (sums[targetIndex] === '') sums[targetIndex] = 0;
  968. return;
  969. }
  970. const num = Number(cell.value);
  971. if (!Number.isFinite(num)) return;
  972. if (typeof sums[targetIndex] !== 'number') sums[targetIndex] = 0;
  973. sums[targetIndex] += num;
  974. });
  975. });
  976. // 计算公式列(使用上面得到的合计值作为引用值)
  977. firstRow.forEach((meta, colIndex) => {
  978. if (!meta.formula) return;
  979. const targetIndex = colIndex + 1;
  980. const tokens = parseFormula(meta.formula);
  981. if (!tokens.length) {
  982. sums[targetIndex] = '';
  983. return;
  984. }
  985. let expr = '';
  986. let invalid = false;
  987. for (const tk of tokens) {
  988. if (invalid) break;
  989. if (tk.type === 'op') {
  990. expr += tk.value;
  991. continue;
  992. }
  993. // 引用列/特殊引用
  994. if (tk.value === '要求生产数量') {
  995. const formingVal = Number(this.formingNum);
  996. if (!Number.isFinite(formingVal)) {
  997. invalid = true;
  998. } else {
  999. expr += formingVal;
  1000. }
  1001. continue;
  1002. }
  1003. const refColIdx = firstRow.findIndex((i) => i.title === tk.value);
  1004. if (refColIdx === -1) {
  1005. invalid = true;
  1006. break;
  1007. }
  1008. const refVal = sums[refColIdx + 1];
  1009. if (!Number.isFinite(Number(refVal))) {
  1010. invalid = true;
  1011. break;
  1012. }
  1013. expr += toNum(refVal);
  1014. }
  1015. if (invalid || !expr || !/^[0-9+\-*/().\s%]+$/.test(expr)) {
  1016. sums[targetIndex] = '';
  1017. return;
  1018. }
  1019. try {
  1020. let result = Function('"use strict";return (' + expr + ')')();
  1021. if (!Number.isFinite(result) || Number.isNaN(result)) {
  1022. sums[targetIndex] = '';
  1023. } else {
  1024. const dec = result.toString().split('.')[1];
  1025. if (dec && dec.length > 4) {
  1026. const intPart = Math.trunc(result);
  1027. const truncated = dec.slice(0, 4);
  1028. result = Number(intPart + '.' + truncated);
  1029. }
  1030. sums[targetIndex] = result;
  1031. }
  1032. } catch (e) {
  1033. sums[targetIndex] = '';
  1034. }
  1035. });
  1036. return sums;
  1037. },
  1038. // 添加行
  1039. addRow() {
  1040. const row = JSON.parse(
  1041. JSON.stringify(this.localDetails[0].statisticsValue[0])
  1042. );
  1043. row.forEach((item) => {
  1044. if (!item.isOnlyShow) {
  1045. item.value = '';
  1046. }
  1047. });
  1048. this.localDetails[0].statisticsValue.push(row);
  1049. },
  1050. deleteRow(row) {
  1051. // 最后一条不允许删除
  1052. if (this.localDetails[0].statisticsValue.length <= 1) {
  1053. this.$message.warning('至少保留一条统计数据');
  1054. return;
  1055. }
  1056. const index = this.localDetails[0].statisticsValue.indexOf(row);
  1057. this.localDetails[0].statisticsValue.splice(index, 1);
  1058. // 通知父组件数据变更
  1059. this.emitChange();
  1060. }
  1061. }
  1062. };
  1063. </script>
  1064. <style scoped lang="scss">
  1065. .column_content ::v-deep .el-input-group__append {
  1066. padding: 0;
  1067. }
  1068. </style>