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.

812 lines
18 KiB

3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
1 month ago
3 months ago
4 weeks ago
3 months ago
4 weeks ago
3 months ago
4 weeks ago
2 months ago
3 months ago
2 months ago
4 weeks ago
3 months ago
4 weeks ago
3 months ago
2 months ago
4 weeks ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
4 weeks ago
3 months ago
  1. <template>
  2. <div>
  3. <div class="pda-container">
  4. <!-- 头部栏 -->
  5. <div class="header-bar">
  6. <div class="header-left" @click="handleBack">
  7. <i class="el-icon-arrow-left"></i>
  8. <span>标签查询</span>
  9. </div>
  10. <div class="header-right" @click="$router.push({ path: '/' })">
  11. 首页
  12. </div>
  13. </div>
  14. <div class="table-body" style="max-height: 500px; overflow-y: auto;">
  15. <div class="main-content form-section">
  16. <!-- 标签扫描输入框 -->
  17. <div class="input-group">
  18. <el-input
  19. v-model="labelCode"
  20. placeholder="请扫描标签编码"
  21. class="form-input"
  22. clearable
  23. inputmode="none"
  24. autocomplete="off"
  25. autocorrect="off"
  26. spellcheck="false"
  27. @keyup.enter.native="handleLabelScan"
  28. ref="labelInput"
  29. />
  30. </div>
  31. <!-- 标签信息显示 (扫描后显示) -->
  32. <div v-if="labelInfo" class="info-section">
  33. <div class="info-title">
  34. <i class="el-icon-document">标签信息</i>
  35. <button
  36. class="print-btn"
  37. @click="handlePrint"
  38. :disabled="printLoading"
  39. >
  40. <i :class="printLoading ? 'el-icon-loading' : 'el-icon-printer'"></i>
  41. {{ printLoading ? '打印中...' : '打印' }}
  42. </button>
  43. </div>
  44. <div class="info-row">
  45. <span class="info-label">标签编码:</span>
  46. <span class="info-value">{{ labelInfo.unitId || '-' }}</span>
  47. </div>
  48. <div class="info-row">
  49. <span class="info-label">物料编码:</span>
  50. <span class="info-value">{{ labelInfo.partNo || '-' }}</span>
  51. </div>
  52. <div class="info-row">
  53. <span class="info-label">数量:</span>
  54. <span class="info-value">{{ labelInfo.qty || '0' }}</span>
  55. </div>
  56. <div class="info-row">
  57. <span class="info-label">批次号:</span>
  58. <span class="info-value">{{ labelInfo.batchNo || '-' }}</span>
  59. </div>
  60. <div class="info-row">
  61. <span class="info-label">仓库:</span>
  62. <span class="info-value">{{ labelInfo.warehouseId || '-' }}</span>
  63. </div>
  64. <div class="info-row">
  65. <span class="info-label">库位:</span>
  66. <span class="info-value">{{ labelInfo.locationId || '-' }}</span>
  67. </div>
  68. <div class="info-row">
  69. <span class="info-label">WDR:</span>
  70. <span class="info-value">{{ labelInfo.wdr || '-' }}</span>
  71. </div>
  72. <div class="info-row">
  73. <span class="info-label">engChgLevel:</span>
  74. <span class="info-value">{{ labelInfo.engChgLevel }}</span>
  75. </div>
  76. <div class="info-row">
  77. <span class="info-label">expiredDate:</span>
  78. <span class="info-value">{{ formatDate(labelInfo.expiredDate) }}</span>
  79. </div>
  80. <div class="info-row">
  81. <span class="info-label">创建时间:</span>
  82. <span class="info-value">{{ formatDate(labelInfo.createdDate) }}</span>
  83. </div>
  84. <!-- 预留申请单号 - rqrq -->
  85. <div class="info-row" v-if="labelInfo.reserveOrderRef1">
  86. <span class="info-label">预留申请单号:</span>
  87. <span class="info-value">{{ labelInfo.reserveOrderRef1 || '-' }}</span>
  88. </div>
  89. <!-- 预留订单号 - rqrq -->
  90. <div class="info-row" v-if="labelInfo.reserveOrderRef3">
  91. <span class="info-label">预留订单号:</span>
  92. <span class="info-value">{{ labelInfo.reserveOrderRef3 || '-' }}</span>
  93. </div>
  94. </div>
  95. <!-- 底部操作按钮 -->
  96. <div v-if="labelInfo" class="bottom-actions">
  97. <button
  98. class="action-btn primary"
  99. @click="showSplitDialog"
  100. >
  101. 拆分
  102. </button>
  103. <button
  104. class="action-btn secondary"
  105. @click="clearData"
  106. >
  107. 清空
  108. </button>
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. <!-- 拆分对话框 -->
  114. <el-dialog
  115. title="标签拆分"
  116. :visible.sync="splitDialogVisible"
  117. width="90%"
  118. :close-on-click-modal="false"
  119. :append-to-body="true"
  120. :modal-append-to-body="true"
  121. >
  122. <div class="split-dialog-content">
  123. <div class="split-info-row">
  124. <span class="split-label">原标签编码:</span>
  125. <span class="split-value">{{ labelInfo ? labelInfo.unitId : '' }}</span>
  126. </div>
  127. <div class="split-info-row">
  128. <span class="split-label">当前数量:</span>
  129. <span class="split-value">{{ labelInfo ? labelInfo.qty : 0 }}</span>
  130. </div>
  131. <div class="split-input-group">
  132. <label class="split-label required">拆分数量:</label>
  133. <el-input
  134. v-model="splitQty"
  135. type="number"
  136. placeholder="请输入拆分数量(必须小于100)"
  137. class="split-input"
  138. ref="splitInput"
  139. />
  140. <div class="split-hint" v-if="splitQty && !isValidSplitQty">
  141. <i class="el-icon-warning"></i>
  142. 拆分数量必须大于0且小于当前数量
  143. </div>
  144. </div>
  145. <div class="split-info-row">
  146. <span class="split-label">拆分后原标签剩余:</span>
  147. <span class="split-value split-remain">{{ calculateRemainQty }}</span>
  148. </div>
  149. </div>
  150. <div slot="footer" class="dialog-footer">
  151. <button class="dialog-btn cancel" @click="splitDialogVisible = false">
  152. 取消
  153. </button>
  154. <button
  155. class="dialog-btn confirm"
  156. @click="confirmSplit"
  157. :disabled="splitLoading || !isValidSplitQty"
  158. >
  159. {{ splitLoading ? '处理中...' : '确定拆分' }}
  160. </button>
  161. </div>
  162. </el-dialog>
  163. </div>
  164. </template>
  165. <script>
  166. import { queryLabelInfo, splitLabel } from '@/api/inventory/label'
  167. import { printLabelCommon } from '@/api/production/production-inbound.js'
  168. export default {
  169. name: 'LabelQuery',
  170. data() {
  171. return {
  172. site: localStorage.getItem('site'),
  173. labelCode: '',
  174. labelInfo: null,
  175. loading: false,
  176. printLoading: false,
  177. splitDialogVisible: false,
  178. splitQty: '',
  179. splitLoading: false
  180. };
  181. },
  182. computed: {
  183. /**
  184. * 计算拆分后原标签剩余数量
  185. */
  186. calculateRemainQty() {
  187. if (!this.labelInfo || !this.splitQty) {
  188. return this.labelInfo ? this.labelInfo.qty : 0;
  189. }
  190. const currentQty = parseFloat(this.labelInfo.qty) || 0;
  191. const split = parseFloat(this.splitQty) || 0;
  192. const remain = currentQty - split;
  193. return remain >= 0 ? remain : 0;
  194. },
  195. /**
  196. * 验证拆分数量是否有效
  197. */
  198. isValidSplitQty() {
  199. if (!this.splitQty || !this.labelInfo) {
  200. return false;
  201. }
  202. const split = parseFloat(this.splitQty);
  203. const current = parseFloat(this.labelInfo.qty) || 0;
  204. return split > 0 && split < current;
  205. }
  206. },
  207. methods: {
  208. /**
  209. * 返回上一页
  210. */
  211. handleBack() {
  212. this.$router.back();
  213. },
  214. /**
  215. * 处理标签扫描
  216. */
  217. handleLabelScan() {
  218. if (!this.labelCode.trim()) {
  219. this.$message.error('请扫描有效的标签编码');
  220. return;
  221. }
  222. this.loading = true;
  223. queryLabelInfo({
  224. site: this.site,
  225. labelCode: this.labelCode.trim()
  226. }).then(({ data }) => {
  227. this.loading = false;
  228. if (data && data.code === 0) {
  229. this.labelInfo = data.data;
  230. this.$message.success('查询成功');
  231. } else {
  232. this.$message.error(data.msg || '标签不存在');
  233. this.labelCode = '';
  234. this.labelInfo = null;
  235. this.focusLabelInput();
  236. }
  237. }).catch(error => {
  238. this.loading = false;
  239. console.error('查询标签失败:', error);
  240. this.$message.error('查询异常');
  241. this.labelCode = '';
  242. this.labelInfo = null;
  243. this.focusLabelInput();
  244. });
  245. },
  246. /**
  247. * 清空数据
  248. */
  249. clearData() {
  250. this.labelCode = '';
  251. this.labelInfo = null;
  252. this.focusLabelInput();
  253. },
  254. /**
  255. * 聚焦标签输入框
  256. */
  257. focusLabelInput() {
  258. this.$nextTick(() => {
  259. if (this.$refs.labelInput) {
  260. this.$refs.labelInput.focus();
  261. }
  262. });
  263. },
  264. /**
  265. * 格式化日期
  266. */
  267. formatDate(date) {
  268. if (!date) return '-';
  269. const d = new Date(date);
  270. const year = d.getFullYear();
  271. const month = String(d.getMonth() + 1).padStart(2, '0');
  272. const day = String(d.getDate()).padStart(2, '0');
  273. const hours = String(d.getHours()).padStart(2, '0');
  274. const minutes = String(d.getMinutes()).padStart(2, '0');
  275. return `${year}-${month}-${day} ${hours}:${minutes}`;
  276. },
  277. /**
  278. * 获取状态标签类型
  279. */
  280. getStatusType(inStockFlag) {
  281. return inStockFlag === 'Y' ? 'success' : 'danger';
  282. },
  283. /**
  284. * 处理打印
  285. */
  286. handlePrint() {
  287. if (!this.labelInfo || !this.labelInfo.unitId) {
  288. this.$message.error('没有可打印的标签信息');
  289. return;
  290. }
  291. let printLabelType;
  292. if (this.labelInfo.partNo && this.labelInfo.partNo.startsWith("80")) {
  293. printLabelType = '库存成品标签';
  294. } else {
  295. printLabelType = 'BIL标签';
  296. }
  297. // 调用打印方法,传入unitId数组和标签类型
  298. this.printViaServer([this.labelInfo.unitId], printLabelType);
  299. },
  300. /**
  301. * 通过服务器打印
  302. * @param {Array} unitIds - HU unitId列表
  303. * @param {String} printLabelType - 标签类型
  304. */
  305. async printViaServer(unitIds, printLabelType) {
  306. if (!unitIds || unitIds.length === 0) {
  307. console.warn('没有可打印的标签');
  308. return;
  309. }
  310. this.printLoading = true;
  311. try {
  312. const printRequest = {
  313. userId: localStorage.getItem('userName'),
  314. username: localStorage.getItem('userName'),
  315. site: localStorage.getItem('site'),
  316. unitIds: unitIds,
  317. labelType: printLabelType
  318. };
  319. console.log('打印请求:', printRequest);
  320. const { data } = await printLabelCommon(printRequest);
  321. if (data.code === 200 || data.code === 0) {
  322. this.$message.success(`打印任务已发送!`);
  323. } else {
  324. this.$message.error(data.msg || '打印失败');
  325. }
  326. } catch (error) {
  327. console.error('服务器打印失败:', error);
  328. this.$message.error(`打印失败: ${error.message || error}`);
  329. } finally {
  330. this.printLoading = false;
  331. }
  332. },
  333. /**
  334. * 显示拆分对话框
  335. */
  336. showSplitDialog() {
  337. if (!this.labelInfo) {
  338. this.$message.error('没有可拆分的标签');
  339. return;
  340. }
  341. this.splitQty = '';
  342. this.splitDialogVisible = true;
  343. // 聚焦到拆分数量输入框
  344. this.$nextTick(() => {
  345. if (this.$refs.splitInput) {
  346. this.$refs.splitInput.focus();
  347. }
  348. });
  349. },
  350. /**
  351. * 确认拆分
  352. */
  353. async confirmSplit() {
  354. // 验证拆分数量
  355. if (!this.splitQty) {
  356. this.$message.error('请输入拆分数量');
  357. return;
  358. }
  359. const splitQty = parseFloat(this.splitQty);
  360. const currentQty = parseFloat(this.labelInfo.qty);
  361. if (splitQty <= 0) {
  362. this.$message.error('拆分数量必须大于0');
  363. return;
  364. }
  365. if (splitQty >= currentQty) {
  366. this.$message.error('拆分数量必须小于当前数量');
  367. return;
  368. }
  369. this.splitLoading = true;
  370. try {
  371. const { data } = await splitLabel({
  372. site: this.site,
  373. unitId: this.labelInfo.unitId,
  374. splitQty: splitQty,
  375. operatorName: localStorage.getItem('userName')
  376. });
  377. if (data && data.code === 0) {
  378. const result = data.data;
  379. this.$message.success('拆分成功!');
  380. // 关闭对话框
  381. this.splitDialogVisible = false;
  382. // 确定标签类型
  383. let printLabelType;
  384. if (this.labelInfo.partNo && this.labelInfo.partNo.startsWith("80")) {
  385. printLabelType = '库存成品标签';
  386. } else {
  387. printLabelType = 'BIL标签';
  388. }
  389. // 打印原标签和新标签
  390. console.log('打印标签:', [result.originalUnitId, result.newUnitId]);
  391. await this.printViaServer([result.originalUnitId, result.newUnitId], printLabelType);
  392. // 刷新标签信息(显示更新后的数量)
  393. this.labelCode = this.labelInfo.unitId;
  394. this.handleLabelScan();
  395. } else {
  396. this.$message.error(data.msg || '拆分失败');
  397. }
  398. } catch (error) {
  399. console.error('拆分标签失败:', error);
  400. this.$message.error('拆分异常: ' + (error.message || error));
  401. } finally {
  402. this.splitLoading = false;
  403. }
  404. }
  405. },
  406. mounted() {
  407. // 页面加载后自动聚焦标签输入框
  408. this.focusLabelInput();
  409. }
  410. };
  411. </script>
  412. <style scoped>
  413. .input-group {
  414. margin-bottom: 1px !important;
  415. }
  416. /* PDA容器样式 */
  417. .pda-container {
  418. width: 100vw;
  419. height: 120vh;
  420. display: flex;
  421. flex-direction: column;
  422. background: #f5f5f5;
  423. font-family: 'Arial', sans-serif;
  424. }
  425. /* 头部栏样式 */
  426. .header-bar {
  427. display: flex;
  428. justify-content: space-between;
  429. align-items: center;
  430. padding: 8px 16px;
  431. background: #17B3A3;
  432. color: white;
  433. height: 40px;
  434. min-height: 40px;
  435. max-height: 40px;
  436. }
  437. .header-left {
  438. display: flex;
  439. align-items: center;
  440. cursor: pointer;
  441. }
  442. .header-left i {
  443. margin-right: 8px;
  444. font-size: 18px;
  445. }
  446. .header-left span {
  447. font-size: 16px;
  448. font-weight: 500;
  449. }
  450. .header-right {
  451. cursor: pointer;
  452. font-size: 14px;
  453. padding: 4px 8px;
  454. border-radius: 4px;
  455. }
  456. /* 主要内容区 */
  457. .table-body {
  458. flex: 1;
  459. overflow-y: auto;
  460. }
  461. .main-content {
  462. padding: 16px;
  463. }
  464. /* 输入组样式 */
  465. .input-label {
  466. display: block;
  467. margin-bottom: 8px;
  468. font-size: 14px;
  469. font-weight: 500;
  470. color: #333;
  471. }
  472. .form-input {
  473. width: 100%;
  474. height: 44px;
  475. }
  476. /* 信息展示区 */
  477. .info-section {
  478. background: white;
  479. border-radius: 8px;
  480. padding: 16px;
  481. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  482. }
  483. .info-title {
  484. display: flex;
  485. align-items: center;
  486. justify-content: space-between;
  487. font-size: 16px;
  488. font-weight: bold;
  489. color: #17B3A3;
  490. margin-bottom: 6px;
  491. padding-bottom: 5px;
  492. border-bottom: 2px solid #17B3A3;
  493. }
  494. .info-title i {
  495. margin-right: 8px;
  496. font-size: 18px;
  497. }
  498. /* 打印按钮 */
  499. .print-btn {
  500. display: flex;
  501. align-items: center;
  502. gap: 4px;
  503. padding: 6px 12px;
  504. background: #17B3A3;
  505. color: white;
  506. border: none;
  507. border-radius: 4px;
  508. font-size: 14px;
  509. cursor: pointer;
  510. transition: all 0.2s;
  511. white-space: nowrap;
  512. }
  513. .print-btn:hover:not(:disabled) {
  514. background: #15a394;
  515. transform: translateY(-1px);
  516. box-shadow: 0 2px 8px rgba(23, 179, 163, 0.3);
  517. }
  518. .print-btn:active:not(:disabled) {
  519. transform: translateY(0);
  520. }
  521. .print-btn:disabled {
  522. background: #ccc;
  523. cursor: not-allowed;
  524. opacity: 0.6;
  525. }
  526. .print-btn i {
  527. margin-right: 0;
  528. font-size: 16px;
  529. }
  530. .info-row {
  531. display: flex;
  532. justify-content: space-between;
  533. align-items: flex-start;
  534. padding: 10px 0;
  535. border-bottom: 1px solid #f0f0f0;
  536. }
  537. .info-row:last-child {
  538. border-bottom: none;
  539. }
  540. .info-label {
  541. font-size: 14px;
  542. color: #666;
  543. min-width: 90px;
  544. flex-shrink: 0;
  545. }
  546. .info-value {
  547. font-size: 14px;
  548. font-weight: 500;
  549. color: #333;
  550. flex: 1;
  551. text-align: right;
  552. word-break: break-all;
  553. }
  554. /* 底部按钮区 */
  555. .bottom-actions {
  556. display: flex;
  557. gap: 12px;
  558. padding-top: 16px;
  559. }
  560. .action-btn {
  561. flex: 1;
  562. padding: 12px 24px;
  563. border: none;
  564. border-radius: 6px;
  565. font-size: 16px;
  566. font-weight: 500;
  567. cursor: pointer;
  568. transition: all 0.2s;
  569. min-height: 44px;
  570. }
  571. .action-btn.primary {
  572. background: #17B3A3;
  573. color: white;
  574. }
  575. .action-btn.primary:hover {
  576. background: #15a394;
  577. }
  578. .action-btn.primary:disabled {
  579. background: #ccc;
  580. cursor: not-allowed;
  581. }
  582. .action-btn.secondary {
  583. background: #f5f5f5;
  584. color: #333;
  585. border: 1px solid #ddd;
  586. }
  587. .action-btn.secondary:hover {
  588. background: #e8e8e8;
  589. }
  590. /* 拆分对话框样式 */
  591. .split-dialog-content {
  592. padding: 16px 0;
  593. }
  594. .split-info-row {
  595. display: flex;
  596. justify-content: space-between;
  597. align-items: center;
  598. padding: 12px 0;
  599. border-bottom: 1px solid #f0f0f0;
  600. }
  601. .split-label {
  602. font-size: 14px;
  603. color: #666;
  604. font-weight: 500;
  605. }
  606. .split-value {
  607. font-size: 15px;
  608. font-weight: 600;
  609. color: #333;
  610. }
  611. .split-value.split-remain {
  612. color: #17B3A3;
  613. font-size: 16px;
  614. }
  615. .split-input-group {
  616. margin: 20px 0;
  617. }
  618. .split-input-group .split-label {
  619. display: block;
  620. margin-bottom: 8px;
  621. }
  622. .split-input {
  623. width: 100%;
  624. }
  625. /* 对话框底部按钮 */
  626. .dialog-footer {
  627. display: flex;
  628. gap: 12px;
  629. padding-top: 16px;
  630. }
  631. .dialog-btn {
  632. flex: 1;
  633. padding: 12px 24px;
  634. border: none;
  635. border-radius: 6px;
  636. font-size: 16px;
  637. font-weight: 500;
  638. cursor: pointer;
  639. transition: all 0.2s;
  640. min-height: 44px;
  641. }
  642. .dialog-btn.confirm {
  643. background: #17B3A3;
  644. color: white;
  645. }
  646. .dialog-btn.confirm:hover:not(:disabled) {
  647. background: #15a394;
  648. }
  649. .dialog-btn.confirm:disabled {
  650. background: #ccc;
  651. cursor: not-allowed;
  652. opacity: 0.6;
  653. }
  654. .dialog-btn.cancel {
  655. background: #f5f5f5;
  656. color: #333;
  657. border: 1px solid #ddd;
  658. }
  659. .dialog-btn.cancel:hover {
  660. background: #e8e8e8;
  661. }
  662. /* 强制对话框显示在最上层 */
  663. ::v-deep .el-dialog__wrapper {
  664. z-index: 9999 !important;
  665. }
  666. ::v-deep .el-dialog {
  667. z-index: 10000 !important;
  668. }
  669. ::v-deep .v-modal {
  670. z-index: 9998 !important;
  671. }
  672. /* 响应式设计 */
  673. @media screen and (max-width: 480px) {
  674. .header-bar {
  675. padding: 8px 12px;
  676. }
  677. .main-content {
  678. padding: 12px;
  679. }
  680. .info-section {
  681. padding: 2px;
  682. }
  683. .info-label {
  684. font-size: 13px;
  685. min-width: 80px;
  686. }
  687. .info-value {
  688. font-size: 13px;
  689. }
  690. .action-btn {
  691. padding: 10px 16px;
  692. font-size: 14px;
  693. }
  694. }
  695. </style>