scanMixMaterials.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. <template>
  2. <gui-page :custom-header="true" :header-class="['gui-theme-background-color']">
  3. <template #gHeader>
  4. <view style="height:44px;" class="gui-flex gui-nowrap gui-rows gui-align-items-center">
  5. <!-- 使用组件实现返回按钮及返回首页按钮 -->
  6. <text style="font-size:44rpx;" class="gui-header-leader-btns gui-color-white font-icons"
  7. @tap="goBack">&#xe6c5;</text>
  8. <!-- 导航文本此处也可以是其他自定义内容 -->
  9. <text
  10. class="gui-h4 gui-blod gui-flex1 gui-text-center gui-ellipsis gui-color-white gui-primary-text">备料明细</text>
  11. <!-- 此处加一个右侧展位元素与左侧同宽,实现标题居中 -->
  12. <!-- 实际宽度请根据自己情况设置 -->
  13. <view style="width:40px;" />
  14. <!-- 如果右侧有其他内容可以利用条件编译和定位来实现-->
  15. </view>
  16. </template>
  17. <template #gBody>
  18. <view class="list-content">
  19. <view class="scan">
  20. <view class="scan-card">
  21. <uni-easyinput ref="easyinput" v-model="scanBatchNumber" :input-border="false"
  22. :clearable="false" type="text" focus @focus="handleInputFocus" placeholder="请扫描物料二维码"
  23. @confirm="handleKeydown" />
  24. <text class="font-icons" @click="handleScanMaterial">&#xe6b7;</text>
  25. </view>
  26. </view>
  27. <view class="tabs">
  28. <div class="tabs-list">
  29. <text :class="!isBefore?'tabs-item-active':'tabs-item'" @click="isBefore = false">备料需求</text>
  30. <text :class="isBefore?'tabs-item-active':'tabs-item'" @click="isBefore = true">已备物料</text>
  31. </div>
  32. </view>
  33. <view v-if="!isBefore" class="custom-table">
  34. <uni-table border stripe empty-text="暂无更多数据">
  35. <!-- 表头行 -->
  36. <uni-tr class="custom-table-head">
  37. <uni-th align="center" width="180px">物料</uni-th>
  38. <uni-th align="center" width="120px">应备数</uni-th>
  39. <uni-th align="center" width="120px">已备数</uni-th>
  40. </uni-tr>
  41. <!-- 表格数据行 -->
  42. <uni-tr v-for="(item, key) in tableData" :key="key" @click="handleToDetails(item)">
  43. <uni-td align="center">{{ item.materialNo }}({{ item.materialName }})</uni-td>
  44. <uni-td align="center">{{ item.shouldPrepareQty }}</uni-td>
  45. <uni-td align="center"
  46. style="color: orange;font-weight: bold;">{{ item.preparedQty }}</uni-td>
  47. </uni-tr>
  48. </uni-table>
  49. </view>
  50. <view v-else class="custom-table">
  51. <uni-table border stripe empty-text="暂无更多数据">
  52. <uni-tr class="custom-table-head">
  53. <uni-th align="center" width="180px">物料</uni-th>
  54. <uni-th align="center" width="120px">应备数</uni-th>
  55. <uni-th align="center" width="120px">已备数</uni-th>
  56. </uni-tr>
  57. <uni-tr v-for="(item, key) in beforeTableData" :key="key">
  58. <uni-td align="center">{{ item.materialNo }}({{ item.materialName }})</uni-td>
  59. <uni-td align="center">{{ item.shouldPrepareQty }}</uni-td>
  60. <uni-td align="center">{{ item.preparedQty }}</uni-td>
  61. </uni-tr>
  62. </uni-table>
  63. </view>
  64. <view class="card-list-item"
  65. style="margin: 12px 0;display: grid;grid-template-columns: 1fr;grid-template-rows: 1fr;"
  66. @click="handleBtnLight">
  67. <view class="sign-btn">
  68. <text
  69. :style="['font-size: 14px;font-weight: bold;', isLightText?'color: rgba(0, 160, 233, 1)':'']">签收</text>
  70. <view style="width: 0px;">
  71. <uni-easyinput ref="signInput" v-model="signText" @focus="handleInputFocus"
  72. :input-border="false" :clearable="false" type="text" @confirm="handleComplete" />
  73. </view>
  74. </view>
  75. </view>
  76. <gui-modal ref="modalForm" :custom-class="['gui-bg-white', 'gui-dark-bg-level-3', 'gui-border-radius']"
  77. title="提示">
  78. <template #content>
  79. <view class="gui-padding gui-bg-gray gui-dark-bg-level-2">
  80. <text class="gui-block gui-text-center gui-text gui-color-gray"
  81. style="line-height:100rpx; padding:10rpx;">备料超出,是否拆分?</text>
  82. </view>
  83. </template>
  84. <!-- 利用 flex 布局 可以放置多个自定义按钮哦 -->
  85. <template #btns>
  86. <view class="gui-flex gui-row gui-space-between operation-flex">
  87. <view hover-class="gui-tap" class="modal-btns gui-flex1" @tap="modalForm.close()">
  88. <text class="modal-btns gui-color-gray">取消</text>
  89. </view>
  90. <view class="line" />
  91. <view hover-class="gui-tap" class="modal-btns gui-flex1" @tap="handleSplitMaterial">
  92. <text class="modal-btns gui-primary-color">确认</text>
  93. </view>
  94. </view>
  95. </template>
  96. </gui-modal>
  97. <uni-popup ref="errorTip" type="dialog">
  98. <uni-popup-dialog type="error" cancel-text="关闭" confirm-text="确认" title="提示"
  99. :content="errorTipMessage" @confirm="handleCloseErrorTipsModal"
  100. @close="handleCloseErrorTipsModal" />
  101. </uni-popup>
  102. </view>
  103. </template>
  104. </gui-page>
  105. </template>
  106. <script>
  107. import {
  108. onReachBottom
  109. } from '@dcloudio/uni-app'
  110. import {
  111. ref,
  112. onMounted,
  113. defineComponent,
  114. onBeforeMount
  115. } from 'vue'
  116. export default defineComponent({
  117. setup(options) {
  118. const popup = ref()
  119. const queryParams = ref({
  120. pageSize: 20,
  121. pageNo: 1,
  122. masterId: options?.id ?? '',
  123. wmsProductionWorkOrderBomId: options?.id ?? ''
  124. })
  125. const errorTip = ref('')
  126. const errorTipMessage = ref('')
  127. const easyinput = ref('')
  128. const errorState = ref(0)
  129. const modalForm = ref()
  130. const parentRow = uni.getStorageSync('masterId') ?? {}
  131. const masterId = ref('')
  132. const signInput = ref()
  133. const signText = ref('')
  134. const isLightText = ref('')
  135. const scanBatchNumber = ref('')
  136. const isBefore = ref(false)
  137. const tableData = ref([])
  138. const beforeTableData = ref([])
  139. const scanMaterialList = ref([])
  140. // 当前处理的数据行
  141. const fdIndex = ref(-1)
  142. // 当前扫描的物料
  143. const currentScanMaterial = ref([])
  144. onBeforeMount(() => {
  145. masterId.value = JSON.parse(parentRow)?.id
  146. })
  147. const search = function() {
  148. uni.$reqGet('getPrepareMatersDetails', {
  149. masterId: masterId.value
  150. })
  151. .then(({
  152. code,
  153. data,
  154. msg
  155. }) => {
  156. if (code === 0) {
  157. uni.setStorageSync('ids', JSON.stringify(data?.list[0]))
  158. tableData.value.length = 0
  159. beforeTableData.value.length = 0
  160. data?.list?.forEach(item => {
  161. if (item.shouldPrepareQty === item.preparedQty) {
  162. beforeTableData.value.push(item)
  163. } else {
  164. tableData.value.push(item)
  165. }
  166. })
  167. } else {
  168. uni.showToast({
  169. title: msg,
  170. icon: 'none',
  171. duration: 2000
  172. })
  173. }
  174. })
  175. }
  176. onMounted(() => {
  177. search()
  178. })
  179. const goBack = function() {
  180. if (uni.getStorageSync('ids')) {
  181. uni.removeStorageSync('ids')
  182. }
  183. uni.$goBack('/pages/workbranch/warehouse/production/prepareMaterials')
  184. }
  185. const handleScanMaterial = function() {
  186. // #ifdef APP-PLUS
  187. const mpaasScanModule = uni.requireNativePlugin('Mpaas-Scan-Module')
  188. mpaasScanModule.mpaasScan({
  189. // 扫码识别类型,参数可多选,qrCode、barCode,不设置,默认识别所有
  190. 'scanType': ['qrCode', 'barCode'],
  191. // 是否隐藏相册,默认false不隐藏
  192. 'hideAlbum': false
  193. },
  194. (ret) => {
  195. if (ret.resp_code === 1000) {
  196. uni.$reqGet('scanPrepareMaterial', {
  197. qrCode: ret.resp_result
  198. })
  199. .then(async ({
  200. code,
  201. data,
  202. msg
  203. }) => {
  204. if (code === 0) {
  205. scanBatchNumber.value = ret.resp_result
  206. if (Object.prototype.toString.call(data) ===
  207. '[object Array]') {
  208. fdIndex.value = tableData.value.findIndex(item => item
  209. ?.materialNo === data[0]?.materialNo)
  210. }
  211. if (fdIndex.value !== -1) {
  212. currentScanMaterial.value = data[0] ?? []
  213. if (tableData.value[fdIndex.value].shouldPrepareQty >
  214. tableData.value[fdIndex.value].preparedQty) {
  215. uni.$reqPost('scanToPrepare', {
  216. id: masterId.value,
  217. materials: data
  218. })
  219. .then(res => {
  220. if (res.code === 0) {
  221. if (res.data === 0) {
  222. search()
  223. setInputFocus()
  224. } else if (res.data === 1) {
  225. modalForm.value.open()
  226. }
  227. } else {
  228. // #ifdef APP-PLUS
  229. plus.device.beep(2)
  230. // #endif
  231. errorTipMessage.value = res.msg
  232. errorTip.value.open()
  233. errorState.value = 0
  234. }
  235. })
  236. }
  237. } else {
  238. // #ifdef APP-PLUS
  239. plus.device.beep(2)
  240. // #endif
  241. errorTipMessage.value = '请扫描所需物料的物料条码'
  242. errorTip.value.open()
  243. errorState.value = 0
  244. }
  245. } else {
  246. // #ifdef APP-PLUS
  247. plus.device.beep(2)
  248. // #endif
  249. errorTipMessage.value = msg
  250. errorTip.value.open()
  251. errorState.value = 0
  252. }
  253. })
  254. }
  255. })
  256. // #endif
  257. }
  258. const handleToNavigate = function() {
  259. uni.navigateTo({
  260. url: '/pages/workbranch/warehouse/production/materialIssuance'
  261. })
  262. }
  263. const handleComplete = function(e) {
  264. // #ifdef APP-PLUS
  265. // 扫描员工工号
  266. uni.$reqPost('scanPrepareMaterialSign', {
  267. encodedEmployeeId: e,
  268. id: masterId.value
  269. })
  270. .then(({
  271. code,
  272. data,
  273. msg
  274. }) => {
  275. if (code === 0) {
  276. uni.showToast({
  277. title: '签收成功',
  278. icon: 'none',
  279. duration: 2000
  280. })
  281. setTimeout(() => {
  282. goBack();
  283. }, 500)
  284. } else {
  285. // #ifdef APP-PLUS
  286. plus.device.beep(2)
  287. // #endif
  288. errorTipMessage.value = msg
  289. errorTip.value.open()
  290. errorState.value = -1
  291. }
  292. isLightText.value = false
  293. signText.value = ''
  294. })
  295. // #endif
  296. }
  297. // 物料拆分
  298. const handleSplitMaterial = function() {
  299. uni.$reqPost('prepareSplit', {
  300. prepareId: masterId.value,
  301. id: scanBatchNumber.value,
  302. inQty: currentScanMaterial.value?.receiptQty,
  303. splitQty: tableData.value[fdIndex.value].preparedQty - currentScanMaterial.value
  304. ?.receiptQty
  305. })
  306. .then(res => {
  307. search()
  308. modalForm.value.close()
  309. if (res.code === 0) {
  310. // 调取打印机拆分物料后打印标签
  311. uni.showToast({
  312. title: '拆分完毕',
  313. icon: 'none',
  314. duration: 2000
  315. })
  316. } else {
  317. // #ifdef APP-PLUS
  318. plus.device.beep(2)
  319. // #endif
  320. errorTipMessage.value = res.msg
  321. errorTip.value.open()
  322. errorState.value = 0
  323. }
  324. })
  325. }
  326. const handleKeydown = function(e) {
  327. uni.$reqGet('scanPrepareMaterial', {
  328. qrCode: e
  329. })
  330. .then(async ({
  331. code,
  332. data,
  333. msg
  334. }) => {
  335. scanBatchNumber.value = e
  336. if (Object.prototype.toString.call(data) === '[object Array]') {
  337. fdIndex.value = tableData.value.findIndex(item => item?.materialNo ===
  338. data[0]?.materialNo)
  339. }
  340. if (fdIndex.value !== -1) {
  341. currentScanMaterial.value = data[0] ?? []
  342. if (tableData.value[fdIndex.value].shouldPrepareQty > tableData.value[
  343. fdIndex.value].preparedQty) {
  344. uni.$reqPost('scanToPrepare', {
  345. id: masterId.value,
  346. materials: data
  347. })
  348. .then(res => {
  349. if (res.code === 0) {
  350. if (res.data === 0) {
  351. search()
  352. setInputFocus()
  353. } else if (res.data === 1) {
  354. modalForm.value.open()
  355. }
  356. } else {
  357. // #ifdef APP-PLUS
  358. plus.device.beep(2)
  359. // #endif
  360. errorTipMessage.value = res.msg
  361. errorTip.value.open()
  362. errorState.value = 0
  363. }
  364. })
  365. }
  366. } else {
  367. // #ifdef APP-PLUS
  368. plus.device.beep(2)
  369. // #endif
  370. errorTipMessage.value = '请扫描所需物料的物料条码'
  371. errorTip.value.open()
  372. errorState.value = 0
  373. }
  374. })
  375. }
  376. const handleToDetails = function(ret) {
  377. uni.navigateTo({
  378. url: '/pages/workbranch/warehouse/production/mixMaterialDetail'
  379. })
  380. uni.setStorageSync('mixMaterialDetail', JSON.stringify(ret))
  381. }
  382. // 设置高亮
  383. const handleBtnLight = function() {
  384. // #ifdef APP-PLUS
  385. signInput.value.onBlur()
  386. isLightText.value = true
  387. signInput.value.onFocus()
  388. // #endif
  389. }
  390. const setInputFocus = function() {
  391. scanBatchNumber.value = ''
  392. easyinput.value.onBlur()
  393. easyinput.value.onFocus()
  394. }
  395. // 关闭错误信息弹窗
  396. const handleCloseErrorTipsModal = async function() {
  397. errorTip.value.close()
  398. if (errorState.value === 0) {
  399. await setInputFocus()
  400. }
  401. }
  402. // 禁用软键盘
  403. const handleInputFocus = function() {
  404. setTimeout(() => {
  405. uni.hideKeyboard()
  406. }, 100)
  407. }
  408. // uniapp移动端触底事件
  409. onReachBottom(() => {
  410. queryParams.value.pageNo += 1
  411. uni.$reqGet('getPrepareMaterialList', queryParams.value)
  412. .then(({
  413. data
  414. }) => {
  415. Array.prototype.push.call(scanMaterialList.value, ...data?.list ?? [])
  416. })
  417. })
  418. return {
  419. option: [{
  420. text: '删除',
  421. style: {
  422. backgroundColor: '#dd524d'
  423. }
  424. }],
  425. goBack,
  426. popup,
  427. signText,
  428. isBefore,
  429. tableData,
  430. beforeTableData,
  431. signInput,
  432. modalForm,
  433. easyinput,
  434. errorTip,
  435. errorTipMessage,
  436. handleInputFocus,
  437. isLightText,
  438. handleBtnLight,
  439. handleKeydown,
  440. scanBatchNumber,
  441. handleScanMaterial,
  442. handleToNavigate,
  443. scanMaterialList,
  444. handleComplete,
  445. handleSplitMaterial,
  446. handleToDetails,
  447. handleCloseErrorTipsModal
  448. }
  449. }
  450. })
  451. </script>
  452. <style lang="scss" scoped>
  453. .gui-header-leader-btns {
  454. color: black;
  455. font-size: 24px !important;
  456. margin-left: 24rpx;
  457. }
  458. .list-content {
  459. margin-top: 80px;
  460. background-color: #edeeee;
  461. }
  462. .card-list-flexbox {
  463. display: flex;
  464. flex-direction: row;
  465. align-items: center;
  466. flex-wrap: wrap;
  467. margin: 3px 2px;
  468. .card-list-item {
  469. width: 750rpx;
  470. height: 40px;
  471. margin: 2rpx 0;
  472. display: flex;
  473. flex-direction: row;
  474. align-items: center;
  475. justify-content: space-between;
  476. background-color: #fff;
  477. uni-text {
  478. font-size: 14px;
  479. height: 50rpx;
  480. text-align: left;
  481. padding: 0 12px;
  482. display: flex;
  483. flex-direction: row;
  484. align-items: center;
  485. }
  486. .text-1 {
  487. flex: 1;
  488. height: 40px;
  489. justify-content: flex-start;
  490. }
  491. .text-2 {
  492. flex: 3;
  493. height: 40px;
  494. justify-content: flex-end;
  495. margin-right: 4px;
  496. padding: 2px 6px;
  497. }
  498. }
  499. }
  500. .card-list-flexbox:nth-of-type(2) {
  501. margin-top: 48px;
  502. }
  503. .fixedTop {
  504. bottom: 0 !important;
  505. top: 3.25rem !important;
  506. }
  507. .popup-content {
  508. height: 75vh;
  509. overflow-y: scroll;
  510. background-color: #edeeee;
  511. }
  512. .font-icons {
  513. width: 40px;
  514. font-size: 20px;
  515. }
  516. .scan {
  517. height: 45px;
  518. width: calc(100% - 48px);
  519. margin: 12px;
  520. padding: 0 12px;
  521. display: flex;
  522. justify-content: space-between;
  523. align-items: center;
  524. border-radius: 6px;
  525. background-color: white;
  526. .scan-card {
  527. width: 100%;
  528. display: grid;
  529. grid-template-rows: 1fr;
  530. grid-template-columns: 7fr 2fr;
  531. align-items: center;
  532. input {
  533. height: 35px;
  534. line-height: 35px;
  535. }
  536. text {
  537. width: 100%;
  538. text-align: right;
  539. }
  540. }
  541. }
  542. .custom-table {
  543. height: calc(100vh - 265px);
  544. min-height: 230px;
  545. margin: 5px 0;
  546. // min-height: 600px;
  547. overflow-y: scroll;
  548. }
  549. .modal-btns {
  550. height: 100rpx;
  551. line-height: 100rpx;
  552. display: flex;
  553. justify-content: center;
  554. align-items: center;
  555. }
  556. .line {
  557. margin-top: 10rpx;
  558. height: 80rpx;
  559. width: 1rpx;
  560. background-color: #dcdcdc;
  561. }
  562. .tabs {
  563. width: 100%;
  564. height: 45px;
  565. display: flex;
  566. align-items: flex-end;
  567. padding: 0 2px;
  568. background-color: white;
  569. .tabs-list {
  570. border-radius: 3px;
  571. overflow: hidden;
  572. }
  573. .tabs-item {
  574. display: inline-block;
  575. width: 72px;
  576. height: 30px;
  577. line-height: 30px;
  578. padding: 0 8px;
  579. font-size: 14px;
  580. font-weight: bold;
  581. text-align: center;
  582. color: black;
  583. border-bottom: 1.5px dashed #00a0e9;
  584. transition: all .5s ease-in-out;
  585. }
  586. .tabs-item-active {
  587. position: relative;
  588. display: inline-block;
  589. width: 72px;
  590. height: 30px;
  591. line-height: 30px;
  592. padding: 0 8px;
  593. font-size: 14px;
  594. font-weight: bold;
  595. text-align: center;
  596. color: white;
  597. border-left: 1px solid #00a0e9;
  598. border-top: 1px solid #00a0e9;
  599. border-right: 1px solid #00a0e9;
  600. border-bottom: 1.5px solid #00a0e9;
  601. border-radius: 5px 5px 0 0;
  602. animation: .3s linear show;
  603. background-color: #00a0e9;
  604. }
  605. .tabs-item-active::before {
  606. content: '';
  607. position: absolute;
  608. left: -10px;
  609. bottom: 0;
  610. width: 10px;
  611. height: 10px;
  612. background: radial-gradient(circle at 0% 25%, transparent 10px, #00a0e9 0)
  613. }
  614. .tabs-item-active::after {
  615. content: '';
  616. position: absolute;
  617. right: -10px;
  618. bottom: 0;
  619. width: 10px;
  620. height: 10px;
  621. background: radial-gradient(circle at 100% 25%, transparent 10px, #00a0e9 0)
  622. }
  623. }
  624. @keyframes show {
  625. from {
  626. transform: translateY(5%);
  627. }
  628. to {
  629. transform: translateY(0%);
  630. }
  631. }
  632. .sign-btn {
  633. display: flex;
  634. align-items: center;
  635. justify-content: center;
  636. margin: 0 8px;
  637. border: 1px solid #999999;
  638. background-color: white;
  639. border-radius: 6px;
  640. }
  641. </style>