You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

794 lines
16 KiB

4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
  1. <template>
  2. <div class="pda-container">
  3. <!-- 头部栏 -->
  4. <div class="header-bar">
  5. <div class="header-left" @click="$router.back()">
  6. <i class="el-icon-arrow-left"></i>
  7. <span>直接领料</span>
  8. </div>
  9. <div class="header-right" @click="$router.push({ path: '/' })">首页</div>
  10. </div>
  11. <!-- 工单号输入 -->
  12. <div class="search-container">
  13. <el-input
  14. clearable
  15. v-model="workOrderNo"
  16. placeholder="请输入工单号"
  17. prefix-icon="el-icon-search"
  18. @keyup.enter.native="handleSearchWorkOrder"
  19. ref="workOrderInput"
  20. />
  21. </div>
  22. <!-- 工单信息卡片列表 -->
  23. <div class="work-order-list" v-if="workOrderList.length > 0">
  24. <div
  25. v-for="(workOrder, index) in displayWorkOrderList"
  26. :key="index"
  27. :class="['work-order-card', { selected: selectedWorkOrder && isSameWorkOrder(selectedWorkOrder, workOrder) }]"
  28. @click="selectWorkOrder(workOrder)"
  29. >
  30. <div class="card-title">
  31. <span class="title-label">工单号{{ workOrder.orderNo }}-{{workOrder.releaseNo}}-{{workOrder.sequenceNo}}</span>
  32. <span class="title-value">{{ workOrder.partNo }}</span>
  33. </div>
  34. <!-- 物料描述单独一行 -->
  35. <div class="part-desc-row">
  36. <span class="desc-text">{{ workOrder.partDesc }}</span>
  37. </div>
  38. <div class="card-details">
  39. <div class="detail-item">
  40. <div class="detail-label">计划数量</div>
  41. <div class="detail-value">{{ workOrder.qtyComplete }}</div>
  42. </div>
  43. <div class="detail-item">
  44. <div class="detail-label">状态</div>
  45. <div class="detail-value">{{ workOrder.status || "进行中" }}</div>
  46. </div>
  47. <div class="detail-item">
  48. <div class="detail-label">单位</div>
  49. <div class="detail-value">{{ workOrder.uom || "个" }}</div>
  50. </div>
  51. </div>
  52. </div>
  53. </div>
  54. <!-- 材料列表 -->
  55. <div
  56. class="content-area"
  57. v-if="selectedWorkOrder && materialList.length > 0"
  58. >
  59. <div
  60. v-for="(material, index) in materialList"
  61. :key="index"
  62. class="material-card"
  63. @click="selectMaterial(material)"
  64. >
  65. <div class="card-title">
  66. <span class="title-label"
  67. >物料编码{{ material.componentPartNo }} &nbsp;&nbsp; 行号{{
  68. material.lineItemNo
  69. }}</span
  70. >
  71. <!-- <span class="title-value">{{ material.componentPartNo }}</span> -->
  72. </div>
  73. <!-- 物料描述单独一行 -->
  74. <div class="part-desc-row">
  75. <span class="desc-text">{{ material.componentPartDesc }}</span>
  76. </div>
  77. <div class="card-details">
  78. <div class="detail-item">
  79. <div class="detail-label">需求数量</div>
  80. <div class="detail-value">{{ material.qtyRequired }}</div>
  81. </div>
  82. <div class="detail-item">
  83. <div class="detail-label">已发数量</div>
  84. <div class="detail-value">{{ material.qtyIssued || 0 }}</div>
  85. </div>
  86. <div class="detail-item">
  87. <div class="detail-label">单位</div>
  88. <div class="detail-value">{{ material.uom || "个" }}</div>
  89. </div>
  90. </div>
  91. </div>
  92. </div>
  93. <!-- 空状态 -->
  94. <div
  95. v-if="selectedWorkOrder && materialList.length === 0"
  96. class="empty-state"
  97. >
  98. <i class="el-icon-box"></i>
  99. <p>该工单暂无材料清单</p>
  100. </div>
  101. <!-- 加载状态 -->
  102. <div v-if="loading" class="loading-state">
  103. <i class="el-icon-loading"></i>
  104. <p>加载中...</p>
  105. </div>
  106. </div>
  107. </template>
  108. <script>
  109. import {
  110. getWorkOrderInfo,
  111. getWorkOrderMaterials,
  112. } from "@/api/production/production-issue";
  113. import moment from "moment";
  114. export default {
  115. data() {
  116. return {
  117. workOrderNo: "",
  118. workOrderList: [],
  119. selectedWorkOrder: null,
  120. materialList: [],
  121. selectedMaterial: null,
  122. loading: false,
  123. showOnlySelected: false,
  124. };
  125. },
  126. computed: {
  127. displayWorkOrderList() {
  128. if (this.showOnlySelected && this.selectedWorkOrder) {
  129. return [this.selectedWorkOrder];
  130. }
  131. return this.workOrderList;
  132. },
  133. },
  134. methods: {
  135. formatDate(date) {
  136. return date ? moment(date).format("YYYY-MM-DD") : "";
  137. },
  138. // 查询工单信息
  139. handleSearchWorkOrder() {
  140. if (!this.workOrderNo.trim()) {
  141. this.$message.warning("请输入工单号");
  142. return;
  143. }
  144. this.loading = true;
  145. const params = {
  146. workOrderNo: this.workOrderNo.trim(),
  147. site: localStorage.getItem('site'),
  148. };
  149. getWorkOrderInfo(params)
  150. .then(({ data }) => {
  151. this.loading = false;
  152. console.log("工单信息", data);
  153. if (
  154. data.workOrders &&
  155. data.workOrders.length > 0 &&
  156. data.code === 0
  157. ) {
  158. this.workOrderList = data.workOrders;
  159. this.selectedWorkOrder = null;
  160. this.materialList = [];
  161. this.showOnlySelected = false;
  162. } else {
  163. this.$message.error("未找到该工单信息");
  164. this.workOrderList = [];
  165. this.selectedWorkOrder = null;
  166. this.materialList = [];
  167. this.showOnlySelected = false;
  168. }
  169. })
  170. .catch((error) => {
  171. this.loading = false;
  172. console.error("查询工单信息失败:", error);
  173. this.$message.error("查询工单信息失败");
  174. });
  175. },
  176. // 判断是否为同一工单
  177. isSameWorkOrder(a, b) {
  178. if (!a || !b) return false;
  179. return (
  180. a.orderNo === b.orderNo &&
  181. a.releaseNo === b.releaseNo &&
  182. a.sequenceNo === b.sequenceNo
  183. );
  184. },
  185. // 选择工单(支持再次点击切换显示所有)
  186. selectWorkOrder(workOrder) {
  187. if (
  188. this.showOnlySelected &&
  189. this.selectedWorkOrder &&
  190. this.isSameWorkOrder(this.selectedWorkOrder, workOrder)
  191. ) {
  192. // 再次点击同一条,恢复显示所有主数据
  193. this.selectedWorkOrder = null;
  194. this.materialList = [];
  195. this.showOnlySelected = false;
  196. return;
  197. }
  198. // 选择并仅显示当前工单
  199. this.selectedWorkOrder = workOrder;
  200. this.showOnlySelected = true;
  201. this.loadMaterialList();
  202. },
  203. // 加载材料清单
  204. loadMaterialList() {
  205. if (!this.selectedWorkOrder) {
  206. this.materialList = [];
  207. return;
  208. }
  209. const params = {
  210. workOrderNo: this.selectedWorkOrder.orderNo,
  211. site: localStorage.getItem('site'),
  212. };
  213. getWorkOrderMaterials(params)
  214. .then(({ data }) => {
  215. console.log("材料清单", data);
  216. if (data && data.code === 0) {
  217. this.materialList = (data.materials || []).map((item, index) => ({
  218. ...item,
  219. id: index + 1,
  220. }));
  221. } else {
  222. this.$message.error(data.msg || "获取材料清单失败");
  223. this.materialList = [];
  224. }
  225. })
  226. .catch((error) => {
  227. console.error("获取材料清单失败:", error);
  228. this.$message.error("获取材料清单失败");
  229. });
  230. },
  231. // 选择材料,跳转到扫描明细页
  232. selectMaterial(material) {
  233. console.log("选择材料", material);
  234. if(material.reserveIssueMethod != 'Reserve And Backflush'){
  235. this.$message.warning('该物料为'+material.reserveIssueMethod+',不支持直接领料,请选择其他物料!');
  236. return;
  237. }
  238. this.$router.push({
  239. name: "directIssueDetail",
  240. params: {
  241. workOrderNo: this.selectedWorkOrder.orderNo,
  242. partNo: material.componentPartNo,
  243. itemNo: material.lineItemNo,
  244. issueInfo:{
  245. releaseNo:this.selectedWorkOrder.releaseNo,
  246. sequenceNo:this.selectedWorkOrder.sequenceNo,
  247. requiredQty: material.qtyRequired,
  248. issuedQty: material.qtyIssued || 0,
  249. partDesc: material.componentPartDesc,
  250. }
  251. },
  252. });
  253. },
  254. },
  255. mounted() {
  256. // 聚焦工单号输入框
  257. this.$nextTick(() => {
  258. if (this.$refs.workOrderInput) {
  259. this.$refs.workOrderInput.focus();
  260. }
  261. });
  262. },
  263. };
  264. </script>
  265. <style scoped>
  266. .pda-container {
  267. width: 100vw;
  268. height: 100vh;
  269. display: flex;
  270. flex-direction: column;
  271. background: #f5f5f5;
  272. }
  273. /* 头部栏 */
  274. .header-bar {
  275. display: flex;
  276. justify-content: space-between;
  277. align-items: center;
  278. padding: 8px 16px;
  279. background: #17b3a3;
  280. color: white;
  281. height: 40px;
  282. min-height: 40px;
  283. }
  284. .header-left {
  285. display: flex;
  286. align-items: center;
  287. cursor: pointer;
  288. font-size: 16px;
  289. font-weight: 500;
  290. }
  291. .header-left i {
  292. margin-right: 8px;
  293. font-size: 18px;
  294. }
  295. .header-right {
  296. cursor: pointer;
  297. font-size: 16px;
  298. font-weight: 500;
  299. }
  300. /* 搜索容器 */
  301. .search-container {
  302. padding: 12px 16px;
  303. background: white;
  304. display: flex;
  305. align-items: center;
  306. gap: 12px;
  307. }
  308. .search-container .el-input {
  309. flex: 1;
  310. }
  311. .search-btn {
  312. padding: 8px 16px;
  313. background: #17b3a3;
  314. color: white;
  315. border: none;
  316. border-radius: 4px;
  317. cursor: pointer;
  318. font-size: 14px;
  319. transition: background-color 0.2s;
  320. }
  321. .search-btn:hover {
  322. background: #0d8f7f;
  323. }
  324. .search-btn:disabled {
  325. background: #ccc;
  326. cursor: not-allowed;
  327. }
  328. /* 工单列表 */
  329. .work-order-list {
  330. overflow-y: auto;
  331. padding: 12px 16px;
  332. }
  333. /* 工单卡片 */
  334. .work-order-card {
  335. background: white;
  336. border-radius: 8px;
  337. margin-bottom: 12px;
  338. padding: 16px;
  339. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  340. cursor: pointer;
  341. transition: all 0.2s ease;
  342. border: 2px solid transparent;
  343. }
  344. .work-order-card:hover {
  345. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  346. transform: translateY(-1px);
  347. }
  348. .work-order-card.selected {
  349. border-color: #17b3a3;
  350. background: #f0fffe;
  351. }
  352. .work-order-card:active {
  353. transform: translateY(0);
  354. }
  355. /* 内容区域 */
  356. .content-area {
  357. flex: 1;
  358. overflow-y: auto;
  359. padding: 12px 16px;
  360. }
  361. /* 材料卡片 */
  362. .material-card {
  363. background: white;
  364. border-radius: 8px;
  365. margin-bottom: 12px;
  366. padding: 16px;
  367. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  368. cursor: pointer;
  369. transition: all 0.2s ease;
  370. border: 2px solid transparent;
  371. }
  372. .material-card:hover {
  373. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  374. transform: translateY(-1px);
  375. }
  376. .material-card.selected {
  377. border-color: #17b3a3;
  378. background: #f0fffe;
  379. }
  380. .material-card:active {
  381. transform: translateY(0);
  382. }
  383. /* 卡片标题 */
  384. .card-title {
  385. margin-bottom: 12px;
  386. }
  387. .title-label {
  388. font-size: 12px;
  389. color: #666;
  390. display: block;
  391. margin-bottom: 4px;
  392. }
  393. .title-value {
  394. font-size: 16px;
  395. font-weight: bold;
  396. color: #333;
  397. margin-left: 16px;
  398. }
  399. /* 物料描述行 */
  400. .part-desc-row {
  401. margin-bottom: 12px;
  402. padding: 0 4px;
  403. }
  404. .desc-text {
  405. font-size: 12px;
  406. color: #666;
  407. line-height: 1.3;
  408. word-break: break-all;
  409. }
  410. /* 卡片详情 */
  411. .card-details {
  412. display: flex;
  413. justify-content: space-between;
  414. align-items: flex-start;
  415. gap: 4px;
  416. }
  417. .detail-item {
  418. flex: 1;
  419. text-align: center;
  420. min-width: 50px;
  421. max-width: 70px;
  422. }
  423. .detail-label {
  424. font-size: 11px;
  425. color: #666;
  426. margin-bottom: 4px;
  427. line-height: 1.2;
  428. margin-left: -12px;
  429. }
  430. .detail-value {
  431. font-size: 13px;
  432. color: #333;
  433. line-height: 1.2;
  434. margin-left: -12px;
  435. }
  436. /* 扫描区域 */
  437. .scan-section {
  438. margin-top: 16px;
  439. }
  440. /* 扫描容器 */
  441. .scan-container {
  442. padding: 12px 16px;
  443. background: white;
  444. display: flex;
  445. align-items: center;
  446. gap: 12px;
  447. border-radius: 0 0 8px 8px;
  448. margin-bottom: 12px;
  449. }
  450. .scan-container .el-input {
  451. width: 240px;
  452. margin-right: 12px;
  453. }
  454. /* 紧凑型输入框样式 */
  455. .compact-input ::v-deep .el-input__inner {
  456. height: 36px;
  457. padding: 0 12px 0 35px;
  458. font-size: 14px;
  459. }
  460. .compact-input ::v-deep .el-input__prefix {
  461. left: 10px;
  462. }
  463. .compact-input ::v-deep .el-input__suffix {
  464. right: 30px;
  465. }
  466. /* 模式切换开关 */
  467. .mode-switch {
  468. position: relative;
  469. display: inline-block;
  470. }
  471. .custom-switch {
  472. transform: scale(1.3);
  473. }
  474. /* 中间文字 */
  475. .switch-text {
  476. position: absolute;
  477. left: 25%;
  478. top: 53%;
  479. transform: translate(-50%, -50%);
  480. font-size: 12px;
  481. font-weight: bold;
  482. color: white;
  483. pointer-events: none;
  484. z-index: 2;
  485. }
  486. .switch-text2 {
  487. position: absolute;
  488. left: 75%;
  489. top: 53%;
  490. transform: translate(-50%, -50%);
  491. font-size: 12px;
  492. font-weight: bold;
  493. color: white;
  494. pointer-events: none;
  495. z-index: 2;
  496. }
  497. /* 调整 switch 尺寸以便容纳文字 */
  498. .custom-switch ::v-deep .el-switch__core {
  499. width: 60px;
  500. height: 28px;
  501. }
  502. /* 已选材料信息 */
  503. .selected-material-info {
  504. background: #e8f5e8;
  505. padding: 12px 16px;
  506. margin-bottom: 12px;
  507. border-radius: 8px;
  508. border-left: 4px solid #17b3a3;
  509. }
  510. .info-title {
  511. font-size: 14px;
  512. font-weight: bold;
  513. color: #333;
  514. margin-bottom: 8px;
  515. }
  516. .info-details {
  517. display: flex;
  518. gap: 16px;
  519. font-size: 12px;
  520. color: #666;
  521. }
  522. /* 标签列表 */
  523. .label-list {
  524. background: white;
  525. margin-bottom: 12px;
  526. border-radius: 8px;
  527. overflow: hidden;
  528. }
  529. .list-header {
  530. display: flex;
  531. background: #f8f9fa;
  532. padding: 12px 8px;
  533. border-bottom: 1px solid #e0e0e0;
  534. font-size: 12px;
  535. color: #666;
  536. font-weight: 500;
  537. }
  538. .list-item {
  539. display: flex;
  540. padding: 12px 8px;
  541. border-bottom: 1px solid #f0f0f0;
  542. font-size: 12px;
  543. color: #333;
  544. }
  545. .list-item:last-child {
  546. border-bottom: none;
  547. }
  548. .col-no {
  549. width: 30px;
  550. text-align: center;
  551. }
  552. .col-label {
  553. flex: 2;
  554. text-align: center;
  555. }
  556. .col-batch {
  557. flex: 1;
  558. text-align: center;
  559. }
  560. .col-qty {
  561. width: 60px;
  562. text-align: center;
  563. }
  564. .empty-labels {
  565. padding: 40px 20px;
  566. text-align: center;
  567. color: #999;
  568. }
  569. .empty-labels p {
  570. margin: 0;
  571. font-size: 14px;
  572. }
  573. /* 底部操作按钮 */
  574. .bottom-actions {
  575. display: flex;
  576. padding: 16px;
  577. gap: 20px;
  578. background: white;
  579. border-radius: 8px;
  580. }
  581. .action-btn {
  582. flex: 1;
  583. padding: 12px;
  584. border: 1px solid #17b3a3;
  585. background: white;
  586. color: #17b3a3;
  587. border-radius: 20px;
  588. font-size: 14px;
  589. cursor: pointer;
  590. transition: all 0.2s ease;
  591. }
  592. .action-btn.primary {
  593. background: #17b3a3;
  594. color: white;
  595. }
  596. .action-btn.primary:hover {
  597. background: #0d8f7f;
  598. }
  599. .action-btn.secondary:hover {
  600. background: #17b3a3;
  601. color: white;
  602. }
  603. .action-btn:active {
  604. transform: scale(0.98);
  605. }
  606. .action-btn:disabled {
  607. opacity: 0.5;
  608. cursor: not-allowed;
  609. }
  610. .action-btn:disabled:hover {
  611. background: #17b3a3;
  612. color: white;
  613. }
  614. /* 空状态 */
  615. .empty-state {
  616. display: flex;
  617. flex-direction: column;
  618. align-items: center;
  619. justify-content: center;
  620. padding: 60px 20px;
  621. color: #999;
  622. }
  623. .empty-state i {
  624. font-size: 48px;
  625. margin-bottom: 16px;
  626. }
  627. .empty-state p {
  628. font-size: 14px;
  629. margin: 0;
  630. }
  631. /* 加载状态 */
  632. .loading-state {
  633. display: flex;
  634. flex-direction: column;
  635. align-items: center;
  636. justify-content: center;
  637. padding: 60px 20px;
  638. color: #17b3a3;
  639. }
  640. .loading-state i {
  641. font-size: 24px;
  642. margin-bottom: 12px;
  643. animation: spin 1s linear infinite;
  644. }
  645. @keyframes spin {
  646. from {
  647. transform: rotate(0deg);
  648. }
  649. to {
  650. transform: rotate(360deg);
  651. }
  652. }
  653. .loading-state p {
  654. font-size: 14px;
  655. margin: 0;
  656. }
  657. /* 响应式设计 */
  658. @media (max-width: 360px) {
  659. .header-bar {
  660. padding: 8px 12px;
  661. }
  662. .search-container {
  663. padding: 8px 12px;
  664. }
  665. .work-order-list {
  666. padding: 8px 12px;
  667. }
  668. .work-order-card {
  669. padding: 12px;
  670. }
  671. .content-area {
  672. padding: 8px 12px;
  673. }
  674. .material-card {
  675. padding: 12px;
  676. }
  677. .card-details {
  678. flex-wrap: wrap;
  679. gap: 6px;
  680. }
  681. .detail-item {
  682. flex: 0 0 48%;
  683. margin-bottom: 6px;
  684. min-width: 50px;
  685. }
  686. .list-header,
  687. .list-item {
  688. font-size: 11px;
  689. }
  690. .col-label {
  691. flex: 1.5;
  692. }
  693. }
  694. </style>