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.

1147 lines
30 KiB

6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
  1. <template>
  2. <div>
  3. <div class="pda-container">
  4. <div class="status-bar">
  5. <div class="goBack" @click="goBack"><i class="el-icon-arrow-left"></i>上一页</div>
  6. <div class="goBack">{{ functionTitle }}</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="function-selector" v-if="!selectedFunction">
  12. <div class="function-card" @click="selectFunction('direct')">
  13. <div class="function-icon">📦</div>
  14. <div class="function-title">直接发料</div>
  15. <div class="function-desc">输入工单号扫描物料标签直接发料</div>
  16. </div>
  17. <div class="function-card" @click="selectFunction('picking')">
  18. <div class="function-icon">🏗</div>
  19. <div class="function-title">拣选装托盘</div>
  20. <div class="function-desc">基于申请单创建托盘扫描箱卷绑定</div>
  21. </div>
  22. <div class="function-card" @click="selectFunction('request')">
  23. <div class="function-icon">📋</div>
  24. <div class="function-title">申请单发料</div>
  25. <div class="function-desc">基于申请单扫描物料标签发料</div>
  26. </div>
  27. </div>
  28. <!-- 直接发料 -->
  29. <div class="direct-issue" v-if="selectedFunction === 'direct'">
  30. <!-- 工单输入 -->
  31. <div class="input-section" v-if="!workOrderMaterials.length">
  32. <div class="input-group">
  33. <label>工单号</label>
  34. <div class="input-with-scan">
  35. <input
  36. v-model="directIssueForm.workOrderNo"
  37. placeholder="请输入或扫描工单号"
  38. @keyup.enter="loadWorkOrderMaterials"
  39. />
  40. <button @click="loadWorkOrderMaterials" class="scan-btn">确认</button>
  41. </div>
  42. </div>
  43. </div>
  44. <!-- 物料列表 -->
  45. <div class="materials-section" v-if="workOrderMaterials.length">
  46. <div class="section-header">
  47. <h3>工单物料 ({{ directIssueForm.workOrderNo }})</h3>
  48. <button @click="resetWorkOrder" class="reset-btn">重新选择</button>
  49. </div>
  50. <div class="material-list">
  51. <div v-for="(material, index) in workOrderMaterials" :key="index"
  52. class="material-item"
  53. :class="{ selected: (selectedMaterial?selectedMaterial.partNo:'') === (material&&material.partNo?material.partNo:'') }"
  54. @click="selectMaterial(material)"
  55. >
  56. <div class="material-info">
  57. <div class="part-no">{{ material.partNo }}</div>
  58. <div class="part-desc">{{ material.partDesc }}</div>
  59. <div class="qty-info">
  60. 需求: {{ material.requiredQty }} |
  61. 已发: {{ material.issuedQty }} |
  62. 剩余: {{ material.remainQty }}
  63. </div>
  64. </div>
  65. <div class="material-status">
  66. <span v-if="material.remainQty > 0" class="status-pending">待发料</span>
  67. <span v-else class="status-complete">已完成</span>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. <!-- 扫描标签 -->
  73. <div class="scan-section" v-if="selectedMaterial">
  74. <div class="section-header">
  75. <h3>扫描物料标签</h3>
  76. </div>
  77. <div class="input-group">
  78. <label>物料标签</label>
  79. <div class="input-with-scan">
  80. <input
  81. v-model="scannedLabel"
  82. placeholder="请扫描物料标签"
  83. @keyup.enter="parseMaterialLabel"
  84. />
  85. <button @click="parseMaterialLabel" class="scan-btn">解析</button>
  86. </div>
  87. </div>
  88. <!-- 标签信息 -->
  89. <div class="label-info" v-if="labelInfo">
  90. <div class="info-row">
  91. <span class="label">物料编码:</span>
  92. <span class="value">{{ labelInfo.partNo }}</span>
  93. </div>
  94. <div class="info-row">
  95. <span class="label">物料描述:</span>
  96. <span class="value">{{ labelInfo.partDesc }}</span>
  97. </div>
  98. <div class="info-row">
  99. <span class="label">批次号:</span>
  100. <span class="value">{{ labelInfo.batchNo }}</span>
  101. </div>
  102. <div class="info-row">
  103. <span class="label">库位:</span>
  104. <span class="value">{{ labelInfo.locationId }}</span>
  105. </div>
  106. <div class="info-row">
  107. <span class="label">可用数量:</span>
  108. <span class="value">{{ labelInfo.availableQty }}</span>
  109. </div>
  110. </div>
  111. <!-- 发料数量 -->
  112. <div class="qty-input" v-if="labelInfo">
  113. <div class="input-group">
  114. <label>发料数量</label>
  115. <input
  116. v-model="issueQty"
  117. type="number"
  118. :max="Math.min(selectedMaterial.remainQty, labelInfo.availableQty)"
  119. placeholder="请输入发料数量"
  120. />
  121. </div>
  122. <div class="input-group">
  123. <label>备注</label>
  124. <input v-model="directIssueForm.remark" placeholder="可选" />
  125. </div>
  126. <button @click="confirmDirectIssue" class="confirm-btn" :disabled="!issueQty">
  127. 确认发料
  128. </button>
  129. </div>
  130. </div>
  131. </div>
  132. <!-- 拣选装托盘 -->
  133. <div class="picking-pallet" v-if="selectedFunction === 'picking'">
  134. <!-- 申请单输入 -->
  135. <div class="input-section" v-if="!currentPallet.palletId">
  136. <div class="input-group">
  137. <label>申请单号</label>
  138. <div class="input-with-scan">
  139. <input
  140. v-model="palletForm.notifyNo"
  141. placeholder="请输入申请单号"
  142. @keyup.enter="createPallet"
  143. />
  144. <button @click="createPallet" class="scan-btn">创建托盘</button>
  145. </div>
  146. </div>
  147. </div>
  148. <!-- 托盘信息 -->
  149. <div class="pallet-info" v-if="currentPallet.palletId">
  150. <div class="section-header">
  151. <h3>托盘信息</h3>
  152. </div>
  153. <div class="info-card">
  154. <div class="info-row">
  155. <span class="label">托盘ID:</span>
  156. <span class="value">{{ currentPallet.palletId }}</span>
  157. </div>
  158. <div class="info-row">
  159. <span class="label">申请单号:</span>
  160. <span class="value">{{ currentPallet.notifyNo }}</span>
  161. </div>
  162. <div class="info-row">
  163. <span class="label">已绑定单元:</span>
  164. <span class="value">{{ currentPallet.unitCount || 0 }}</span>
  165. </div>
  166. </div>
  167. <!-- 扫描绑定 -->
  168. <div class="scan-bind-section">
  169. <div class="input-group">
  170. <label>扫描箱/</label>
  171. <div class="input-with-scan">
  172. <input
  173. v-model="scannedUnit"
  174. placeholder="请扫描处理单元条码"
  175. @keyup.enter="bindUnit"
  176. />
  177. <button @click="bindUnit" class="scan-btn">绑定</button>
  178. </div>
  179. </div>
  180. </div>
  181. <!-- 已绑定单元列表 -->
  182. <div class="bound-units" v-if="currentPallet.scannedUnits.length">
  183. <div class="section-header">
  184. <h4>已绑定单元 ({{ currentPallet.scannedUnits.length }})</h4>
  185. </div>
  186. <div class="unit-list">
  187. <div v-for="unit in currentPallet.scannedUnits" :key="unit" class="unit-item">
  188. {{ unit }}
  189. </div>
  190. </div>
  191. </div>
  192. <!-- 打印标签 -->
  193. <div class="print-section">
  194. <div class="input-group">
  195. <label>打印机</label>
  196. <select v-model="palletForm.printerName">
  197. <option value="">请选择打印机</option>
  198. <option value="PRINTER_01">打印机01</option>
  199. <option value="PRINTER_02">打印机02</option>
  200. </select>
  201. </div>
  202. <button @click="printPalletLabel" class="print-btn" :disabled="!palletForm.printerName">
  203. 打印托盘标签
  204. </button>
  205. </div>
  206. </div>
  207. </div>
  208. <!-- 申请单发料 -->
  209. <div class="request-issue" v-if="selectedFunction === 'request'">
  210. <!-- 申请单输入 -->
  211. <div class="input-section" v-if="!requestMaterials.length">
  212. <div class="input-group">
  213. <label>申请单号</label>
  214. <div class="input-with-scan">
  215. <input
  216. v-model="requestIssueForm.notifyNo"
  217. placeholder="请输入申请单号"
  218. @keyup.enter="loadRequestMaterials"
  219. />
  220. <button @click="loadRequestMaterials" class="scan-btn">确认</button>
  221. </div>
  222. </div>
  223. </div>
  224. <!-- 申请单物料列表 -->
  225. <div class="materials-section" v-if="requestMaterials.length">
  226. <div class="section-header">
  227. <h3>申请单物料 ({{ requestIssueForm.notifyNo }})</h3>
  228. <button @click="resetRequest" class="reset-btn">重新选择</button>
  229. </div>
  230. <div class="material-list">
  231. <div
  232. v-for="material in requestMaterials"
  233. :key="`${material.partNo}-${material.itemNo}`"
  234. class="material-item"
  235. :class="{ selected: selectedRequestMaterial.itemNo === material.itemNo }"
  236. @click="selectRequestMaterial(material)"
  237. >
  238. <div class="material-info">
  239. <div class="part-no">{{ material.partNo }}</div>
  240. <div class="part-desc">{{ material.partDesc }}</div>
  241. <div class="work-order">工单: {{ material.workOrderNo }}</div>
  242. <div class="qty-info">
  243. 申请: {{ material.requestQty }} |
  244. 已发: {{ material.issuedQty }} |
  245. 剩余: {{ material.remainQty }}
  246. </div>
  247. </div>
  248. <div class="material-status">
  249. <span v-if="material.remainQty > 0" class="status-pending">待发料</span>
  250. <span v-else class="status-complete">已完成</span>
  251. </div>
  252. </div>
  253. </div>
  254. </div>
  255. <!-- 扫描标签和发料 -->
  256. <div class="scan-section" v-if="selectedRequestMaterial">
  257. <div class="section-header">
  258. <h3>扫描物料标签</h3>
  259. </div>
  260. <div class="input-group">
  261. <label>物料标签</label>
  262. <div class="input-with-scan">
  263. <input
  264. v-model="scannedLabel"
  265. placeholder="请扫描物料标签"
  266. @keyup.enter="parseMaterialLabel"
  267. />
  268. <button @click="parseMaterialLabel" class="scan-btn">解析</button>
  269. </div>
  270. </div>
  271. <!-- 标签信息和发料确认 -->
  272. <div class="label-info" v-if="labelInfo">
  273. <div class="info-row">
  274. <span class="label">物料编码:</span>
  275. <span class="value">{{ labelInfo.partNo }}</span>
  276. </div>
  277. <div class="info-row">
  278. <span class="label">批次号:</span>
  279. <span class="value">{{ labelInfo.batchNo }}</span>
  280. </div>
  281. <div class="info-row">
  282. <span class="label">可用数量:</span>
  283. <span class="value">{{ labelInfo.availableQty }}</span>
  284. </div>
  285. <div class="qty-input">
  286. <div class="input-group">
  287. <label>发料数量</label>
  288. <input
  289. v-model="issueQty"
  290. type="number"
  291. :max="Math.min(selectedRequestMaterial.remainQty, labelInfo.availableQty)"
  292. placeholder="请输入发料数量"
  293. />
  294. </div>
  295. <div class="input-group">
  296. <label>备注</label>
  297. <input v-model="requestIssueForm.remark" placeholder="可选" />
  298. </div>
  299. <button @click="confirmRequestIssue" class="confirm-btn" :disabled="!issueQty">
  300. 确认发料 (同步IFS)
  301. </button>
  302. </div>
  303. </div>
  304. </div>
  305. </div>
  306. <!-- 加载提示 -->
  307. <div class="loading" v-if="loading">
  308. <div class="loading-spinner"></div>
  309. <div class="loading-text">{{ loadingText }}</div>
  310. </div>
  311. <!-- 消息提示 -->
  312. <div class="message" v-if="message" :class="messageType">
  313. {{ message }}
  314. </div>
  315. </div>
  316. </div>
  317. </div>
  318. </template>
  319. <script>
  320. import {
  321. getWorkOrderMaterials,
  322. parseMaterialLabel,
  323. directIssue,
  324. getRequestMaterials,
  325. requestIssue,
  326. createPickingPallet,
  327. bindUnitsToPallet,
  328. printPalletLabel,
  329. getPalletInfo
  330. } from '@/api/production/production-issue'
  331. export default {
  332. name: 'ProductionIssuePDA',
  333. data() {
  334. return {
  335. selectedFunction: null,
  336. loading: false,
  337. loadingText: '',
  338. message: '',
  339. messageType: 'info',
  340. // 直接发料
  341. directIssueForm: {
  342. site: this.$store.state.user.site,
  343. workOrderNo: '',
  344. operatorName: 'PDA_USER',
  345. remark: '',
  346. selectedMaterials: []
  347. },
  348. workOrderMaterials: [],
  349. selectedMaterial: {
  350. partNo: '',
  351. partDesc: '',
  352. requiredQty: 0,
  353. issuedQty: 0,
  354. remainQty: 0
  355. },
  356. // 申请单发料
  357. requestIssueForm: {
  358. site: this.$store.state.user.site,
  359. notifyNo: '',
  360. workOrderNo: '',
  361. operatorName: 'PDA_USER',
  362. remark: '',
  363. selectedMaterials: []
  364. },
  365. requestMaterials: [],
  366. selectedRequestMaterial: null,
  367. // 托盘拣选
  368. palletForm: {
  369. site: this.$store.state.user.site,
  370. notifyNo: '',
  371. operatorName: 'PDA_USER',
  372. printerName: '',
  373. remark: ''
  374. },
  375. currentPallet: {},
  376. scannedUnit: '',
  377. // 通用
  378. scannedLabel: '',
  379. labelInfo: null,
  380. issueQty: null
  381. }
  382. },
  383. computed: {
  384. functionTitle() {
  385. if (!this.selectedFunction) return '生产发料';
  386. if (this.selectedFunction === 'direct') return '直接发料';
  387. if (this.selectedFunction === 'picking') return '拣选装托盘';
  388. if (this.selectedFunction === 'request') return '申请单发料';
  389. return '生产发料';
  390. }
  391. },
  392. methods: {
  393. selectFunction(func) {
  394. this.selectedFunction = func
  395. this.resetAll()
  396. },
  397. goBack() {
  398. if (!this.selectedFunction) {
  399. this.$router.push('/')
  400. } else {
  401. this.selectedFunction = null
  402. this.resetAll()
  403. }
  404. },
  405. resetAll() {
  406. this.workOrderMaterials = []
  407. this.requestMaterials = []
  408. this.selectedMaterial = null
  409. this.selectedRequestMaterial = null
  410. this.currentPallet = {}
  411. this.scannedLabel = ''
  412. this.labelInfo = null
  413. this.issueQty = null
  414. this.message = ''
  415. },
  416. // 直接发料相关方法
  417. async loadWorkOrderMaterials() {
  418. if (!this.directIssueForm.workOrderNo) {
  419. this.$message.error('请输入工单号', 'error')
  420. return
  421. }
  422. this.loading = true
  423. this.loadingText = '加载工单物料...'
  424. try {
  425. const response = await getWorkOrderMaterials({
  426. site: this.directIssueForm.site,
  427. workOrderNo: this.directIssueForm.workOrderNo
  428. })
  429. if (response.data.code === 0) {
  430. this.workOrderMaterials = response.data.materials || []
  431. if (this.workOrderMaterials.length === 0) {
  432. this.showMessage('该工单没有物料需求', 'warning')
  433. }
  434. } else {
  435. this.showMessage(response.data.msg, 'error')
  436. }
  437. } catch (error) {
  438. this.showMessage('加载工单物料失败', 'error')
  439. } finally {
  440. this.loading = false
  441. }
  442. },
  443. selectMaterial(material) {
  444. if (material.remainQty <= 0) {
  445. this.showMessage('该物料已发料完成', 'warning')
  446. return
  447. }
  448. this.selectedMaterial = material
  449. this.scannedLabel = ''
  450. this.labelInfo = null
  451. this.issueQty = null
  452. },
  453. resetWorkOrder() {
  454. this.directIssueForm.workOrderNo = ''
  455. this.workOrderMaterials = []
  456. this.selectedMaterial = null
  457. this.scannedLabel = ''
  458. this.labelInfo = null
  459. this.issueQty = null
  460. },
  461. async confirmDirectIssue() {
  462. if (!this.issueQty || this.issueQty <= 0) {
  463. this.showMessage('请输入有效的发料数量', 'error')
  464. return
  465. }
  466. this.loading = true
  467. this.loadingText = '发料中...'
  468. try {
  469. const issueData = {
  470. ...this.directIssueForm,
  471. selectedMaterials: [{
  472. ...this.selectedMaterial,
  473. issueQty: this.issueQty
  474. }],
  475. scannedLabel: this.scannedLabel
  476. }
  477. const response = await directIssue(issueData)
  478. if (response.data.code === 0) {
  479. this.showMessage('发料成功', 'success')
  480. // 刷新物料列表
  481. await this.loadWorkOrderMaterials()
  482. // 重置选择
  483. this.selectedMaterial = null
  484. this.scannedLabel = ''
  485. this.labelInfo = null
  486. this.issueQty = null
  487. } else {
  488. this.showMessage(response.data.msg, 'error')
  489. }
  490. } catch (error) {
  491. this.showMessage('发料失败', 'error')
  492. } finally {
  493. this.loading = false
  494. }
  495. },
  496. // 申请单发料相关方法
  497. async loadRequestMaterials() {
  498. if (!this.requestIssueForm.notifyNo) {
  499. this.showMessage('请输入申请单号', 'error')
  500. return
  501. }
  502. this.loading = true
  503. this.loadingText = '加载申请单物料...'
  504. try {
  505. const response = await getRequestMaterials({
  506. site: this.requestIssueForm.site,
  507. notifyNo: this.requestIssueForm.notifyNo
  508. })
  509. if (response.data.code === 0) {
  510. this.requestMaterials = response.data.materials || []
  511. if (this.requestMaterials.length === 0) {
  512. this.showMessage('该申请单没有物料需求', 'warning')
  513. }
  514. } else {
  515. this.showMessage(response.data.msg, 'error')
  516. }
  517. } catch (error) {
  518. this.showMessage('加载申请单物料失败', 'error')
  519. } finally {
  520. this.loading = false
  521. }
  522. },
  523. selectRequestMaterial(material) {
  524. if (material.remainQty <= 0) {
  525. this.showMessage('该物料已发料完成', 'warning')
  526. return
  527. }
  528. this.selectedRequestMaterial = material
  529. this.requestIssueForm.workOrderNo = material.workOrderNo
  530. this.scannedLabel = ''
  531. this.labelInfo = null
  532. this.issueQty = null
  533. },
  534. resetRequest() {
  535. this.requestIssueForm.notifyNo = ''
  536. this.requestIssueForm.workOrderNo = ''
  537. this.requestMaterials = []
  538. this.selectedRequestMaterial = null
  539. this.scannedLabel = ''
  540. this.labelInfo = null
  541. this.issueQty = null
  542. },
  543. async confirmRequestIssue() {
  544. if (!this.issueQty || this.issueQty <= 0) {
  545. this.showMessage('请输入有效的发料数量', 'error')
  546. return
  547. }
  548. this.loading = true
  549. this.loadingText = '发料中,同步IFS...'
  550. try {
  551. const issueData = {
  552. ...this.requestIssueForm,
  553. selectedMaterials: [{
  554. ...this.selectedRequestMaterial,
  555. issueQty: this.issueQty
  556. }],
  557. scannedLabel: this.scannedLabel
  558. }
  559. const response = await requestIssue(issueData)
  560. if (response.data.code === 0) {
  561. this.showMessage('发料成功,已同步到IFS', 'success')
  562. // 刷新物料列表
  563. await this.loadRequestMaterials()
  564. // 重置选择
  565. this.selectedRequestMaterial = null
  566. this.scannedLabel = ''
  567. this.labelInfo = null
  568. this.issueQty = null
  569. } else {
  570. this.showMessage(response.data.msg, 'error')
  571. }
  572. } catch (error) {
  573. this.showMessage('发料失败', 'error')
  574. } finally {
  575. this.loading = false
  576. }
  577. },
  578. // 托盘拣选相关方法
  579. async createPallet() {
  580. if (!this.palletForm.notifyNo) {
  581. this.showMessage('请输入申请单号', 'error')
  582. return
  583. }
  584. this.loading = true
  585. this.loadingText = '创建托盘...'
  586. try {
  587. const response = await createPickingPallet({
  588. ...this.palletForm,
  589. palletType: 'PALLET'
  590. })
  591. if (response.data.code === 0) {
  592. this.currentPallet = {
  593. palletId: response.data.palletId,
  594. notifyNo: this.palletForm.notifyNo,
  595. scannedUnits: [],
  596. unitCount: 0
  597. }
  598. this.showMessage('托盘创建成功: ' + response.data.palletId, 'success')
  599. } else {
  600. this.showMessage(response.data.msg, 'error')
  601. }
  602. } catch (error) {
  603. this.showMessage('创建托盘失败', 'error')
  604. } finally {
  605. this.loading = false
  606. }
  607. },
  608. async bindUnit() {
  609. if (!this.scannedUnit) {
  610. this.showMessage('请扫描处理单元条码', 'error')
  611. return
  612. }
  613. if (this.currentPallet.scannedUnits.includes(this.scannedUnit)) {
  614. this.showMessage('该处理单元已绑定', 'warning')
  615. this.scannedUnit = ''
  616. return
  617. }
  618. this.loading = true
  619. this.loadingText = '绑定处理单元...'
  620. try {
  621. const response = await bindUnitsToPallet({
  622. site: this.palletForm.site,
  623. palletId: this.currentPallet.palletId,
  624. scannedUnits: [this.scannedUnit]
  625. })
  626. if (response.data.code === 0) {
  627. this.currentPallet.scannedUnits.push(this.scannedUnit)
  628. this.currentPallet.unitCount = this.currentPallet.scannedUnits.length
  629. this.showMessage('绑定成功', 'success')
  630. this.scannedUnit = ''
  631. } else {
  632. this.showMessage(response.data.msg, 'error')
  633. }
  634. } catch (error) {
  635. this.showMessage('绑定失败', 'error')
  636. } finally {
  637. this.loading = false
  638. }
  639. },
  640. async printPalletLabel() {
  641. if (!this.palletForm.printerName) {
  642. this.showMessage('请选择打印机', 'error')
  643. return
  644. }
  645. this.loading = true
  646. this.loadingText = '打印标签...'
  647. try {
  648. const response = await printPalletLabel({
  649. site: this.palletForm.site,
  650. palletId: this.currentPallet.palletId,
  651. printerName: this.palletForm.printerName
  652. })
  653. if (response.data.code === 0) {
  654. this.showMessage('标签打印成功', 'success')
  655. } else {
  656. this.showMessage(response.data.msg, 'error')
  657. }
  658. } catch (error) {
  659. this.showMessage('打印失败', 'error')
  660. } finally {
  661. this.loading = false
  662. }
  663. },
  664. // 通用方法
  665. async parseMaterialLabel() {
  666. if (!this.scannedLabel) {
  667. this.showMessage('请扫描物料标签', 'error')
  668. return
  669. }
  670. this.loading = true
  671. this.loadingText = '解析标签...'
  672. try {
  673. const response = await parseMaterialLabel({
  674. site: this.directIssueForm.site || this.requestIssueForm.site,
  675. scannedLabel: this.scannedLabel
  676. })
  677. if (response.data.code === 0 && response.data.labelInfo) {
  678. this.labelInfo = response.data.labelInfo
  679. this.issueQty = null // 重置发料数量
  680. this.showMessage('标签解析成功', 'success')
  681. } else {
  682. this.showMessage('无法解析标签或标签无效', 'error')
  683. this.labelInfo = null
  684. }
  685. } catch (error) {
  686. this.showMessage('标签解析失败', 'error')
  687. this.labelInfo = null
  688. } finally {
  689. this.loading = false
  690. }
  691. },
  692. showMessage(text, type = 'info') {
  693. this.message = text
  694. this.messageType = type
  695. setTimeout(() => {
  696. this.message = ''
  697. }, 3000)
  698. }
  699. }
  700. }
  701. </script>
  702. <style scoped>
  703. .production-issue-pda {
  704. padding: 10px;
  705. font-family: Arial, sans-serif;
  706. background-color: #f5f5f5;
  707. min-height: 100vh;
  708. }
  709. /* 功能选择 */
  710. .function-selector {
  711. display: flex;
  712. flex-direction: column;
  713. gap: 15px;
  714. padding: 20px 0;
  715. }
  716. .function-card {
  717. background: white;
  718. border-radius: 8px;
  719. padding: 20px;
  720. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  721. cursor: pointer;
  722. transition: all 0.3s;
  723. text-align: center;
  724. }
  725. .function-card:hover {
  726. transform: translateY(-2px);
  727. box-shadow: 0 4px 8px rgba(0,0,0,0.15);
  728. }
  729. .function-icon {
  730. font-size: 48px;
  731. margin-bottom: 10px;
  732. }
  733. .function-title {
  734. font-size: 18px;
  735. font-weight: bold;
  736. margin-bottom: 8px;
  737. color: #333;
  738. }
  739. .function-desc {
  740. font-size: 14px;
  741. color: #666;
  742. }
  743. /* 输入区域 */
  744. .input-section, .materials-section, .scan-section, .pallet-info, .print-section {
  745. background: white;
  746. border-radius: 8px;
  747. padding: 15px;
  748. margin-bottom: 15px;
  749. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  750. }
  751. .section-header {
  752. display: flex;
  753. justify-content: space-between;
  754. align-items: center;
  755. margin-bottom: 15px;
  756. padding-bottom: 10px;
  757. border-bottom: 1px solid #eee;
  758. }
  759. .section-header h3, .section-header h4 {
  760. margin: 0;
  761. color: #333;
  762. }
  763. .reset-btn {
  764. background: #17b3a3;
  765. color: white;
  766. border: none;
  767. padding: 6px 12px;
  768. border-radius: 4px;
  769. cursor: pointer;
  770. font-size: 12px;
  771. }
  772. .input-group {
  773. margin-bottom: 15px;
  774. }
  775. .input-group label {
  776. display: block;
  777. margin-bottom: 5px;
  778. font-weight: bold;
  779. color: #333;
  780. }
  781. .input-with-scan {
  782. display: flex;
  783. gap: 10px;
  784. }
  785. .input-with-scan input {
  786. flex: 1;
  787. padding: 10px;
  788. border: 1px solid #ddd;
  789. border-radius: 4px;
  790. font-size: 16px;
  791. }
  792. .scan-btn, .confirm-btn, .print-btn {
  793. background: #17b3a3;
  794. color: white;
  795. border: none;
  796. padding: 10px 15px;
  797. border-radius: 4px;
  798. cursor: pointer;
  799. font-size: 14px;
  800. white-space: nowrap;
  801. }
  802. .scan-btn:hover, .confirm-btn:hover, .print-btn:hover {
  803. background: #13998c;
  804. }
  805. .confirm-btn:disabled, .print-btn:disabled {
  806. background: #6c757d;
  807. cursor: not-allowed;
  808. }
  809. /* 物料列表 */
  810. .material-list {
  811. display: flex;
  812. flex-direction: column;
  813. gap: 10px;
  814. }
  815. .material-item {
  816. display: flex;
  817. justify-content: space-between;
  818. align-items: center;
  819. padding: 15px;
  820. border: 1px solid #ddd;
  821. border-radius: 6px;
  822. cursor: pointer;
  823. transition: all 0.3s;
  824. }
  825. .material-item:hover {
  826. border-color: #007bff;
  827. background-color: #f8f9fa;
  828. }
  829. .material-item.selected {
  830. border-color: #007bff;
  831. background-color: #e3f2fd;
  832. }
  833. .material-info {
  834. flex: 1;
  835. }
  836. .part-no {
  837. font-weight: bold;
  838. font-size: 16px;
  839. color: #333;
  840. margin-bottom: 4px;
  841. }
  842. .part-desc {
  843. color: #666;
  844. font-size: 14px;
  845. margin-bottom: 4px;
  846. }
  847. .work-order {
  848. color: #007bff;
  849. font-size: 12px;
  850. margin-bottom: 4px;
  851. }
  852. .qty-info {
  853. font-size: 12px;
  854. color: #666;
  855. }
  856. .material-status {
  857. text-align: right;
  858. }
  859. .status-pending {
  860. background: #ffc107;
  861. color: #212529;
  862. padding: 4px 8px;
  863. border-radius: 12px;
  864. font-size: 12px;
  865. }
  866. .status-complete {
  867. background: #28a745;
  868. color: white;
  869. padding: 4px 8px;
  870. border-radius: 12px;
  871. font-size: 12px;
  872. }
  873. /* 标签信息 */
  874. .label-info, .info-card {
  875. background: #f8f9fa;
  876. border-radius: 6px;
  877. padding: 15px;
  878. margin-bottom: 15px;
  879. }
  880. .info-row {
  881. display: flex;
  882. justify-content: space-between;
  883. margin-bottom: 8px;
  884. padding: 5px 0;
  885. border-bottom: 1px solid #eee;
  886. }
  887. .info-row:last-child {
  888. border-bottom: none;
  889. margin-bottom: 0;
  890. }
  891. .info-row .label {
  892. font-weight: bold;
  893. color: #666;
  894. }
  895. .info-row .value {
  896. color: #333;
  897. }
  898. /* 数量输入 */
  899. .qty-input {
  900. margin-top: 15px;
  901. }
  902. .qty-input input[type="number"] {
  903. width: 100%;
  904. padding: 10px;
  905. border: 1px solid #ddd;
  906. border-radius: 4px;
  907. font-size: 16px;
  908. }
  909. /* 托盘相关 */
  910. .scan-bind-section {
  911. margin: 20px 0;
  912. }
  913. .bound-units {
  914. margin-top: 20px;
  915. }
  916. .unit-list {
  917. display: flex;
  918. flex-direction: column;
  919. gap: 8px;
  920. max-height: 200px;
  921. overflow-y: auto;
  922. }
  923. .unit-item {
  924. background: #e9ecef;
  925. padding: 10px;
  926. border-radius: 4px;
  927. font-family: monospace;
  928. font-size: 14px;
  929. }
  930. /* 加载和消息 */
  931. .loading {
  932. position: fixed;
  933. top: 50%;
  934. left: 50%;
  935. transform: translate(-50%, -50%);
  936. background: rgba(0,0,0,0.8);
  937. color: white;
  938. padding: 20px;
  939. border-radius: 8px;
  940. text-align: center;
  941. z-index: 1000;
  942. }
  943. .loading-spinner {
  944. width: 40px;
  945. height: 40px;
  946. border: 4px solid #f3f3f3;
  947. border-top: 4px solid #007bff;
  948. border-radius: 50%;
  949. animation: spin 1s linear infinite;
  950. margin: 0 auto 10px;
  951. }
  952. @keyframes spin {
  953. 0% { transform: rotate(0deg); }
  954. 100% { transform: rotate(360deg); }
  955. }
  956. .loading-text {
  957. font-size: 14px;
  958. }
  959. .message {
  960. position: fixed;
  961. top: 20px;
  962. left: 50%;
  963. transform: translateX(-50%);
  964. padding: 12px 20px;
  965. border-radius: 6px;
  966. font-weight: bold;
  967. z-index: 1001;
  968. max-width: 90%;
  969. text-align: center;
  970. }
  971. .message.success {
  972. background: #d4edda;
  973. color: #155724;
  974. border: 1px solid #c3e6cb;
  975. }
  976. .message.error {
  977. background: #f8d7da;
  978. color: #721c24;
  979. border: 1px solid #f5c6cb;
  980. }
  981. .message.warning {
  982. background: #fff3cd;
  983. color: #856404;
  984. border: 1px solid #ffeaa7;
  985. }
  986. .message.info {
  987. background: #d1ecf1;
  988. color: #0c5460;
  989. border: 1px solid #bee5eb;
  990. }
  991. /* 响应式设计 */
  992. @media (max-width: 768px) {
  993. .production-issue-pda {
  994. padding: 5px;
  995. }
  996. .function-card {
  997. padding: 15px;
  998. }
  999. .function-icon {
  1000. font-size: 36px;
  1001. }
  1002. .input-with-scan {
  1003. flex-direction: column;
  1004. }
  1005. .material-item {
  1006. flex-direction: column;
  1007. align-items: flex-start;
  1008. gap: 10px;
  1009. }
  1010. .material-status {
  1011. align-self: flex-end;
  1012. }
  1013. }
  1014. </style>