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.

741 lines
18 KiB

12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
4 weeks ago
4 weeks ago
4 weeks ago
12 months ago
4 weeks ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
  1. <template>
  2. <div>
  3. <div class="pda-container">
  4. <div class="status-bar">
  5. <div class="goBack" @click="$router.back()"><i class="el-icon-arrow-left"></i>上一页</div>
  6. <div class="goBack">其它入库</div>
  7. <div class="network" style="color: #fff" @click="$router.push({ path: '/' })">🏠首页</div>
  8. </div>
  9. <div style="overflow-y: auto">
  10. <!-- 搜索框 -->
  11. <div class="search-container">
  12. <el-input clearable class="compact-input"
  13. v-model="scanCode"
  14. placeholder="请扫描HandlingUnit条码"
  15. prefix-icon="el-icon-search"
  16. @keyup.enter.native="handleScan"
  17. ref="scanInput"
  18. inputmode="none"
  19. autocomplete="off"
  20. autocorrect="off"
  21. spellcheck="false"
  22. />
  23. <div class="mode-switch">
  24. <el-switch
  25. class="custom-switch"
  26. v-model="isRemoveMode"
  27. active-color="#ff4949"
  28. inactive-color="#13ce66">
  29. </el-switch>
  30. <span v-if="isRemoveMode" class="switch-text">{{ '移除' }}</span>
  31. <span v-else class="switch-text2">{{ '添加' }}</span>
  32. </div>
  33. </div>
  34. <!-- 其它入库信息卡片 -->
  35. <div class="material-info-card">
  36. <div class="input-form">
  37. <div class="form-row">
  38. <div class="form-item">
  39. <label class="form-label">入库原因</label>
  40. <el-input
  41. v-model="inboundForm.inboundReason"
  42. placeholder="请输入入库原因"
  43. size="small">
  44. </el-input>
  45. </div>
  46. <div class="form-item">
  47. <label class="form-label">目标库位</label>
  48. <el-input
  49. v-model="inboundForm.targetLocationId"
  50. placeholder="请输入目标库位"
  51. size="small"
  52. @keyup.enter.native="handleLocationScan"
  53. ref="locationInput">
  54. </el-input>
  55. </div>
  56. </div>
  57. </div>
  58. </div>
  59. <!-- 其它入库信息确认标题 -->
  60. <div class="section-title">
  61. <div class="title-left">
  62. <i class="el-icon-box"></i>
  63. <span>其它入库信息确认</span>
  64. </div>
  65. </div>
  66. <!-- 扫描的HandlingUnit明细列表 -->
  67. <div class="scanned-items" v-if="scannedItems.length > 0" style="margin: 2px;">
  68. <div class="label-list">
  69. <el-form label-position="top" style="margin: 3px;">
  70. <el-row :gutter="5"
  71. v-for="(label, index) in scannedItems"
  72. :key="label.id"
  73. :class="index < scannedItems.length - 1 ? 'bottom-line-row' : ''"
  74. style="border: 1px solid #e0e0e0; border-radius: 4px; margin-bottom: 8px; padding: 8px;">
  75. <el-col :span="16">
  76. <el-form-item label="HandlingUnit">
  77. <span>{{ label.unitId }}</span>
  78. </el-form-item>
  79. </el-col>
  80. <el-col :span="8">
  81. <el-form-item label="物料编码">
  82. <span>{{ label.partNo }}</span>
  83. </el-form-item>
  84. </el-col>
  85. <el-col :span="24" v-if="label.partDesc">
  86. <el-form-item label="物料描述">
  87. <span>{{ label.partDesc }}</span>
  88. </el-form-item>
  89. </el-col>
  90. <el-col :span="10" v-if="label.batchNo">
  91. <el-form-item label="批次号">
  92. <span>{{ label.batchNo }}</span>
  93. </el-form-item>
  94. </el-col>
  95. <el-col :span="10" v-if="label.locationId">
  96. <el-form-item label="库位">
  97. <span>{{ label.locationId }}</span>
  98. </el-form-item>
  99. </el-col>
  100. <el-col :span="4">
  101. <el-form-item label="数量">
  102. <span>{{ label.qty }} {{ label.unit}}</span>
  103. </el-form-item>
  104. </el-col>
  105. </el-row>
  106. </el-form>
  107. </div>
  108. </div>
  109. <!-- 空状态 -->
  110. <div v-if="scannedItems.length === 0" class="empty-labels">
  111. <div style="text-align: center; padding: 20px;">
  112. <p style="color: #999; margin: 0;">暂无扫描HandlingUnit</p>
  113. </div>
  114. </div>
  115. <!-- 底部操作按钮 -->
  116. <div class="bottom-actions" v-if="scannedItems.length > 0">
  117. <button class="action-btn primary" @click="confirmInbound" :disabled="loading">
  118. <i v-if="loading" class="el-icon-loading"></i>
  119. {{ loading ? '处理中...' : '确认其它入库' }}
  120. </button>
  121. <button class="action-btn secondary" style="margin-left: 10px;" @click="cancelProcess" :disabled="loading">
  122. 取消
  123. </button>
  124. </div>
  125. </div>
  126. </div>
  127. </div>
  128. </template>
  129. <script>
  130. import { scanHandlingUnitLabel, confirmOtherInbound } from '@/api/po/po.js';
  131. export default {
  132. data() {
  133. return {
  134. scanCode: '',
  135. isRemoveMode: false, // 默认为添加模式
  136. inboundForm: {
  137. inboundReason: '',
  138. targetLocationId: ''
  139. },
  140. scannedItems: [],
  141. site: '',
  142. loading: false // 加载状态
  143. };
  144. },
  145. methods: {
  146. // 处理扫描
  147. handleScan() {
  148. if (!this.scanCode.trim()) {
  149. return;
  150. }
  151. if (this.isRemoveMode) {
  152. this.removeLabelByCode(this.scanCode.trim());
  153. } else {
  154. this.validateAndAddLabel(this.scanCode.trim());
  155. }
  156. this.scanCode = '';
  157. },
  158. // 处理库位扫描
  159. handleLocationScan() {
  160. if (!this.inboundForm.targetLocationId.trim()) {
  161. return;
  162. }
  163. // 失去光标
  164. if (this.$refs.locationInput) {
  165. this.$refs.locationInput.blur();
  166. }
  167. // 定位到页面最底部
  168. this.$nextTick(() => {
  169. // 方法1:尝试滚动到底部操作按钮
  170. const bottomActions = document.querySelector('.bottom-actions');
  171. if (bottomActions) {
  172. bottomActions.scrollIntoView({
  173. behavior: 'smooth',
  174. block: 'end'
  175. });
  176. return;
  177. }
  178. // 方法2:查找滚动容器并滚动
  179. const scrollContainer = document.querySelector('.pda-container > div[style*="overflow-y"]') ||
  180. document.querySelector('.pda-container > div:nth-child(2)');
  181. if (scrollContainer) {
  182. scrollContainer.scrollTo({
  183. top: scrollContainer.scrollHeight,
  184. behavior: 'smooth'
  185. });
  186. return;
  187. }
  188. // 方法3:备用方案 - 滚动整个页面
  189. setTimeout(() => {
  190. window.scrollTo({
  191. top: document.documentElement.scrollHeight,
  192. behavior: 'smooth'
  193. });
  194. }, 100);
  195. });
  196. },
  197. // 验证标签并添加到列表
  198. validateAndAddLabel(unitId) {
  199. const params = {
  200. unitId: unitId,
  201. //site: this.site,
  202. };
  203. scanHandlingUnitLabel(params).then(({ data }) => {
  204. if (data && data.code === 0 && data.data) {
  205. const huInfo = data.data;
  206. this.processHandlingUnit(unitId, huInfo);
  207. } else {
  208. this.$message.error(data.msg || 'HandlingUnit不存在或查询失败');
  209. }
  210. }).catch(error => {
  211. this.$message.error('扫描失败');
  212. });
  213. },
  214. // 处理HandlingUnit数据
  215. processHandlingUnit(unitId, huInfo) {
  216. // 检查是否已经扫描过
  217. const exists = this.scannedItems.find(item => item.unitId === unitId);
  218. if (exists) {
  219. this.$message.warning('该HandlingUnit已扫描,请勿重复扫描');
  220. return;
  221. }
  222. this.site = this.scannedItems.length > 0 ? this.scannedItems[0].site : huInfo.site; // 设置当前站点为第一个扫描的HU的站点
  223. // 所有标签site必须一致
  224. if (huInfo.site && huInfo.site !== this.site) {
  225. this.$message.error('所有标签site必须一致');
  226. return;
  227. }
  228. // 校验HU是否在库
  229. if (huInfo.inStockFlag !== 'X') {
  230. this.$message.error(unitId+'不是未入库状态');
  231. return;
  232. }
  233. // 添加到列表
  234. this.scannedItems.push({
  235. id: Date.now(),
  236. unitId: unitId,
  237. partNo: huInfo.partNo,
  238. partDesc: huInfo.partDesc,
  239. qty: huInfo.qty,
  240. unit: huInfo.unit,
  241. batchNo: huInfo.batchNo,
  242. locationId: huInfo.locationId,
  243. site: huInfo.site
  244. });
  245. this.$message.success('扫描成功');
  246. },
  247. // 辅助函数:字符串左填充(兼容Vue2)
  248. padStart(str, targetLength, padString) {
  249. str = String(str);
  250. if (str.length >= targetLength) {
  251. return str;
  252. }
  253. padString = String(padString || ' ');
  254. const padLength = targetLength - str.length;
  255. let pad = '';
  256. for (let i = 0; i < padLength; i++) {
  257. pad += padString;
  258. }
  259. return pad + str;
  260. },
  261. // 通过条码移除标签
  262. removeLabelByCode(unitId) {
  263. const index = this.scannedItems.findIndex(item => item.unitId === unitId);
  264. if (index !== -1) {
  265. this.scannedItems.splice(index, 1);
  266. this.$message.success('移除成功');
  267. } else {
  268. this.$message.warning('未找到该HandlingUnit');
  269. }
  270. },
  271. // 确认其它入库
  272. confirmInbound() {
  273. // 验证必填字段
  274. if (!this.inboundForm.targetLocationId) {
  275. this.$message.error('请输入目标库位');
  276. return;
  277. }
  278. if (this.scannedItems.length === 0) {
  279. this.$message.warning('请先扫描HandlingUnit');
  280. return;
  281. }
  282. this.$confirm('确认要进行其它入库操作吗?', '提示', {
  283. confirmButtonText: '确定',
  284. cancelButtonText: '取消',
  285. type: 'warning'
  286. }).then(() => {
  287. this.submitConfirmInbound();
  288. }).catch(() => {
  289. // 用户取消
  290. });
  291. },
  292. // 提交确认入库
  293. submitConfirmInbound() {
  294. const handlingUnitIds = this.scannedItems.map(item => item.unitId);
  295. const params = {
  296. site: this.site,
  297. fuSite: localStorage.getItem('site'),
  298. inboundReason: this.inboundForm.inboundReason,
  299. targetLocationId: this.inboundForm.targetLocationId,
  300. handlingUnitIds: handlingUnitIds,
  301. scannedItems: this.scannedItems
  302. };
  303. // 开始加载
  304. this.loading = true;
  305. // 调用后端API
  306. confirmOtherInbound(params).then(({ data }) => {
  307. if (data && data.code === 0) {
  308. this.$message({
  309. message: '其它入库成功!处理单元: ' + handlingUnitIds.length + '个',
  310. type: 'success',
  311. duration: 3000
  312. });
  313. // 清空当前页面内容并聚焦扫描框
  314. setTimeout(() => {
  315. this.clearFormData();
  316. this.focusScanInput();
  317. }, 1000);
  318. } else {
  319. this.$message.error(data.msg || '其它入库失败');
  320. }
  321. }).catch(error => {
  322. console.error('其它入库失败:', error);
  323. this.$message.error('其它入库失败,请重试');
  324. }).finally(() => {
  325. // 结束加载
  326. this.loading = false;
  327. });
  328. },
  329. // 取消处理
  330. cancelProcess() {
  331. if (this.scannedItems.length > 0) {
  332. this.$confirm('取消后将清空已扫描的HandlingUnit,确定取消吗?', '提示', {
  333. confirmButtonText: '确定',
  334. cancelButtonText: '继续操作',
  335. type: 'warning'
  336. }).then(() => {
  337. this.$router.back();
  338. }).catch(() => {
  339. // 用户选择继续操作
  340. });
  341. } else {
  342. this.$router.back();
  343. }
  344. },
  345. // 初始化表单数据
  346. initFormData() {
  347. // 可以在这里设置其他默认值
  348. },
  349. // 清空表单数据
  350. clearFormData() {
  351. this.scanCode = '';
  352. this.site = '';
  353. this.isRemoveMode = false;
  354. this.inboundForm = {
  355. inboundReason: '',
  356. targetLocationId: ''
  357. };
  358. this.scannedItems = [];
  359. this.loading = false; // 重置加载状态
  360. console.log('表单数据已清空');
  361. },
  362. // 聚焦扫描框
  363. focusScanInput() {
  364. this.$nextTick(() => {
  365. if (this.$refs.scanInput) {
  366. this.$refs.scanInput.focus();
  367. console.log('扫描框已聚焦');
  368. }
  369. });
  370. }
  371. },
  372. mounted() {
  373. // 初始化表单数据
  374. this.initFormData();
  375. // 聚焦扫描框
  376. this.$nextTick(() => {
  377. if (this.$refs.scanInput) {
  378. this.$refs.scanInput.focus();
  379. }
  380. });
  381. }
  382. };
  383. </script>
  384. <style scoped>
  385. /* 复用unqualifiedProcess.vue的样式 */
  386. .pda-container {
  387. width: 100%;
  388. height: 100vh;
  389. display: flex;
  390. flex-direction: column;
  391. overflow: hidden;
  392. }
  393. .status-bar {
  394. background: #17B3A3;
  395. color: white;
  396. padding: 8px 16px;
  397. display: flex;
  398. justify-content: space-between;
  399. align-items: center;
  400. height: 40px;
  401. min-height: 40px;
  402. }
  403. .goBack {
  404. cursor: pointer;
  405. font-size: 16px;
  406. font-weight: 500;
  407. }
  408. /* 搜索容器 */
  409. .search-container {
  410. padding: 12px 16px;
  411. background: white;
  412. display: flex;
  413. align-items: center;
  414. gap: 12px;
  415. }
  416. .search-container .el-input {
  417. width: 240px;
  418. margin-right: 12px;
  419. }
  420. /* 紧凑型输入框样式 */
  421. .compact-input ::v-deep .el-input__inner {
  422. height: 36px;
  423. padding: 0 12px 0 35px;
  424. font-size: 14px;
  425. }
  426. .compact-input ::v-deep .el-input__prefix {
  427. left: 10px;
  428. }
  429. .compact-input ::v-deep .el-input__suffix {
  430. right: 30px;
  431. }
  432. /* 模式切换开关 */
  433. .mode-switch {
  434. position: relative;
  435. display: inline-block;
  436. }
  437. .custom-switch {
  438. transform: scale(1.3);
  439. }
  440. /* 中间文字 */
  441. .switch-text {
  442. position: absolute;
  443. left: 25%;
  444. transform: translateX(-50%);
  445. top: 50%;
  446. transform: translateY(-50%) translateX(-50%);
  447. font-size: 12px;
  448. font-weight: 500;
  449. color: #606266;
  450. white-space: nowrap;
  451. pointer-events: none;
  452. z-index: 1;
  453. top: 53%;
  454. transform: translate(-50%, -50%);
  455. font-size: 12px;
  456. font-weight: bold;
  457. color: white;
  458. pointer-events: none;
  459. z-index: 2;
  460. }
  461. .switch-text2 {
  462. position: absolute;
  463. left: 75%;
  464. transform: translateX(-50%);
  465. top: 50%;
  466. transform: translateY(-50%) translateX(-50%);
  467. font-size: 12px;
  468. font-weight: 500;
  469. color: #606266;
  470. white-space: nowrap;
  471. pointer-events: none;
  472. z-index: 1;
  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 尺寸以便容纳文字 */
  482. .custom-switch ::v-deep .el-switch__core {
  483. width: 60px;
  484. height: 28px;
  485. }
  486. /* 物料信息卡片 */
  487. .material-info-card {
  488. background: white;
  489. margin: 4px 4px;
  490. padding: 6px 20px;
  491. border-radius: 8px;
  492. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  493. border: 1px solid #f0f0f0;
  494. }
  495. .card-title {
  496. margin-bottom: 8px;
  497. display: flex;
  498. align-items: center;
  499. gap: 8px;
  500. }
  501. .title-label {
  502. font-size: 12px;
  503. color: #666;
  504. font-weight: 500;
  505. }
  506. .title-value {
  507. font-size: 16px;
  508. font-weight: bold;
  509. color: #333;
  510. line-height: 1.2;
  511. }
  512. /* 表单样式 */
  513. .input-form {
  514. margin-top: 8px;
  515. }
  516. .form-row {
  517. display: flex;
  518. gap: 12px;
  519. margin-bottom: 12px;
  520. }
  521. .form-row:last-child {
  522. margin-bottom: 0;
  523. }
  524. .form-item {
  525. flex: 1;
  526. display: flex;
  527. flex-direction: column;
  528. }
  529. .form-label {
  530. font-size: 11px;
  531. color: #666;
  532. font-weight: 500;
  533. margin-bottom: 4px;
  534. display: block;
  535. }
  536. .form-item .el-input {
  537. width: 100%;
  538. }
  539. .form-item .el-date-editor {
  540. width: 100%;
  541. }
  542. /* 区域标题 */
  543. .section-title {
  544. display: flex;
  545. align-items: center;
  546. justify-content: space-between;
  547. padding: 6px 8px;
  548. background: white;
  549. margin: 0 4px;
  550. margin-top: 4px;
  551. border-radius: 8px 8px 0 0;
  552. border-bottom: 2px solid #17B3A3;
  553. }
  554. .title-left {
  555. display: flex;
  556. align-items: center;
  557. }
  558. .title-left i {
  559. color: #17B3A3;
  560. font-size: 16px;
  561. margin-right: 8px;
  562. }
  563. .title-left span {
  564. color: #17B3A3;
  565. font-size: 14px;
  566. font-weight: 500;
  567. }
  568. /* 标签列表 */
  569. .label-list {
  570. background: white;
  571. margin: 0 4px 4px;
  572. border-radius: 0 0 8px 8px;
  573. overflow: hidden;
  574. }
  575. .label-list .el-form-item {
  576. margin-bottom: 1px;
  577. }
  578. .label-list .el-form-item__label {
  579. padding-bottom: 1px;
  580. margin-bottom: 0;
  581. line-height: 1.1;
  582. font-size: 11px;
  583. }
  584. .label-list .el-form-item__content {
  585. line-height: 1.2;
  586. font-size: 12px;
  587. }
  588. .bottom-line-row {
  589. border-bottom: 1px solid #f0f0f0;
  590. margin-bottom: 8px;
  591. padding-bottom: 8px;
  592. }
  593. .empty-labels {
  594. padding: 20px;
  595. text-align: center;
  596. color: #999;
  597. background: white;
  598. margin: 0 4px;
  599. border-radius: 8px;
  600. }
  601. /* 底部操作按钮 */
  602. .bottom-actions {
  603. display: flex;
  604. padding: 16px;
  605. gap: 20px;
  606. background: white;
  607. margin-top: auto;
  608. }
  609. .action-btn {
  610. flex: 1;
  611. padding: 12px;
  612. border: 1px solid #17B3A3;
  613. background: #17B3A3;
  614. color: white;
  615. border-radius: 20px;
  616. font-size: 14px;
  617. cursor: pointer;
  618. transition: all 0.2s ease;
  619. }
  620. .action-btn.secondary {
  621. background: white;
  622. color: #17B3A3;
  623. }
  624. .action-btn:hover {
  625. background: #0d8f7f;
  626. border-color: #0d8f7f;
  627. }
  628. .action-btn.secondary:hover {
  629. background: #17B3A3;
  630. color: white;
  631. }
  632. .action-btn:active {
  633. transform: scale(0.98);
  634. }
  635. /* 响应式设计 */
  636. @media (max-width: 360px) {
  637. .status-bar {
  638. padding: 8px 12px;
  639. }
  640. .search-container {
  641. padding: 8px 12px;
  642. }
  643. .material-info-card {
  644. margin: 4px 4px;
  645. padding: 6px 16px;
  646. }
  647. .item-list {
  648. margin: 0 12px 8px;
  649. }
  650. .form-row {
  651. gap: 8px;
  652. margin-bottom: 8px;
  653. }
  654. .form-label {
  655. font-size: 10px;
  656. margin-bottom: 2px;
  657. }
  658. }
  659. </style>