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.

790 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
  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: this.$store.state.user.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: this.$store.state.user.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+'-'+this.selectedWorkOrder.releaseNo+'-'+this.selectedWorkOrder.sequenceNo,
  242. partNo: material.componentPartNo,
  243. partDesc: material.componentPartDesc,
  244. requiredQty: material.qtyRequired,
  245. issuedQty: material.qtyIssued || 0,
  246. },
  247. });
  248. },
  249. },
  250. mounted() {
  251. // 聚焦工单号输入框
  252. this.$nextTick(() => {
  253. if (this.$refs.workOrderInput) {
  254. this.$refs.workOrderInput.focus();
  255. }
  256. });
  257. },
  258. };
  259. </script>
  260. <style scoped>
  261. .pda-container {
  262. width: 100vw;
  263. height: 100vh;
  264. display: flex;
  265. flex-direction: column;
  266. background: #f5f5f5;
  267. }
  268. /* 头部栏 */
  269. .header-bar {
  270. display: flex;
  271. justify-content: space-between;
  272. align-items: center;
  273. padding: 8px 16px;
  274. background: #17b3a3;
  275. color: white;
  276. height: 40px;
  277. min-height: 40px;
  278. }
  279. .header-left {
  280. display: flex;
  281. align-items: center;
  282. cursor: pointer;
  283. font-size: 16px;
  284. font-weight: 500;
  285. }
  286. .header-left i {
  287. margin-right: 8px;
  288. font-size: 18px;
  289. }
  290. .header-right {
  291. cursor: pointer;
  292. font-size: 16px;
  293. font-weight: 500;
  294. }
  295. /* 搜索容器 */
  296. .search-container {
  297. padding: 12px 16px;
  298. background: white;
  299. display: flex;
  300. align-items: center;
  301. gap: 12px;
  302. }
  303. .search-container .el-input {
  304. flex: 1;
  305. }
  306. .search-btn {
  307. padding: 8px 16px;
  308. background: #17b3a3;
  309. color: white;
  310. border: none;
  311. border-radius: 4px;
  312. cursor: pointer;
  313. font-size: 14px;
  314. transition: background-color 0.2s;
  315. }
  316. .search-btn:hover {
  317. background: #0d8f7f;
  318. }
  319. .search-btn:disabled {
  320. background: #ccc;
  321. cursor: not-allowed;
  322. }
  323. /* 工单列表 */
  324. .work-order-list {
  325. overflow-y: auto;
  326. padding: 12px 16px;
  327. }
  328. /* 工单卡片 */
  329. .work-order-card {
  330. background: white;
  331. border-radius: 8px;
  332. margin-bottom: 12px;
  333. padding: 16px;
  334. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  335. cursor: pointer;
  336. transition: all 0.2s ease;
  337. border: 2px solid transparent;
  338. }
  339. .work-order-card:hover {
  340. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  341. transform: translateY(-1px);
  342. }
  343. .work-order-card.selected {
  344. border-color: #17b3a3;
  345. background: #f0fffe;
  346. }
  347. .work-order-card:active {
  348. transform: translateY(0);
  349. }
  350. /* 内容区域 */
  351. .content-area {
  352. flex: 1;
  353. overflow-y: auto;
  354. padding: 12px 16px;
  355. }
  356. /* 材料卡片 */
  357. .material-card {
  358. background: white;
  359. border-radius: 8px;
  360. margin-bottom: 12px;
  361. padding: 16px;
  362. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  363. cursor: pointer;
  364. transition: all 0.2s ease;
  365. border: 2px solid transparent;
  366. }
  367. .material-card:hover {
  368. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  369. transform: translateY(-1px);
  370. }
  371. .material-card.selected {
  372. border-color: #17b3a3;
  373. background: #f0fffe;
  374. }
  375. .material-card:active {
  376. transform: translateY(0);
  377. }
  378. /* 卡片标题 */
  379. .card-title {
  380. margin-bottom: 12px;
  381. }
  382. .title-label {
  383. font-size: 12px;
  384. color: #666;
  385. display: block;
  386. margin-bottom: 4px;
  387. }
  388. .title-value {
  389. font-size: 16px;
  390. font-weight: bold;
  391. color: #333;
  392. margin-left: 16px;
  393. }
  394. /* 物料描述行 */
  395. .part-desc-row {
  396. margin-bottom: 12px;
  397. padding: 0 4px;
  398. }
  399. .desc-text {
  400. font-size: 12px;
  401. color: #666;
  402. line-height: 1.3;
  403. word-break: break-all;
  404. }
  405. /* 卡片详情 */
  406. .card-details {
  407. display: flex;
  408. justify-content: space-between;
  409. align-items: flex-start;
  410. gap: 4px;
  411. }
  412. .detail-item {
  413. flex: 1;
  414. text-align: center;
  415. min-width: 50px;
  416. max-width: 70px;
  417. }
  418. .detail-label {
  419. font-size: 11px;
  420. color: #666;
  421. margin-bottom: 4px;
  422. line-height: 1.2;
  423. margin-left: -12px;
  424. }
  425. .detail-value {
  426. font-size: 13px;
  427. color: #333;
  428. line-height: 1.2;
  429. margin-left: -12px;
  430. }
  431. /* 扫描区域 */
  432. .scan-section {
  433. margin-top: 16px;
  434. }
  435. /* 扫描容器 */
  436. .scan-container {
  437. padding: 12px 16px;
  438. background: white;
  439. display: flex;
  440. align-items: center;
  441. gap: 12px;
  442. border-radius: 0 0 8px 8px;
  443. margin-bottom: 12px;
  444. }
  445. .scan-container .el-input {
  446. width: 240px;
  447. margin-right: 12px;
  448. }
  449. /* 紧凑型输入框样式 */
  450. .compact-input ::v-deep .el-input__inner {
  451. height: 36px;
  452. padding: 0 12px 0 35px;
  453. font-size: 14px;
  454. }
  455. .compact-input ::v-deep .el-input__prefix {
  456. left: 10px;
  457. }
  458. .compact-input ::v-deep .el-input__suffix {
  459. right: 30px;
  460. }
  461. /* 模式切换开关 */
  462. .mode-switch {
  463. position: relative;
  464. display: inline-block;
  465. }
  466. .custom-switch {
  467. transform: scale(1.3);
  468. }
  469. /* 中间文字 */
  470. .switch-text {
  471. position: absolute;
  472. left: 25%;
  473. top: 53%;
  474. transform: translate(-50%, -50%);
  475. font-size: 12px;
  476. font-weight: bold;
  477. color: white;
  478. pointer-events: none;
  479. z-index: 2;
  480. }
  481. .switch-text2 {
  482. position: absolute;
  483. left: 75%;
  484. top: 53%;
  485. transform: translate(-50%, -50%);
  486. font-size: 12px;
  487. font-weight: bold;
  488. color: white;
  489. pointer-events: none;
  490. z-index: 2;
  491. }
  492. /* 调整 switch 尺寸以便容纳文字 */
  493. .custom-switch ::v-deep .el-switch__core {
  494. width: 60px;
  495. height: 28px;
  496. }
  497. /* 已选材料信息 */
  498. .selected-material-info {
  499. background: #e8f5e8;
  500. padding: 12px 16px;
  501. margin-bottom: 12px;
  502. border-radius: 8px;
  503. border-left: 4px solid #17b3a3;
  504. }
  505. .info-title {
  506. font-size: 14px;
  507. font-weight: bold;
  508. color: #333;
  509. margin-bottom: 8px;
  510. }
  511. .info-details {
  512. display: flex;
  513. gap: 16px;
  514. font-size: 12px;
  515. color: #666;
  516. }
  517. /* 标签列表 */
  518. .label-list {
  519. background: white;
  520. margin-bottom: 12px;
  521. border-radius: 8px;
  522. overflow: hidden;
  523. }
  524. .list-header {
  525. display: flex;
  526. background: #f8f9fa;
  527. padding: 12px 8px;
  528. border-bottom: 1px solid #e0e0e0;
  529. font-size: 12px;
  530. color: #666;
  531. font-weight: 500;
  532. }
  533. .list-item {
  534. display: flex;
  535. padding: 12px 8px;
  536. border-bottom: 1px solid #f0f0f0;
  537. font-size: 12px;
  538. color: #333;
  539. }
  540. .list-item:last-child {
  541. border-bottom: none;
  542. }
  543. .col-no {
  544. width: 30px;
  545. text-align: center;
  546. }
  547. .col-label {
  548. flex: 2;
  549. text-align: center;
  550. }
  551. .col-batch {
  552. flex: 1;
  553. text-align: center;
  554. }
  555. .col-qty {
  556. width: 60px;
  557. text-align: center;
  558. }
  559. .empty-labels {
  560. padding: 40px 20px;
  561. text-align: center;
  562. color: #999;
  563. }
  564. .empty-labels p {
  565. margin: 0;
  566. font-size: 14px;
  567. }
  568. /* 底部操作按钮 */
  569. .bottom-actions {
  570. display: flex;
  571. padding: 16px;
  572. gap: 20px;
  573. background: white;
  574. border-radius: 8px;
  575. }
  576. .action-btn {
  577. flex: 1;
  578. padding: 12px;
  579. border: 1px solid #17b3a3;
  580. background: white;
  581. color: #17b3a3;
  582. border-radius: 20px;
  583. font-size: 14px;
  584. cursor: pointer;
  585. transition: all 0.2s ease;
  586. }
  587. .action-btn.primary {
  588. background: #17b3a3;
  589. color: white;
  590. }
  591. .action-btn.primary:hover {
  592. background: #0d8f7f;
  593. }
  594. .action-btn.secondary:hover {
  595. background: #17b3a3;
  596. color: white;
  597. }
  598. .action-btn:active {
  599. transform: scale(0.98);
  600. }
  601. .action-btn:disabled {
  602. opacity: 0.5;
  603. cursor: not-allowed;
  604. }
  605. .action-btn:disabled:hover {
  606. background: #17b3a3;
  607. color: white;
  608. }
  609. /* 空状态 */
  610. .empty-state {
  611. display: flex;
  612. flex-direction: column;
  613. align-items: center;
  614. justify-content: center;
  615. padding: 60px 20px;
  616. color: #999;
  617. }
  618. .empty-state i {
  619. font-size: 48px;
  620. margin-bottom: 16px;
  621. }
  622. .empty-state p {
  623. font-size: 14px;
  624. margin: 0;
  625. }
  626. /* 加载状态 */
  627. .loading-state {
  628. display: flex;
  629. flex-direction: column;
  630. align-items: center;
  631. justify-content: center;
  632. padding: 60px 20px;
  633. color: #17b3a3;
  634. }
  635. .loading-state i {
  636. font-size: 24px;
  637. margin-bottom: 12px;
  638. animation: spin 1s linear infinite;
  639. }
  640. @keyframes spin {
  641. from {
  642. transform: rotate(0deg);
  643. }
  644. to {
  645. transform: rotate(360deg);
  646. }
  647. }
  648. .loading-state p {
  649. font-size: 14px;
  650. margin: 0;
  651. }
  652. /* 响应式设计 */
  653. @media (max-width: 360px) {
  654. .header-bar {
  655. padding: 8px 12px;
  656. }
  657. .search-container {
  658. padding: 8px 12px;
  659. }
  660. .work-order-list {
  661. padding: 8px 12px;
  662. }
  663. .work-order-card {
  664. padding: 12px;
  665. }
  666. .content-area {
  667. padding: 8px 12px;
  668. }
  669. .material-card {
  670. padding: 12px;
  671. }
  672. .card-details {
  673. flex-wrap: wrap;
  674. gap: 6px;
  675. }
  676. .detail-item {
  677. flex: 0 0 48%;
  678. margin-bottom: 6px;
  679. min-width: 50px;
  680. }
  681. .list-header,
  682. .list-item {
  683. font-size: 11px;
  684. }
  685. .col-label {
  686. flex: 1.5;
  687. }
  688. }
  689. </style>