index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. <template>
  2. <div class="quality-wrapper flex flex-col">
  3. <div class="search-wrapper">
  4. <BasicForm class="search-form" @register="registerForm">
  5. <template #ccmNo="{ model, field }">
  6. <segmented-select
  7. v-model:value="model[field]"
  8. @change="
  9. (v) => {
  10. ccmNo = v;
  11. getList();
  12. }
  13. "
  14. dict="lg_zj"
  15. />
  16. </template>
  17. <template #shiftObj>
  18. <div class="shift-performance-tags flex-1">
  19. <a-tag
  20. :color="shiftColor[index]"
  21. @click="
  22. () => {
  23. currentShift = index;
  24. getList();
  25. }
  26. "
  27. v-for="(item, index) in shiftPerformanceColumns"
  28. >
  29. {{ item.createTime ? item.createTime.substring(5, 16) : '' }} ~
  30. {{ item.changeShiftTime ? item.changeShiftTime.substring(5, 16) : '当前时间' }}
  31. 【{{ getTeamShift(item.shift, item.shiftGroup) }}】
  32. <span class="current-shift" :style="{ background: shiftColor[index] }" v-if="index === currentShift"></span>
  33. </a-tag>
  34. </div>
  35. </template>
  36. <template #advanceBefore>
  37. <div class="flex items-center">
  38. <a-button
  39. size="large"
  40. type="primary"
  41. @click="
  42. () => {
  43. switchMiopen = true;
  44. switchMiNum = 0;
  45. }
  46. "
  47. >
  48. 切换米重</a-button
  49. >
  50. <a-button
  51. style="margin-left: 10px"
  52. size="large"
  53. type="primary"
  54. @click="
  55. () =>
  56. shiftPerformanceColumns[currentShift] &&
  57. openPrintOriginalRecordsModal(true, {
  58. ccmNo: ccmNo,
  59. queryType: shiftPerformanceColumns[currentShift] && shiftPerformanceColumns[currentShift].changeShiftTime ? '2' : '1',
  60. shiftText: '',
  61. time:
  62. shiftPerformanceColumns[currentShift] && shiftPerformanceColumns[currentShift].changeShiftTime
  63. ? dayjs(shiftPerformanceColumns[currentShift].changeShiftTime).subtract(3, 'hour').format('YYYY-MM-DD HH:mm:ss')
  64. : '',
  65. curShiftInfo: {
  66. shift: shiftPerformanceColumns[currentShift] ? shiftPerformanceColumns[currentShift].shift : '',
  67. shiftGroup: shiftPerformanceColumns[currentShift] ? shiftPerformanceColumns[currentShift].shiftGroup : '',
  68. },
  69. changeShiftId: shiftPerformanceColumns[currentShift] ? shiftPerformanceColumns[currentShift].id : '',
  70. })
  71. "
  72. >
  73. 原始记录</a-button
  74. >
  75. <a-button
  76. style="margin-left: 10px"
  77. size="large"
  78. type="primary"
  79. @click="
  80. () => {
  81. confirmedWithoutErrorOpen = true;
  82. }
  83. "
  84. >
  85. 确认无误</a-button
  86. >
  87. </div>
  88. </template>
  89. </BasicForm>
  90. </div>
  91. <BasicTable @register="registerTable">
  92. <template #tableTitle>
  93. <div class="flex table-title-info">
  94. <div class="table-title-info-item flex">
  95. <span class="line_b">{{ ccmNo }}</span>
  96. <span>#机</span>
  97. </div>
  98. <div class="table-title-info-item flex"><span>质检员</span><span class="line_b"></span></div>
  99. <div class="table-title-info-item flex flex-1 align-center justify-center">
  100. <span class="line_b">{{ getSelectedDate[0] }}</span>
  101. <span>年</span>
  102. <span class="line_b">{{ getSelectedDate[1] }}</span>
  103. <span>月</span>
  104. <span class="line_b">{{ getSelectedDate[2] }}</span>
  105. <span>日</span>
  106. <span class="line_b">{{
  107. shiftPerformanceColumns[currentShift]
  108. ? getTeamShift(shiftPerformanceColumns[currentShift].shift, shiftPerformanceColumns[currentShift].shiftGroup)
  109. : ''
  110. }}</span>
  111. </div>
  112. <div class="table-title-info-item flex"><span>zj/R19</span></div>
  113. </div>
  114. </template>
  115. <template #footer>
  116. <div class="flex footer-tj">
  117. <a-descriptions
  118. size="small"
  119. bordered
  120. class="remark"
  121. :labelStyle="labelStyle"
  122. :contentStyle="{
  123. padding: 0,
  124. }"
  125. >
  126. <a-descriptions-item label="备注:">
  127. <a-textarea :bordered="false" @blur="onTotalNoteBlur" v-model:value="statisticsInfo.remark" style="height: 116px" placeholder="备注" />
  128. </a-descriptions-item>
  129. </a-descriptions>
  130. <a-descriptions size="small" bordered class="produce" :labelStyle="labelStyle">
  131. <a-descriptions-item label="本班冶炼:"> {{ statisticsInfo.classHeatNum }} 炉</a-descriptions-item>
  132. <a-descriptions-item label="止上班日累计:">{{ statisticsInfo.dayStartHeatCount }} 炉</a-descriptions-item>
  133. <a-descriptions-item label="止本班日累计:"> {{ statisticsInfo.dayEndHeatCount }} 炉</a-descriptions-item>
  134. <a-descriptions-item label="本班冶炼:"> {{ statisticsInfo.classTotalWeight }} 吨</a-descriptions-item>
  135. <a-descriptions-item label="止上班日累计:">{{ statisticsInfo.dayStartWeight }} 吨</a-descriptions-item>
  136. <a-descriptions-item label="止本班日累计:">{{ statisticsInfo.dayEndWeight }} 吨</a-descriptions-item>
  137. <a-descriptions-item label="日冶炼:">{{ statisticsInfo.dayHeatCount }} 炉</a-descriptions-item>
  138. <a-descriptions-item label="日产:">{{ statisticsInfo.dayEndWeight }} 吨</a-descriptions-item>
  139. <a-descriptions-item> </a-descriptions-item>
  140. </a-descriptions>
  141. </div>
  142. </template>
  143. </BasicTable>
  144. </div>
  145. <!-- 打印原始数据 -->
  146. <printOriginalRecords @register="registerPrintOriginalRecordsModal" />
  147. <!-- 确认无误 -->
  148. <a-modal v-model:open="confirmedWithoutErrorOpen" title="确认信息" ok-text="确认" cancel-text="取消" @ok="onTotalNoteBlur(1)">
  149. <div style="margin: 20px">
  150. <a-textarea v-model:value="statisticsInfo.remark" placeholder="备注" :auto-size="{ minRows: 2, maxRows: 5 }" />
  151. </div>
  152. </a-modal>
  153. <!-- 编辑备注 -->
  154. <a-modal v-model:open="editNotesOpen" :title="'备注【' + editNotesRecord.heatNo + '】'" ok-text="确认" cancel-text="取消" @ok="editNotesSubmit">
  155. <div style="margin: 20px">
  156. <a-textarea v-model:value="editNotesTxt" placeholder="备注" :auto-size="{ minRows: 2, maxRows: 5 }" />
  157. </div>
  158. </a-modal>
  159. <!-- 切换米重 -->
  160. <a-modal v-model:open="switchMiopen" title="切换米重" ok-text="确认" cancel-text="取消" @ok="switchMiSubmit">
  161. <div style="margin: 20px">
  162. <a-input-number v-model:value="switchMiNum" placeholder="米重" :min="0" />
  163. </div>
  164. </a-modal>
  165. </template>
  166. <script setup lang="ts">
  167. import { computed, onMounted, ref } from 'vue';
  168. import { useForm, BasicForm } from '/@/components/Form';
  169. import SegmentedSelect from '/@/components/SegmentedSelect/index.vue';
  170. import { BasicTable } from '/@/components/Table';
  171. import dayjs, { Dayjs } from 'dayjs';
  172. import { useListPage } from '/@/hooks/system/useListPage';
  173. import { getFormSchemas, getTableColumns } from './quality.data';
  174. import { getTeamShift } from '../Dashboard/dashboard.api';
  175. import { list } from '../ShiftPerformance/ShiftPerformance.api';
  176. import { getQualityInspection, updateInfo, confirmRecordNote, changeMeterWeight } from './quality.api';
  177. import { useModal } from '/@/components/Modal';
  178. import printOriginalRecords from '../operator/components/printOriginalRecords.vue';
  179. import { useMessage } from '/@/hooks/web/useMessage';
  180. const { createMessage } = useMessage();
  181. // 注册打印原始记录modal
  182. const [registerPrintOriginalRecordsModal, { openModal: openPrintOriginalRecordsModal }] = useModal();
  183. const shiftPerformanceColumns = ref<any>([]);
  184. const shiftColor = ['#f50', '#2db7f5', '#87d068'];
  185. const currentShift = ref(-1);
  186. const ccmNo = ref('5');
  187. const labelStyle = {
  188. width: '100px',
  189. paddingRight: 0,
  190. paddingLeft: 0,
  191. textAlign: 'center',
  192. };
  193. /**
  194. * BasicForm绑定注册;
  195. */
  196. const [registerForm, { getFieldsValue }] = useForm({
  197. //注册表单列
  198. schemas: getFormSchemas({
  199. onDateChange: (v) => {
  200. currentShift.value = -1;
  201. getShiftInfo(3, dayjs(v));
  202. },
  203. }),
  204. //是否显示展开收起按钮,默认false
  205. showAdvancedButton: false,
  206. showResetButton: false,
  207. showSubmitButton: false,
  208. compact: true,
  209. //超过指定行数折叠,默认3行
  210. autoAdvancedCol: 3,
  211. //折叠时默认显示行数,默认1行
  212. alwaysShowLines: 3,
  213. //将表单内时间区域的值映射成 2个字段, 'YYYY-MM-DD'日期格式化
  214. fieldMapToTime: [
  215. ['arrivalTime', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm:ss'],
  216. // ['storageTime', ['storageTime_begin', 'storageTime_end'], 'YYYY-MM-DD HH:mm:ss'],
  217. ],
  218. //每列占比,默认一行为24
  219. baseColProps: { span: 6 },
  220. });
  221. // 获取日期
  222. const getSelectedDate = computed(() => {
  223. const formVals = getFieldsValue();
  224. if (formVals && formVals.queryDate) {
  225. return formVals.queryDate.split('-');
  226. }
  227. return dayjs().format('YYYY-MM-DD').split('-');
  228. });
  229. const columns = getTableColumns({
  230. onTimeChange(date, record) {
  231. editInfo({
  232. originalProductRecordId: record.originalProductRecordId,
  233. deliveryTime: date.format('YYYY-MM-DD HH:mm:ss'),
  234. });
  235. },
  236. onClickRemark(record) {
  237. editNotesOpen.value = true;
  238. editNotesTxt.value = record.notes;
  239. editNotesRecord.value = record;
  240. },
  241. onBrandNumChange(v, record) {
  242. editInfo({
  243. originalProductRecordId: record.originalProductRecordId,
  244. brandNum: v,
  245. });
  246. },
  247. });
  248. //注册table数据
  249. const { tableContext } = useListPage({
  250. tableProps: {
  251. columns,
  252. canResize: false,
  253. showActionColumn: false,
  254. useSearchForm: false,
  255. showTableSetting: false,
  256. striped: true,
  257. pagination: false,
  258. size: 'small',
  259. },
  260. });
  261. const [registerTable, { setLoading, setTableData, getColumns, setColumns }, {}] = tableContext;
  262. const getDefaulData = (total = 30) => {
  263. let data: any[] = [];
  264. for (let i = 0; i < total; i++) {
  265. data.push({
  266. heatNo: '',
  267. brandNum: '',
  268. deliveryTime: '',
  269. originalProductRecordId: '',
  270. lengthCountMap: {},
  271. });
  272. }
  273. return data;
  274. };
  275. const statisticsInfo = ref<any>({});
  276. const getList = async () => {
  277. try {
  278. setLoading(true);
  279. const res = await getQualityInspection({
  280. ccmNo: ccmNo.value,
  281. changeShiftId: shiftPerformanceColumns.value[currentShift.value].id,
  282. });
  283. const { records, statistics } = res;
  284. statisticsInfo.value = { ...statistics, orgRemark: statistics.remark };
  285. let sizeArr: string[] = [];
  286. records.forEach((item: any) => {
  287. if (item.lengthCountMap && typeof item.lengthCountMap === 'object') {
  288. sizeArr = sizeArr.concat(Object.keys(item.lengthCountMap));
  289. }
  290. });
  291. // 班产统计
  292. const workLength = JSON.parse(statistics.classLengthCountWeight ? statistics.classLengthCountWeight : '{}');
  293. const workLengthKeys = Object.keys(workLength);
  294. const workLengthArr: any[] = [];
  295. if (workLengthKeys.length) {
  296. workLengthKeys.forEach((item) => {
  297. const numW = workLength[item].split('/');
  298. workLengthArr.push({
  299. size: Number(item) / 1000 + 'm',
  300. num: numW[0],
  301. weight: numW[1],
  302. });
  303. });
  304. }
  305. // 日产统计
  306. const dayLength = JSON.parse(statistics.dayLengthCountWeight ? statistics.dayLengthCountWeight : '{}');
  307. const dayLengthKey: any[] = Object.keys(dayLength);
  308. const dayLengthArr: any[] = [];
  309. if (dayLengthKey.length) {
  310. dayLengthKey.forEach((item: any) => {
  311. const numW = dayLength[item].split('/');
  312. dayLengthArr.push({
  313. size: Number(item) / 1000 + 'm',
  314. num: numW[0],
  315. weight: numW[1],
  316. });
  317. });
  318. }
  319. let sizeArrChildren: any[] = [];
  320. [...new Set(sizeArr)].forEach((item) => {
  321. sizeArrChildren.push({
  322. title: item + 'm',
  323. dataIndex: ['lengthCountMap', item],
  324. width: 60,
  325. align: 'center',
  326. customRender({ text }) {
  327. return text || '';
  328. },
  329. });
  330. });
  331. const oldColumns = getColumns();
  332. const sizeColumnIndex = oldColumns.findIndex((item) => item.dataIndex === 'size');
  333. oldColumns[sizeColumnIndex].children = sizeArrChildren;
  334. // 设计表格
  335. const rowNums = records.length < 10 ? 10 : records.length;
  336. const datas = getDefaulData(rowNums + 3).map((item, index) => {
  337. return {
  338. ...item,
  339. ...(records[index] || {}),
  340. dayLength: dayLengthArr[index] || '',
  341. workLength: workLengthArr[index] || '',
  342. };
  343. });
  344. setColumns(oldColumns);
  345. setTableData(datas);
  346. setLoading(false);
  347. } catch (error) {
  348. console.log(error);
  349. setLoading(false);
  350. }
  351. };
  352. // 编辑信息
  353. const editInfo = async (params: any) => {
  354. try {
  355. setLoading(true);
  356. await updateInfo(params);
  357. getList();
  358. } catch (error) {
  359. console.log(error);
  360. setLoading(false);
  361. }
  362. };
  363. // 获取班次信息
  364. const getShiftInfo = async (pageSize: number = 3, date?: Dayjs) => {
  365. const formVals = getFieldsValue();
  366. try {
  367. if (date && !date.isValid()) {
  368. shiftPerformanceColumns.value = [];
  369. currentShift.value = -1;
  370. return false;
  371. }
  372. setLoading(true);
  373. const createTime_begin = date ? date.subtract(1, 'day').format('YYYY-MM-DD 23:55:00') : undefined;
  374. const createTime_end = date ? date.add(1, 'day').format('YYYY-MM-DD 00:05:00') : undefined;
  375. const res = await list({
  376. ccmNo: formVals.ccmNo || '5',
  377. pageNo: 1,
  378. pageSize: pageSize,
  379. column: 'createTime',
  380. order: 'asc',
  381. ...(date ? { createTime_begin, createTime_end } : {}),
  382. });
  383. const { records } = res;
  384. const arr = records || [];
  385. shiftPerformanceColumns.value = arr
  386. .filter((item) => item.createTime) // 过滤掉 createTime 为空的数据
  387. .sort((a, b) => {
  388. const dateA = new Date(a.createTime).getTime(); // 转换为时间戳
  389. const dateB = new Date(b.createTime).getTime(); // 转换为时间戳
  390. if (isNaN(dateA) || isNaN(dateB)) {
  391. console.warn('Invalid date detected:', a.createTime, b.createTime);
  392. return 0; // 如果日期无效,视为相等
  393. }
  394. return dateA - dateB; // 按时间戳排序
  395. });
  396. currentShift.value = shiftPerformanceColumns.value.length > 0 ? shiftPerformanceColumns.value.length - 1 : 0;
  397. // 获取列表
  398. getList();
  399. } catch (error) {
  400. console.error(error);
  401. setLoading(false);
  402. }
  403. };
  404. // 修改备注
  405. const editNotesOpen = ref(false);
  406. const editNotesTxt = ref('');
  407. const editNotesRecord = ref({
  408. heatNo: '',
  409. originalProductRecordId: undefined,
  410. });
  411. const editNotesSubmit = async () => {
  412. if (!editNotesTxt.value) {
  413. createMessage.error('请填写备注');
  414. return;
  415. }
  416. editInfo({
  417. originalProductRecordId: editNotesRecord.value.originalProductRecordId,
  418. notes: editNotesTxt.value,
  419. });
  420. editNotesOpen.value = false;
  421. };
  422. // 确认无误
  423. const confirmedWithoutErrorOpen = ref(false);
  424. // 编辑总备注
  425. const onTotalNoteBlur = async (t?) => {
  426. try {
  427. if (t !== 1 && statisticsInfo.value.orgRemark === statisticsInfo.value.remark) {
  428. return;
  429. }
  430. setLoading(true);
  431. await confirmRecordNote({
  432. id: statisticsInfo.value.id,
  433. remark: statisticsInfo.value.remark, // 备注
  434. });
  435. getList();
  436. confirmedWithoutErrorOpen.value = false;
  437. } catch (error) {
  438. setLoading(false);
  439. }
  440. };
  441. // 切换米重
  442. const switchMiopen = ref(false);
  443. const switchMiNum = ref(0);
  444. const switchMiSubmit = async () => {
  445. try {
  446. await changeMeterWeight({
  447. ccmNo: ccmNo.value,
  448. meterWeight: switchMiNum.value,
  449. });
  450. getList();
  451. switchMiopen.value = false;
  452. } catch (error) {
  453. console.log(error);
  454. }
  455. };
  456. onMounted(() => {
  457. getShiftInfo(3, dayjs('2025-07-07'));
  458. });
  459. </script>
  460. <style lang="less" scoped>
  461. .quality-wrapper {
  462. width: 100%;
  463. height: 100%;
  464. padding: 20px;
  465. overflow: hidden;
  466. background: var(--bg-s-color);
  467. .search-wrapper {
  468. margin-bottom: 10px;
  469. background-color: #005baf;
  470. padding: 20px 20px 0;
  471. border-radius: 4px;
  472. .search-form {
  473. :deep(.btnArea) {
  474. .ant-form-item-row {
  475. width: 100%;
  476. justify-content: flex-end;
  477. }
  478. }
  479. }
  480. :deep(.ant-form) {
  481. .ant-form-item .ant-form-item-label > label {
  482. color: #fff;
  483. }
  484. .ant-col-12 {
  485. width: 100%;
  486. .ant-form-item-row {
  487. .ant-form-item-control {
  488. flex: 1;
  489. max-width: 100%;
  490. }
  491. }
  492. }
  493. }
  494. .shift-performance-tags {
  495. margin-left: 10px;
  496. .ant-tag {
  497. padding: 6px 6px;
  498. position: relative;
  499. cursor: pointer;
  500. margin-bottom: 6px;
  501. font-size: 14px;
  502. }
  503. .current-shift {
  504. position: absolute;
  505. display: inline-block;
  506. width: 60px;
  507. height: 4px;
  508. border-radius: 4px;
  509. bottom: -8px;
  510. left: 50%;
  511. transform: translateX(-30px);
  512. }
  513. }
  514. }
  515. .table-title-info {
  516. width: 100%;
  517. font-size: 16px;
  518. font-weight: 600;
  519. line-height: 24px;
  520. .line_b {
  521. display: inline-block;
  522. min-width: 40px;
  523. height: 24px;
  524. text-align: center;
  525. border-bottom: 1px solid #ebeef5;
  526. padding: 0 10px;
  527. }
  528. .table-title-info-item {
  529. margin-right: 20px;
  530. }
  531. }
  532. .jeecg-basic-table {
  533. :deep(.ant-table-footer) {
  534. padding: 0;
  535. }
  536. :deep(.jeecg-basic-table-header__toolbar) {
  537. width: 0;
  538. }
  539. :deep(.ant-picker) {
  540. color: var(--fn-color);
  541. padding: 0;
  542. input {
  543. color: var(--fn-color);
  544. }
  545. }
  546. :deep(.ant-select) {
  547. .ant-select-selection-item {
  548. color: var(--fn-color);
  549. }
  550. }
  551. }
  552. .footer-tj {
  553. background: var(--bg-color);
  554. color: var(--fn-color);
  555. .remark {
  556. width: 400px;
  557. }
  558. .produce {
  559. flex: 1;
  560. }
  561. :deep(.ant-descriptions-item-label),
  562. :deep(.ant-input) {
  563. color: var(--fn-color);
  564. }
  565. :deep(.ant-descriptions-item-content),
  566. :deep(.ant-descriptions-item-label) {
  567. border-inline-end: 1px solid var(--fn-color);
  568. }
  569. :deep(.ant-descriptions-row) {
  570. border-bottom: 1px solid var(--fn-color);
  571. }
  572. }
  573. }
  574. </style>