index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <template>
  2. <div class="tenant">
  3. <div class="content">
  4. <byTable
  5. :source="sourceList.data"
  6. :pagination="sourceList.pagination"
  7. :config="config"
  8. :loading="loading"
  9. highlight-current-row
  10. :selectConfig="selectConfig"
  11. :table-events="{
  12. select: select,
  13. }"
  14. :action-list="[
  15. {
  16. text: '添加任务',
  17. action: () => openModal('add'),
  18. },
  19. ]"
  20. @get-list="getList"
  21. >
  22. </byTable>
  23. </div>
  24. <el-dialog
  25. :title="modalType == 'add' ? '添加任务' : '查看任务'"
  26. v-model="dialogVisible"
  27. width="700"
  28. v-loading="loading"
  29. destroy-on-close
  30. :before-close="handleClose"
  31. >
  32. <byForm
  33. :formConfig="formConfig"
  34. :formOption="formOption"
  35. v-model="formData.data"
  36. :rules="rules"
  37. ref="byform"
  38. v-if="modalType == 'add'"
  39. >
  40. <template
  41. v-for="(cur, index) in productionProcessesList"
  42. :key="cur.id"
  43. v-slot:[cur.id]="{ item }"
  44. >
  45. <el-select
  46. v-model="productionObj[cur.id]"
  47. multiple
  48. placeholder="请选择负责人"
  49. >
  50. <el-option
  51. v-for="item in userList"
  52. :key="item.value"
  53. :label="item.label"
  54. :value="item.value"
  55. />
  56. </el-select>
  57. </template>
  58. </byForm>
  59. <byForm
  60. :formConfig="formConfigOne"
  61. :formOption="formOptionOne"
  62. v-model="formData.dataOne"
  63. ref="byformOne"
  64. v-else
  65. >
  66. <template #list>
  67. <div style="width: 100%">
  68. <el-table :data="formData.dataOne.productionTaskDetailList">
  69. <el-table-column prop="productSn" label="产品SN" />
  70. <el-table-column
  71. prop="productionProcessesName"
  72. label="当前工序"
  73. />
  74. <el-table-column prop="cumulativeTime" label="累计耗时" />
  75. <el-table-column
  76. label="工序状态"
  77. prop="processesStatus"
  78. :formatter="
  79. (row) =>
  80. dictValueLabel(row.processesStatus, processesStatusData)
  81. "
  82. />
  83. </el-table>
  84. </div>
  85. </template>
  86. </byForm>
  87. <template #footer>
  88. <el-button @click="handleClose" size="large">取 消</el-button>
  89. <el-button
  90. type="primary"
  91. @click="submitForm()"
  92. size="large"
  93. :loading="submitLoading"
  94. v-if="modalType == 'add'"
  95. >
  96. 确 定
  97. </el-button>
  98. </template>
  99. </el-dialog>
  100. <el-dialog
  101. title="打印"
  102. v-if="printDialog"
  103. v-model="printDialog"
  104. width="500"
  105. >
  106. <div
  107. style="height: calc(100vh - 300px); overflow: auto; padding-right: 20px"
  108. >
  109. <div id="pdfDom">
  110. <div
  111. v-for="item in qrList"
  112. :key="item.productSn"
  113. style="border: 1px solid #000; padding: 10px; margin-bottom: 20px"
  114. >
  115. <div style="display: flex">
  116. <div :ref="item.productSn"></div>
  117. <div
  118. style="
  119. margin-left: 20px;
  120. display: flex;
  121. padding: 10px 0;
  122. flex-direction: column;
  123. justify-content: space-between;
  124. "
  125. >
  126. <div class="print-row">产品:{{ printData.productName }}</div>
  127. <div class="print-row">客户:{{ printData.customerName }}</div>
  128. </div>
  129. </div>
  130. <div
  131. style="
  132. font-size: 16px;
  133. font-weight: 700;
  134. color: #000;
  135. margin-top: 10px;
  136. "
  137. >
  138. {{ item.productSn }}
  139. </div>
  140. <!-- 换页 -->
  141. <div style="page-break-after: always"></div>
  142. </div>
  143. </div>
  144. </div>
  145. <template #footer>
  146. <el-button @click="printDialog = false" size="large">取 消</el-button>
  147. <el-button type="primary" v-print="printObj" size="large"
  148. >打 印</el-button
  149. >
  150. </template>
  151. </el-dialog>
  152. </div>
  153. </template>
  154. <script setup>
  155. import { ElMessage, ElMessageBox } from "element-plus";
  156. import byTable from "@/components/byTable/index";
  157. import byForm from "@/components/byForm/index";
  158. import useUserStore from "@/store/modules/user";
  159. import QRCode from "qrcodejs2-fix";
  160. const { proxy } = getCurrentInstance();
  161. const loading = ref(false);
  162. const submitLoading = ref(false);
  163. const sourceList = ref({
  164. data: [],
  165. pagination: {
  166. total: 3,
  167. pageNum: 1,
  168. pageSize: 10,
  169. keyword: "",
  170. },
  171. });
  172. const processesStatusData = ref([
  173. {
  174. label: "未开始",
  175. value: "0",
  176. },
  177. {
  178. label: "进行中",
  179. value: "1",
  180. },
  181. {
  182. label: "驳回",
  183. value: "2",
  184. },
  185. {
  186. label: "完成",
  187. value: "3",
  188. },
  189. ]);
  190. const dialogVisible = ref(false);
  191. const modalType = ref("add");
  192. const rules = ref({
  193. productionPlanId: [
  194. { required: true, message: "请选择生产计划", trigger: "change" },
  195. ],
  196. dueDate: [{ required: true, message: "请选择完成期限", trigger: "change" }],
  197. quantity: [{ required: true, message: "请输入生产数量", trigger: "blur" }],
  198. });
  199. const selectConfig = reactive([]);
  200. const config = computed(() => {
  201. return [
  202. {
  203. attrs: {
  204. label: "计划单号",
  205. prop: "productionPlanCode",
  206. },
  207. },
  208. {
  209. attrs: {
  210. label: "任务编码",
  211. prop: "code",
  212. },
  213. },
  214. {
  215. attrs: {
  216. label: "产品名称",
  217. prop: "productName",
  218. },
  219. },
  220. {
  221. attrs: {
  222. label: "规格型号",
  223. prop: "productSpec",
  224. },
  225. },
  226. {
  227. attrs: {
  228. label: "任务数量",
  229. prop: "quantity",
  230. },
  231. },
  232. {
  233. attrs: {
  234. label: "完成期限",
  235. prop: "dueDate",
  236. },
  237. },
  238. {
  239. attrs: {
  240. label: "操作",
  241. width: "120",
  242. align: "center",
  243. },
  244. renderHTML(row) {
  245. return [
  246. {
  247. attrs: {
  248. label: "查看",
  249. type: "primary",
  250. text: true,
  251. },
  252. el: "button",
  253. click() {
  254. getDtl(row);
  255. },
  256. },
  257. {
  258. attrs: {
  259. label: "打印",
  260. type: "primary",
  261. text: true,
  262. },
  263. el: "button",
  264. click() {
  265. handlePrint(row);
  266. },
  267. },
  268. ];
  269. },
  270. },
  271. ];
  272. });
  273. const formData = reactive({
  274. data: {},
  275. dataOne: {},
  276. });
  277. const formOption = reactive({
  278. inline: true,
  279. labelWidth: 100,
  280. itemWidth: 100,
  281. rules: [],
  282. });
  283. const formOptionOne = reactive({
  284. inline: true,
  285. labelWidth: 100,
  286. itemWidth: 100,
  287. rules: [],
  288. });
  289. const byform = ref(null);
  290. const formConfig = computed(() => {
  291. return [
  292. {
  293. type: "select",
  294. prop: "productionPlanId",
  295. label: "生产计划",
  296. required: true,
  297. filterable: true,
  298. data: planData.value,
  299. fn: (val) => {
  300. changeFn(val);
  301. },
  302. style: {
  303. width: "50%",
  304. },
  305. },
  306. {
  307. type: "input",
  308. itemType: "text",
  309. prop: "productName",
  310. label: "产品名称",
  311. disabled: true,
  312. style: {
  313. width: "50%",
  314. },
  315. },
  316. {
  317. type: "input",
  318. itemType: "text",
  319. prop: "productSpec",
  320. label: "规格型号",
  321. disabled: true,
  322. style: {
  323. width: "50%",
  324. },
  325. },
  326. {
  327. type: "input",
  328. itemType: "text",
  329. prop: "waitQuantity",
  330. label: "待排程数量",
  331. disabled: true,
  332. style: {
  333. width: "50%",
  334. },
  335. },
  336. {
  337. type: "number",
  338. prop: "quantity",
  339. label: "任务数量",
  340. precision: 0,
  341. min: 1,
  342. controls: false,
  343. style: {
  344. width: "50%",
  345. },
  346. },
  347. {
  348. type: "date",
  349. itemType: "date",
  350. prop: "dueDate",
  351. label: "完成期限",
  352. required: true,
  353. style: {
  354. width: "50%",
  355. },
  356. },
  357. ];
  358. });
  359. const formConfigOne = computed(() => {
  360. return [
  361. {
  362. type: "select",
  363. prop: "productionPlanId",
  364. label: "生产计划",
  365. required: true,
  366. filterable: true,
  367. data: planDataOne.value,
  368. fn: (val) => {
  369. changeFn(val);
  370. },
  371. style: {
  372. width: "50%",
  373. },
  374. },
  375. {
  376. type: "input",
  377. itemType: "text",
  378. prop: "productName",
  379. label: "产品名称",
  380. disabled: true,
  381. style: {
  382. width: "50%",
  383. },
  384. },
  385. {
  386. type: "input",
  387. itemType: "text",
  388. prop: "productSpec",
  389. label: "规格型号",
  390. disabled: true,
  391. style: {
  392. width: "50%",
  393. },
  394. },
  395. {
  396. type: "input",
  397. itemType: "text",
  398. prop: "waitQuantity",
  399. label: "待排程数量",
  400. disabled: true,
  401. style: {
  402. width: "50%",
  403. },
  404. isShow: modalType.value == "add",
  405. },
  406. {
  407. type: "number",
  408. prop: "quantity",
  409. label: "任务数量",
  410. precision: 0,
  411. min: 1,
  412. controls: false,
  413. style: {
  414. width: "50%",
  415. },
  416. },
  417. {
  418. type: "date",
  419. itemType: "date",
  420. prop: "dueDate",
  421. label: "完成期限",
  422. required: true,
  423. style: {
  424. width: "50%",
  425. },
  426. },
  427. {
  428. type: "slot",
  429. slotName: "list",
  430. label: "产品明细",
  431. },
  432. ];
  433. });
  434. const getList = async (req) => {
  435. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  436. loading.value = true;
  437. proxy
  438. .post("/productionTask/page", sourceList.value.pagination)
  439. .then((message) => {
  440. sourceList.value.data = message.rows;
  441. sourceList.value.pagination.total = message.total;
  442. setTimeout(() => {
  443. loading.value = false;
  444. }, 200);
  445. });
  446. };
  447. const openModal = () => {
  448. dialogVisible.value = true;
  449. modalType.value = "add";
  450. formOption.disabled = false;
  451. formData.data = {};
  452. };
  453. const submitForm = () => {
  454. byform.value.handleSubmit(() => {
  455. if (Number(formData.data.quantity) > Number(formData.data.waitQuantity)) {
  456. return ElMessage({
  457. message: "任务数量不可大于待排程数量",
  458. type: "info",
  459. });
  460. }
  461. // if (proxy.compareTime(formData.data.startDate, formData.data.dueDate)) {
  462. // return ElMessage({
  463. // message: "完成期限不可小于计划开始时间",
  464. // type: "info",
  465. // });
  466. // }
  467. for (const key in productionObj.value) {
  468. if (productionObj.value[key] && productionObj.value[key].length > 0) {
  469. } else {
  470. return ElMessage({
  471. message: "所有工序都需选择负责人!",
  472. type: "info",
  473. });
  474. }
  475. }
  476. formData.data.taskProcessesUser = JSON.stringify(productionObj.value);
  477. submitLoading.value = true;
  478. proxy.post("/productionTask/addByJxst", formData.data).then(
  479. () => {
  480. ElMessage({
  481. message: "添加成功",
  482. type: "success",
  483. });
  484. handleClose();
  485. submitLoading.value = false;
  486. getList();
  487. getDict();
  488. },
  489. (err) => {
  490. console.log(err);
  491. submitLoading.value = false;
  492. }
  493. );
  494. });
  495. };
  496. const getDtl = (row) => {
  497. modalType.value = "detail";
  498. formOptionOne.disabled = true;
  499. proxy.post("/productionTask/detailByJxst", { id: row.id }).then((res) => {
  500. formData.dataOne = res;
  501. formData.dataOne.waitQuantity = res.remainingQuantity;
  502. dialogVisible.value = true;
  503. });
  504. };
  505. const userList = ref([]);
  506. const planData = ref([]);
  507. const planDataOne = ref([]);
  508. const statusData = ref([
  509. {
  510. label: "未开始",
  511. value: "0",
  512. },
  513. {
  514. label: "进行中",
  515. value: "1",
  516. },
  517. {
  518. label: "完成",
  519. value: "2",
  520. },
  521. ]);
  522. const getDict = () => {
  523. proxy
  524. .get("/tenantUser/list", {
  525. pageNum: 1,
  526. pageSize: 10000,
  527. tenantId: useUserStore().user.tenantId,
  528. })
  529. .then((res) => {
  530. userList.value = res.rows.map((item) => {
  531. return {
  532. label: item.nickName,
  533. value: item.userId,
  534. };
  535. });
  536. });
  537. proxy
  538. .post("/productionPlan/page", {
  539. pageNum: 1,
  540. pageSize: 9999,
  541. isRemaining: "1",
  542. })
  543. .then((res) => {
  544. planData.value = res.rows.map((x) => ({
  545. label: x.code + `(${x.startDate}-${x.stopDate})`,
  546. value: x.id,
  547. ...x,
  548. }));
  549. });
  550. proxy
  551. .post("/productionPlan/page", {
  552. pageNum: 1,
  553. pageSize: 9999,
  554. })
  555. .then((res) => {
  556. planDataOne.value = res.rows.map((x) => ({
  557. label: x.code,
  558. value: x.id,
  559. ...x,
  560. }));
  561. });
  562. };
  563. getDict();
  564. getList();
  565. const changeId = ref("");
  566. const changeFn = (val) => {
  567. const current =
  568. modalType.value == "add"
  569. ? planData.value.find((x) => x.value == val)
  570. : planDataOne.value.find((x) => x.value == val);
  571. if (current) {
  572. formData.data.productName = current.productName;
  573. formData.data.productSpec = current.productSpec;
  574. formData.data.waitQuantity = current.remainingQuantity;
  575. }
  576. if (
  577. productionProcessesList.value &&
  578. productionProcessesList.value.length > 0
  579. ) {
  580. // changeId.value = productionProcessesList.value[0].id;
  581. // const index = formConfig.value.findIndex(
  582. // (x) => x.slotName === changeId.value
  583. // );
  584. formConfig.value.splice(6, productionProcessesList.value.length);
  585. productionProcessesList.value = [];
  586. productionObj.value = {};
  587. }
  588. getProductionDetails(val);
  589. };
  590. const productionProcessesList = ref([]);
  591. const productionObj = ref({});
  592. const getProductionDetails = (id) => {
  593. proxy.post("/productionPlan/detail", { id }).then((res) => {
  594. productionProcessesList.value = res.productionProcessesList;
  595. for (let i = 0; i < productionProcessesList.value.length; i++) {
  596. const e = productionProcessesList.value[i];
  597. formConfig.value.push({
  598. type: "slot",
  599. label: e.name + " 负责人",
  600. slotName: e.id,
  601. });
  602. productionObj.value[e.id] = [];
  603. }
  604. });
  605. };
  606. const handleClose = () => {
  607. if (
  608. productionProcessesList.value &&
  609. productionProcessesList.value.length > 0
  610. ) {
  611. formConfig.value.splice(6, productionProcessesList.value.length);
  612. }
  613. productionProcessesList.value = [];
  614. productionObj.value = {};
  615. dialogVisible.value = false;
  616. };
  617. const printDialog = ref(false);
  618. const printData = ref({});
  619. const qrList = ref([]);
  620. const handlePrint = (row) => {
  621. const { code, quantity } = row;
  622. const arr = [];
  623. for (let i = 0; i < quantity; i++) {
  624. let key = i + 1 + "";
  625. key = key.padStart(3, "0");
  626. let obj = {
  627. productSn: code + "-" + key,
  628. };
  629. arr.push(obj);
  630. }
  631. qrList.value = arr;
  632. printData.value = {
  633. customerName: row.customerName,
  634. productName: row.productName + `(${row.productSpec})`,
  635. };
  636. printDialog.value = true;
  637. nextTick(() => {
  638. for (let i = 0; i < qrList.value.length; i++) {
  639. const ele = qrList.value[i];
  640. proxy.$refs[ele.productSn][0].innerHTML = ""; //清除二维码方法一
  641. new QRCode(proxy.$refs[ele.productSn][0], {
  642. text: ele.productSn, //页面地址 ,如果页面需要参数传递请注意哈希模式#
  643. width: 100,
  644. height: 100,
  645. colorDark: "#000000",
  646. colorLight: "#ffffff",
  647. correctLevel: QRCode.CorrectLevel.H,
  648. });
  649. }
  650. });
  651. };
  652. const printObj = ref({
  653. id: "pdfDom",
  654. popTitle: "",
  655. extraCss:
  656. "https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.compat.css, https://cdn.bootcdn.net/ajax/libs/hover.css/2.3.1/css/hover-min.css",
  657. extraHead: '<meta http-equiv="Content-Language"content="zh-cn"/>',
  658. });
  659. </script>
  660. <style lang="scss" scoped>
  661. .tenant {
  662. padding: 20px;
  663. }
  664. ::v-deep(.el-input-number .el-input__inner) {
  665. text-align: left;
  666. }
  667. .print-row {
  668. font-size: 14px;
  669. font-weight: 700;
  670. color: #000;
  671. }
  672. </style>