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.

823 lines
19 KiB

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